此篇文章主要对C语言中的" 文件读写函数 "进行详细的刨析~通过此篇文章能够了解并学习到:" 字符读写函数 "," 文本行读写函数 "," 格式化读写函数 "," 二进制读写函数 "," 文件随机读取函数 "等相关知识~
(上一篇:文件基础知识传送门:C语言的文件基础知识-CSDN博客)
文件使用方式表:
文件使用方式 | 含义 | 如指定文件不存在 |
" r "(只读) | 为了输入数据,打开一个已经存在的文本文件 | 出错 |
" w "(只写) | 为了输出数据,打开一个文本文件 | 建立一个新文件 |
" a "(追加) | 像文本文件尾添加数据 | 建立一个新文件 |
" rb "(只读) | 为了输入数据,打开一个二进制文件 | 出错 |
" wb "(只写) | 为了输出数据,打开一个二进制文件 | 建立一个新文件 |
" ab "(追加) | 向一个二进制文件尾添加数据 | 出错 |
" r+ "(读写) | 为了读和写,打开一个文本文件 | 出错 |
" w+ "(读写) | 为了读和写,建立一个新的文件 | 建立一个新文件 |
" a+ "(读写) | 打开一个文件,在文件尾进行读写 | 建立一个新文件 |
" rb+ "(读写) | 为了读和写,打开一个二进制文件 | 出错 |
" wb+ "(读写) | 为了读和写,新建一个新的二进制文件 | 建立一个新文件 |
" ab+ "(读写) | 打开一个二进制文件,在文件尾进行读和写 | 建立一个新文件 |
一、文件的打开与关闭
① 文件的打开函数
在我们编写程序,想要实现对文件的读写前,我们需要先打开一个文件,我们可以看到fopen的参数:filename 是文件名,其代表的就是想要进行操作的对应文件(有时文件并不在程序中,我们可以将此处替换成此文件的绝对路径)。
mode 代表的是文件的打开模式,文件的打开模式:
(文件打开成功时,返回文件起始位置的文件指针;文件打开失败时,返回空指针NULL!!!)
测试代码:
int main()
{FILE* pf = fopen("data.txt", "w");if (pf == NULL){perror("fopen");return 1;}return 0;
}
注意!!!!!!
在我们使用fopen去打开一个文件时,为了安全,我们要判断接收文件指针的变量pf是否为空(也就是判断文件是否成功被打开~),如果打开失败,则输出错误原因,并退出此次运行。
这段代码...是不是少了些什么呢?(注意参考动态内存管理中的内存开辟...此代码只"打开"了,但没有"关闭"~)
知道了!既然我们打开了一个文件,相应的就需要关闭文件~
② 文件的关闭函数
此函数的作用就是用于关闭文件。
FILE* stream 指的是想要关闭的文件。
(关闭文件后,需要将pf置空,否则pf会变成危险的野指针!!!)
(就像之前学习动态内存管理时,使用free释放内存后,需要再置空一次~)
那么既然了解了文件的打开与关闭,让我们将两者融合,练习一下~:
int main()
{FILE* pf = fopen("data1.txt", "w");//检查是否打开失败if (pf == NULL){perror("fopen");return 1;}fclose(pf);//防止pf变成野指针pf = NULL;
}
注意:在我们运行此代码之前,我的电脑中是没有"data1.txt"文件的。
而当我们运行代码后,再进行查看,会发现文件中出现了"data1.txt"。
这是因为 " w " 在查找不到目标文件时,就会创建一个新文件,并且命名为目标文件的名字。
而当我们将此文件删除,再使用 " r " 来读取该文件会发生什么呢:
没错,就是报错(找不到该文件)
(如果我们将文件 "data1.txt" 存入其他的文件路径中,那么对 "data1.txt" 的读取是否能够成功呢?)
我们可以看到,这种情况下是无法找到该文件的,那么这时我们就不能找到这个文件了吗?答案是,可以找到~因为此时我们输入的文件名并不完整,所以它找不到另一个文件路径中的该文件,此时我们可以将全部文件名写入:"data1.txt"—>"D:\\data1.txt"
(正常是一个\,写\\会防止\与后面字符结合)
此时我们可以看到,再次进行文件的查找,就能够找到了~
二、文件的顺序读写函数
顺序读写函数表:
功能 | 函数名 | 适用于 |
字符输入函数 | fgetc | 所有输入流 |
字符输出函数 | fputc | 所有输出流 |
文本行输入函数 | fgets | 所有输入流 |
文本行输出函数 | fputs | 所有输出流 |
格式化输入函数 | fscanf | 所有输入流 |
格式化输出函数 | fprintf | 所有输出流 |
二进制输入 | fread | 文件 |
二进制输出 | fwrite | 文件 |
① 字符输出函数 fputc
我们可以看到,fputc函数有两个参数:
int character:代表想要写入文件的字符。
FILE* stream:代表操作的目标文件。
fputc函数的作用:将character代表的字符写入文件stream中,并将位置标识符向前移动。
📚代码演示:创建一个 data.txt ,然后使用fputc函数分别写入 "hello" 到文件中。
int main()
{FILE* pf = fopen("data.txt", "w");if (pf == NULL){perror("fopen");return 1;}int arr[5] = { 'h','e','l','l','o' };for (int i = 0; i < 5; i++){fputc(arr[i], pf);//或者直接fputc('h',pf);...} //的依次存入也可以fclose(pf); //因为每次存入,位置标识符自动向前移动.pf = NULL;return 0;
}
代码运行前:
代码运行后:
② 字符输出函数 fgetc
可以看到,fgetc函数的参数只有FILE* stream。
fgetc函数的作用:从指定的stream中提取下一个字符,并且位置标识符自动向前移动。
(如果调用流时流在文件末尾,则函数返回EOF并设置流的文件末尾标识符)
(如果发生读取错误,函数返回EOF并设置流的错误标识符(ferror))
📚代码演示:将"data.txt"中的数据进行读取并输出:
int main()
{FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}int c;while ((c = fgetc(pf)) != EOF){printf("%c", c);}fclose(pf);pf = NULL;return 0;
}
代码运行前:(该文件中已经存入了"hello")
代码运行:
③ 文本行输出函数 fputs
我们可以看到,fputs函数的参数有两个:
const char* str:存储想要传递的数据(字符串)。
FILE* stream:用于接收数据的目标文件。
fputs函数的作用:将字符串str中的数据传输到stream中。
📚代码演示:使用 fputs 函数向文件"data.txt"中写入"hello world","abcdef"。
int main()
{FILE* pf = fopen("data.txt", "w");if (pf == NULL){perror("fopen");return 1;}char str0[20] = "hello world\n";char str1[20] = "abcdef";fputs(str0, pf);//也可以直接fputs("hello world\n",pf);fputs(str1, pf);fclose(pf);pf = NULL;return 0;
}
代码运行后:
(注意:fputs函数是可以接收换行符 '\n' 的)
④ 文本行输出函数 fgets
fgets函数的参数有三个:
char* str:代表接收文件数据的字符串。
int num:代表传输的字符个数。
FILE* stream:代表提取数据的文件。
fgets函数的作用:从流中获取字符串,并将其作为C字符串存入str中,直到 (num - 1) 字符读取完毕,或到达新行或文件的末尾。
📚代码演示:使用 fgets 函数读取上题中data.txt中的两行字符串:
int main()
{FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}char str[100];fgets(str, 13, pf);printf("%s", str);fgets(str, 7, pf);printf("%s", str);fclose(pf);pf = NULL;return 0;
}
运行代码:
(需要注意的是: "hello world\n" 中共有12个字符,而输出12个字符,要将num设置为13~)
⑤ 格式化输出函数 fprintf
fprintf函数的参数有三个:
FILE* stream:想要写入的目标文件。
const char* format:格式化数据的对应类型。
"......":格式化数据的名字。
fprintf函数的作用:将格式化的数据写入文件。
或许这样看有些不大明了,没关系,让我们对比一下与它极其极其极其相似的函数~:printf
怎么样,这样看来思路就清晰了许多吧?没错,printf与fprintf的差别就是:
"printf是标准输出流的输出函数,用于将输出输出到屏幕上;而fprintf则是向文件输出,二者均是输出函数,只是目标不同~"
📚代码演示:创建一个学生结构体,写入"姓名","年龄","成绩",并初始化数据然后使用 fprintf 将数据存入文件 "data.txt" 中。
struct stu
{char name[20];int age;double score;
};
int main()
{FILE* pf = fopen("data.txt", "w");if (pf == NULL){perror("fopen");return 1;}stu s = { "xiaowang",20,99.5 };fprintf(pf, "%s %d %lf", s.name, s.age, s.score);fclose(pf);pf = NULL;return 0;
}
代码运行:
⑥ 格式化输出函数 fscanf
fscanf函数的参数同样有三个:
FILE* stream:代表从该文件中读取数据。
const char* format:数据的类型。
"...":对应数据的名字。
fscanf函数的作用:对文件中格式化的数据进行读取。
同样的,让我们与"scanf"进行一下比较:
fscanf 与 scanf 二者区别:
"scanf是从标准输入(键盘)读取格式化数据;而fscanf是从文件中读取格式化的数据(所有的输入流),二者读取数据的位置不同~"
📚代码演示:将上段代码中存入文件"data.txt"中的学生信息读取到一个结构体中,并将其数据打印到屏幕上。
struct stu
{char name[20];int age;double score;
};
int main()
{FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}stu s = {};fscanf(pf, "%s %d %lf", s.name, &s.age, &s.score);printf("%s %d %lf", s.name, s.age, s.score);fclose(pf);pf = NULL;return 0;
}
运行代码:
(注意:字符数组名字就是地址,故不用取地址)
⑦ 二进制输出函数 fwrite
fwrite函数有四个参数!!!:
const void* ptr:代表要写入的元素数组。
size_t size:要写入的每个元素的大小(以字节为单位)。
size_t count:代表元素数目。
FILE* stream:指定的目标文件。
fwrite函数的作用:从ptr指向的内存块写入计数元素数组。
(每个数组的大小为数据类型大小的字节。)
(流的位置指示符由写入的字节总数来提高。)
📚代码演示:定义一些数据,并使用 fwrite 输入到文件"data.txt"中:
int main()
{FILE* pf = fopen("data.txt", "wb");if (pf == NULL){perror("fopen");return 1;}int num[5] = { 1,2,4,8,16 };int sz = sizeof(num[0]);int len = sizeof(num) / sizeof(num[0]);fwrite(num, sz, len, pf);fclose(pf);pf = NULL;return 0;
}
运行代码:
欸?此时我们会发现,存入文件中的似乎都是乱码,我们打开文件夹中的文件再查看一下:
仍然是乱码,这是怎么回事呢?
其实这是因为,fwrite是二进制形式的写入,而在文档中一般采用的都是ASCII存储方式,所以当我们直接将二进制形式写入文件中,自然就是看不懂的~
而其实我们强大的VS编译器能够解决这种问题:
而我们也可以使用下面的函数做到读取~
⑧ 二进制输入函数 fread
此函数的参数与上一个函数的参数是一样的(除了ptr不再被const保护)~
fread的作用:从流stream中读取数据到 ptr 所指的数组中。
📚代码演示:
int main()
{FILE* pf = fopen("data.txt", "rb");if (pf == NULL){perror("fopen");return 1;}int num[5] = { 0 };int sz = sizeof(num[0]);fread(num, sz, 5, pf);int len = sizeof(num) / sizeof(num[0]);for (int i = 0; i < len; i++){printf("%d ", num[i]);}fclose(pf);pf = NULL;return 0;
}
运行代码:
三、文件的随机读写函数
① 文件指针定位函数 fseek
fseek函数有三个参数:
FILE* stream:进行操作的文件。
long int offset:相对 origin 的偏移量。
int origin:表示开始添加偏移 offset 的位置,它一般指定为以下列常量之一:
常量 | 描述 |
SEEK_SET | 文件的开头 |
SEEK_CUR | 文件指针的当前位置 |
SEEK_END | 文件的末尾 |
fseek函数的作用:可以根据文件指针所指向的位置,以及偏移量来指定数据。
📚代码演示:在文件"data.txt"中随便写一些数据,并读取(此时文件中有"abc")
int main()
{FILE* pf = fopen("data.txt", "r");if (pf == NULL) {perror("fopen");return 1;}int ch = fgetc(pf);printf("%c\n", ch);ch = fgetc(pf);printf("%c\n", ch);ch = fgetc(pf);printf("%c\n", ch);fclose(pf);pf = NULL;return 0;
}
那如果我想要单独得到一个b,改如何去得到呢?
此时我们就可以使用fseek函数:
int main()
{FILE* pf = fopen("data.txt", "r");if (pf == NULL) {perror("fopen");return 1;}fseek(pf, 1, SEEK_CUR);int ch = fgetc(pf);printf("%c\n", ch);fclose(pf);pf = NULL;return 0;
}
② 返回偏移量函数 ftell
作用:返回文件指针相对起始位置的偏移量。
📚代码演示:使用ftell返回偏移量。(此时文件中为"abcdef")
#include <stdio.h>int main(void)
{FILE* pf = fopen("data.txt", "r");if (pf == NULL) {perror("fopen");return 1;}fseek(pf, 3, SEEK_CUR); int ch = fgetc(pf);printf("%c\n", ch);int ret = ftell(pf);printf("%d\n", ret);fclose(pf);pf = NULL;return 0;
}
③ 文件指针返回起始位置函数 rewind
功能于其名称一样,就是使给定的文件开头,让文件返回起始位置。
📚代码演示:将上一段代码的文件使用rewind返回起始位置。
int main(void)
{FILE* pf = fopen("data.txt", "r");if (pf == NULL) {perror("fopen");return 1;}fseek(pf, 3, SEEK_CUR); int ch = fgetc(pf);printf("%c\n", ch);int ret = ftell(pf);printf("%d\n", ret);rewind(pf);ret = ftell(pf);printf("%d\n", ret);fclose(pf);pf = NULL;return 0;
}
代码运行:
好了,那么关于文件的读写函数相关知识就为大家讲解到这里啦~如果有哪里写的不够清楚或者不够详细的,还请各位多多指出,我也会吸取教训多多改正的,那么我们下一期再见啦~