🐵本篇文章将会对文件相关知识进行讲解

当我们写了一个简单的项目比如一个通讯录程序,我们添加完成员信息并关闭程序后,这些成员的信息也会随着程序的结束而不复存在了;所以为了使得数据持久化,我们将数据存放到文件中,因为文件是放在硬盘上的,数据不会丢失
磁盘上的文件就是文件,在程序设计中我们讨论的文件有两种:程序文件,数据文件
程序文件包括源程序文件(.c)、目标文件(.obj)、可执行程序(.exe)

文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件或者输出内容的文件(.txt就是数据文件)

比如将通讯录成员的信息写到文件中,将文件中的信息读取出来;这里说的文件就是数据文件
每一个文件都要有唯一的文件标识也就是文件名,以便用户识别
文件名包含3部分:文件路径+文件主干名+文件后缀比如D:\code\data.txt

每一个使用过的文件都在内存上开辟了一块文件信息区,文件信息区实际上就是一个结构体,这个结构体用来存放文件的各种信息(比如:文件的名字、状态、位置等)该结构体类型是系统声明的,取名为:FILE
每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并自动填充其中的信息,使用者不必关心细节
一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。下面我们可以创建一个FILE* 的指针变量:
FILE* pf
pf就是一个指向FILE类型的指针,可以使pf指向某个文件的文件信息区(也可以说是一个结构体变量)通过pf可以找到文件信息区的信息,进而访问该文件;通过文件指针变量能够找到与它关联的文件
fopen函数原型:

fopen是以mode的方式打开文件filename,filename是文件名,可以字符串的形式表示,第二个参数是打开文件的方式,也是以字符串的形式表示
| 文件使用方式 | 含义 | 如果指定文件不存在 |
| r(只读) | 为了(输入)读取数据,打开一个已存在的文本文件 | 报错 |
| w(只写) | 为了(输出)写入数据,打开一个文本文件 | 建立一个新的文件 |

fclose函数原型:

参数stream是指向某个文件的指针,关闭文件后要将stream赋为空指针,以防止野指针
实例演示:
- int main()
- {
- FILE* pf = fopen("data.txt", "w");
- if (pf == NULL)
- {
- perror("fopen");
- return 1;
- }
- fclose(pf);
- pf == NULL;
-
- return 0;
- }
当使用fopen打开文件data.txt后,系统会自动创建一个FILE类型的变量,并向这个变量填充信息,之后将这个变量的地址传给pf
如果在当前路径(与.c文件相同的路径)没有data.txt,系统会自动创建一个同名文件

文件路径:文件路径有两种:绝对路径和相对路径
相对路径中.\\表示当前路径,..\\表示上一级路径
FILE* pf = fopen(".\\data.txt", "r"); //相对路径(此时是当前路径)
FILE* pf = fopen("C:\\Project\\C\\study\\study_10_8\\data.txt", "r"); //绝对路径(完整表述文件位置的路径)
| 功能 | 函数 | 适用于 |
| 字符输入函数 | fgetc | 所有输入流 |
| 字符输出函数 | fputc | 所有输出流 |

向不同的外部设备传输数据的方法是不同的,为了简化这种传输方式,引入了流,我们可以将数据传输到流中,然后流会将数据传输到对应的外部设备里

流有文件流,标准输入流,标准输出流,标准错误流
操作文件前要先打开文件,操作完文件后要关闭文件,在使用scanf和printf时为什么没有打开键盘和打开屏幕的操作,这是因为C语言程序只要运行起来就会自动打开三个流:标准输入流(stdin)标准输出流(stdout)标准错误流(stderr),这三个流的类型也都是FILE*
fgetc函数原型:

fgetc用于读取文件的一个字符,stream是一个FILE*类型的指针,也可以理解为是一个流,fgetc如果读取失败则会返回EOF
实例演示:
事先向data.txt文件中输入abcdef
- int main()
- {
- FILE* pf = fopen("data.txt", "r");//读文件就要以只读的方式打开文件
- //写文件就要以只写的方式打开
- if (pf == NULL)
- {
- perror("fopen");
- return 1;
- }
- int ch = 0;
- while ((ch = fgetc(pf)) != EOF)
- {
- printf("%c", ch);
- }//结果为abcdef
-
- fclose(pf);
- pf = NULL;
-
- return 0;
- }
fputc函数原型:

fputc用于向文件输入(写)一个字符,character是要输入的那一个字符,stream是一个FILE*类型的指针,也可以理解为一个流
实例演示:
- int main()
- {
- FILE* pf = fopen("data.txt", "w");
- if (pf == NULL)
- {
- perror("fopen");
- return 1;
- }
-
- char ch = 0;
- for (ch = 'a'; ch <= 'z'; ch++)
- {
- fputc(ch , pf); //输出到文件(写文件)
- fputc(ch, stdout); //输出到屏幕上,此时和printf功能相同
- }
- fclose(pf);
- pf == NULL;
-
- return 0;
- }

| 功能 | 函数 | 适用于 |
| 文本行输入函数 | fgets | 所有输入流 |
| 文本行输出函数 | fputs | 所有输出流 |
| 格式化输入函数 | fscanf | 所有输入流 |
| 格式化输出函数 | fprintf | 所有输出流 |
| 二进制输入 | fread | 文件流 |
| 二进制输出 | fwrite | 文件流 |
| 将格式化数据转化为字符串 | sprintf | --- |
| 将字符串转化为格式化数据 | sscanf | --- |
fgets函数原型:

fgets函数用于文本行输入,每次读取流中一行数据,即如果流中有多行数据,在读取其中的某一行时,当读取到'\n'就停止读取;fgets会从流stream中读取num - 1个字符到str中
- FILE* pf = fopen("data.txt", "r");
- char arr[100] = { 0 };
- fgets(arr,num, pf);
- printf("%s", arr);
事先在data.txt中输入hello<换行>world,当num是5时,意思是将pf中5 - 1 = 4个字符读取到arr中,arr的打印结果为hell,当num为100时,会读取到第一行的'\n'所以会将"hello\n"读取到arr中
fputs函数原型:

fputs用于文本行输出,将str的数据写到流stream中,直到遇到'\0'
- FILE* pf = fopen("data.txt", "w");
- fputs("abcdef\0abcd", pf);
上述代码将"abcdef\0abcd"写到pf中,因为字符串中有'\0',所以只会将abcdef写到pf中,那么data.txt中的内容就是abcdef
fscanf函数原型:

fscanf用于格式化输入,直接看代码:
- struct S
- {
- float f;
- int i;
- char c;
- };
-
- FILE* pf = fopen("data.txt", "r");
-
- struct S s = { 0 };
- fscanf(pf, "%f %d %c", &(s.f), &(s.i), &(s.c));
- printf("%f %d %c", (s.f), (s.i), (s.c));
事先在data.txt中输入3.14 5 c,上述代码就是读取pf中的数据以特定的格式到结构体s对应的变量中,最后打印结果为3.140000 5 c
fprintf函数原型:

fprintf函数用于格式化输出,直接看代码:
- struct S
- {
- float f;
- int i;
- char c;
- };
-
- FILE* pf = fopen("data.txt", "w");
-
- struct S s = {3.14, 5, 'c'};
- fprintf(pf, "%f %d %c", s.f, s.i, s.c);
-
上述代码是将结构体变量初始化的数据以特定的格式写入文件data.txt中
| 文件使用方式 | 含义 | 如果指定文件不存在 |
| rb(只读) | 为了输入数据,打开一个二进制文件 | 出错 |
| wb(只写) | 为了输出数据,打开一个二进制文件 | 建立一个新的文件 |
fwrite函数原型:

fwrite是对一个二进制文件写入数据,fwrite的功能是将ptr中的内容写到stream中,size是写入数据的字节大小,count是写入的元素个数
- FILE* pf = fopen("data.txt", "wb");
-
- int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
- fwrite(arr, sizeof(int), sizeof(arr)/sizeof(arr[0]), pf);
在data.txt中显示的内容是:

这些数据我们无法理解其含义,所以要通过fread将该二进制文件中的数据读取出来并打印到屏幕上(我们向文件中写的是1~10,那么读取后打印到屏幕上的也应该是1~10)
fread函数原型:

fread是读取一个二进制文件中的数据,fread的功能是将stream中的数据读取到ptr中,stream中每一个数据的字节大小是size,一共读取count个元素
- FILE* pf = fopen("data.txt", "rb");
- int arr1[10] = { 0 };
- fread(arr, sizeof(int), 10, pf);
-
- int i = 0;
- for (i = 0; i < 10; i++)
- {
- printf("%d ", arr1[i]);
- }
打印结果为:

sprintf函数原型:

sprintf用于将格式化的数据转化为字符串
sscanf函数原型:

sscanf用于将字符串转化为格式化数据
- struct S
- {
- float f;
- char c;
- int i;
- };
-
- int main()
- {
- struct S s = { 3.14, 'c', 100 };
- char arr[100] = { 0 };
- sprintf(arr, "%f %c %d", (s.f), (s.c), (s.i));
- printf("%s\n", arr);
-
- struct S t = { 0 };
- sscanf(arr, "%f %c %d", &(t.f), &(t.c), &(t.i));
- printf("%f %c %d\n", t.f, t.c, t.i);
- return 0;
- }
打印结果为:

我们可以通过调试观察arr

接下来通过对比来总结printf, scanf, fprintf, fscanf, sprintf, sscanf这六个函数
printf:格式化输出函数,适用于标准输出流(屏幕)
scanf:格式化输入函数,适用于标准输入流(屏幕)
fprintf:格式化输出函数,适用于所有输出流(文件流,标准输出流)
fscanf:格式化输入函数,适用于所有输入流(文件流,标准输入流)
sprintf:将格式化数据转化为字符串
sscanf:将字符串转化为格式化数据
🙉至此,对文件的第一部分讲解结束,接下来会继续讲解文件相关知识