文件操作

  • 1. 文件指针
  • 2. 文件的打开和关闭
    • 2.1 fopen() 函数
    • 2.2 fclose() 函数
  • 3. 文件的顺序读写
    • 3.1 fputc() 字符输出函数
      • fgetc() 字符输入函数
    • 3.2 fputs() 文本行输出函数
      • fgets() 文本行输入函数
    • 3.3 fprintf() 格式化输出函数
      • fscanf() 格式化输入函数
    • 3.4 fwrite() 二进制输出函数
      • fread() 二进制输入函数
  • 4. scanf/printf、fscanf/fprintf 、sscanf/sprintf 的区别?
  • 5. 文件的随机读写
    • 5.1 fseek() 函数
    • 5.2 ftell() 函数
    • 5.3 rewind() 函数
  • 6. 文本文件和二进制文件
  • 7. 文件读取结束的判定
    • 7.1 常被误用的 feof
  • 8. 文件缓冲区

1. 文件指针

文件指针是 文件类型指针 的简称,指向存放文件信息的位置。

每一个被使用的文件都有一块文件信息区,这是一块名为 FILE 的结构体类型空间,这个结构体里存放的是该文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。

这个结构体类型由系统声明的,我们不需要过多关注,只需要知道这些概念。使用 FILE 指针来维护管理 FILE 结构的变量会更加便捷。

不同的C编译器的FILE类型包含的内容不完全相同,但是大同小异。


2. 文件的打开和关闭

文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。
ANSIC 规定使用 fopen 函数来打开文件,fclose 来关闭文件。

2.1 fopen() 函数

FILE * fopen ( const char * filename, const char * mode )

作用:打开一个文件
参数 filename:文件名
参数 mode:对打开文件的操作模式,详见后表
返回值:FILE* 类型的文件指针,指向存放文件相关信息的区域,失败则返回空指针(需要检查,避免非法访问)
头文件:

注意 文件名包括三部分:文件路径+文件名主干+文件后缀
(如果不写文件路径,默认创建在工程所在源文件路径下~

⚙️模式:

文件使用方式含义如果指定文件不存在
“r”(只读)为了输入数据,打开一个已经存在的文本文件出错
“w”(只写)为了输出数据,打开一个文本文件建立一个新的文件
“a”(追加)向文本文件尾添加数据建立一个新的文件
“rb”(只读)为了输入数据,打开一个二进制文件出错
“wb”(只写)为了输出数据,打开一个二进制文件建立一个新的文件
“ab”(追加)向一个二进制文件尾添加数据出错
“r+”(读写)为了读和写,打开一个文本文件出错
“w+”(读写)为了读和写,建议一个新的文件建立一个新的文件
“a+”(读写)打开一个文件,在文件尾进行读写建立一个新的文件
“rb+”(读写)为了读和写打开一个二进制文件出错
“wb+”(读写)为了读和写,新建一个新的二进制文件建立一个新的文件
“ab+”(读写)打开一个二进制文件,在文件尾进行读和写建立一个新的文件

2.2 fclose() 函数

int fclose ( FILE * stream )

作用:关闭打开的文件
参数:需要关闭文件的文件指针
返回值:如果成功返回0,如果失败返回EOF
头文件:

注意:需要跟 fopen() 成对出现,打开了就需要关闭。

int main(){// 【打开文件】// 相对路径// FILE* pf = fopen("test.txt","w");// "w" - 写模式// 绝对路径FILE* pf = fopen("c:\\file\\test.txt", "w");// 多的 \ 是转义字符if (NULL == pf){perror("fopen");return 1;}// 写文件// 【关闭文件】fclose(pf);pf = NULL;return 0;}

3. 文件的顺序读写

功能函数名适用于
字符 输入函数fgetc所有输入流
字符输出函数fputc所有输出流
文本行 输入函数fgets所有输入流
文本行输出函数fputs所有输出流
格式化 输入函数fscanf所有输入流
格式化输出函数fprintf所有输出流
二进制 输入fread文件
二进制输出fwrite文件

ps:getchar() 只适用于标准输入流

注意:

内存读取文件里的数据,供我们打印出来查看,叫做内存的 操作;
内存把我们输入的内容,输出成文件,叫做内存的 操作。

放张图便于理解:

3.1 fputc() 字符输出函数

int fputc ( int character, FILE * stream )

作用:将指定的字符(一个无符号字符)写入到指定的流 stream 中,并把位置标识符往前移动
参数 character:将要写入的字符
参数 stream:指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符的流
返回值:如果成功返回字符的字符编码,如果失败返回EOF
头文件:
// 输入 A~Z 26 个字母到 test.txt 文件去int main(){FILE* pf = fopen("test.txt","w");// "w" - 写模式if (NULL == pf){perror("fopen");return 1;}// 写文件int i = 0;for (i = 0; i < 26; i++){fputc('A' + i, pf);}// 关闭文件fclose(pf);pf = NULL;return 0;}

fgetc() 字符输入函数

int fgetc ( FILE * stream )

作用:从指定的流 stream 获取下一个字符(一个无符号字符),并把位置标识符往前移动
参数:指向 FILE 对象的指针,该 FILE 对象标识了要在上面执行操作的流
返回值:如果读取正常返回所提取字符的 ASCII 码值,如果失败返回 或读到了文件的末尾 返回EOF
头文件:
// 从文件中提取所有字符进行输出int main(){FILE* pf = fopen("test.txt", "r");// "r" - 读模式if (NULL == pf){perror("fopen");return 1;}// 读文件int ch = 0;while ((ch = fgetc(pf))!=EOF)printf("%c ", ch);fclose(pf);pf = NULL;return 0;}

3.2 fputs() 文本行输出函数

int fputs ( const char * str, FILE * stream )

作用:把字符串写入到指定的流 stream 中,但不包括空字符
参数 str:一个数组,包含了要写入的以空字符终止的字符序列
参数 stream: 这是指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符串的流。
返回值:如果成功返回一个非负数,失败返回 EOF
头文件:

注意:换行由用户输入 \n 控制

int main(){// 打开文件FILE* pf = fopen("test.txt", "w");// "w" - 写模式if (NULL == pf){perror("fopen");return 1;}// 写入文本行fputs("hello files\n", pf);fputs("byebye~~", pf);// 关闭文件fclose(pf);pf = NULL;return 0;}

fgets() 文本行输入函数

char * fgets ( char * str, int num, FILE * stream )

作用:从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符、或者读取到换行符、或者到达文件末尾时,它会停止,具体视情况而定。
参数 str:指向一个字符数组的指针,该数组存储了要读取的字符串。
参数 num:要拷贝到 str 中的最大字符数(包括 str 的终止字符,实际读取的是 N-1 个字符)通常是使用以 str 传递的数组长度。
参数 stream:这是指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的流。
返回值:如果成功,返回相同的 str 参数。如果到达文件末尾或者没有读取到任何字符,str 的内容保持不变,并返回一个空指针。
头文件:

注意:若读到了文本行的末尾自动结束读取(feof),如果一行没有读完,下次调用时从没读完的地方继续读

int main(){// 打开文件FILE* pf = fopen("test.txt", "r");// "r" - 读模式if (NULL == pf){perror("fopen");return 1;}// 一行一行的读文件char ch[20] = "**********";fgets(ch, 5, pf);printf("%s", ch);fgets(ch, 5, pf);printf("%s", ch);// 关闭文件fclose(pf);pf = NULL;return 0;}

格式化 输入函数 fscanf 所有输入流
格式化输出函数 fprintf 所有输出流
二进制 输入 fread 文件
二进制输出 fwrite 文件

3.3 fprintf() 格式化输出函数

输出函数(内存输出到文件),我们用来写数据到文件,你没有看错

int fprintf ( FILE * stream, const char * format, ... )

作用:发送格式化输出到流 stream 中
参数 format:这是 C 字符串,包含了要被写入到流 stream 中的文本。它可以包含嵌入的 format 标签。
参数 stream:指向 FILE 对象的指针
返回值:如果成功,则返回写入的字符总数,否则返回一个负数。
头文件:

注意:语法参考 printf() 函数

// 把 s 中的数据写入文件struct S{char name[20];int age;float score;};int main(){struct S s = { "Kevin",24,95.5f };FILE* pf = fopen("test.txt", "w");if (NULL == pf){perror("fopen");return 1;}// 写文件fprintf(pf, "%s %d %.2f", s.name, s.age, s.score);// 格式间,用空格隔开代表不同的成员fclose(pf);pf = NULL;return 0;}

fscanf() 格式化输入函数

输入函数(往内存中输入文件数据,经保存转化到屏幕),我们用作查看文件内容

int fscanf ( FILE * stream, const char * format, ... )

作用:从流 stream 读取格式化输入
参数 format:C 字符串,包含了以下各项中的一个或多个:空格字符、非空格字符 和 format 说明符。format 说明符形式为 [=%[*][width][modifiers]type=],详见后文
参数 stream:指向 FILE 对象的指针
返回值:如果成功,该函数返回成功匹配和赋值的个数。如果到达文件末尾或发生读错误,则返回 EOF。
头文件:

注意:语法参考 scanf() 函数

int main(){struct S s = {0};// 把文件中的数据以 s 格式输出FILE* pf = fopen("test.txt", "r");if (NULL == pf){perror("fopen");return 1;}// 读文件fscanf(pf, "%s %d %f", s.name, &(s.age), &(s.score));printf("%s%d%.2f\n", s.name, s.age, s.score);fclose(pf);pf = NULL;return 0;}

3.4 fwrite() 二进制输出函数

size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream )

作用:把 ptr 所指向的数组中的数据写入到给定流 stream 中
参数 ptr – 指向要被写入的元素数组的指针
参数 size – 要被写入的每个元素的大小,以字节为单位
参数 count – 元素的个数
参数 stream – 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输出流。
返回值:如果成功返回元素的总数。如果该数字与 count 参数不同,则会显示一个错误。
头文件:
// 把 s 中的数据以二进制写入文件int main(){struct S s = { "Kevin",24,95.5f };FILE* pf = fopen("test.txt", "wb");if (NULL == pf){perror("fopen");return 1;}fwrite(&s, sizeof(s), 1, pf);fclose(pf);pf = NULL;return 0;}

fread() 二进制输入函数

size_t fread ( void * ptr, size_t size, size_t count, FILE * stream )

作用:从给定流 stream 读取数据到 ptr 所指向的数组中
参数 ptr – 这是指向带有最小尺寸 size*count 字节的内存块的指针。
参数 size – 这是要读取的每个元素的大小,以字节为单位。
参数 count – 元素的个数
参数 stream – 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流。
返回值:如果成功返回元素的总数。如果总数与 count 参数不同,则可能发生了一个错误或者到达了文件末尾。
头文件:
int main(){struct S s = { 0 };FILE* pf = fopen("test.txt", "rb");if (NULL == pf){perror("fopen");return 1;}// 二进制读文件fread(&s, sizeof(s), 1, pf);printf("%s%d%.2f\n", s.name, s.age, s.score);fclose(pf);pf = NULL;return 0;}

4. scanf/printf、fscanf/fprintf 、sscanf/sprintf 的区别?

适用于标准输入 / 输出的格式化的输入 / 输出语句

scanf:按照一定的格式从键盘输入数据
printf:按照一定的格式把数据打印(输出)到屏幕上

适用于所有的输入 / 输出流的格式化输入 / 输出语句

fscanf:按照一定的格式从输入流(文件 / stdin)输入数据
fprintf:按照一定的格式向输出流(文件 / stdout)输出数据

不是简单的输入输出,还进行了相应的转化

sscanf:从字符串中按照一定的格式读取出格式化的数据
sprintf:把格式化的数据按照一定的格式转化成字符串
int sscanf ( const char * s, const char * format, ...)

作用:从字符串中按照一定的格式读取出格式化的数据
参数 ptr – 这是指向带有最小尺寸 size*count 字节的内存块的指针。
参数 str:字符串
参数 format:参考 scanf() 的语法
返回值:如果成功,该函数返回成功匹配和赋值的个数。如果到达文件末尾或发生读错误,则返回 EOF。
头文件:
int sprintf ( char * str, const char * format, ... )

作用:把格式化的数据按照一定的格式转化成字符串
参数 str:字符串
参数 format:参考 printf() 的语法
返回值:如果成功,则返回写入的字符总数,不包括字符串追加在字符串末尾的空字符。如果失败,则返回一个负数。
头文件:
int main(){char buf[100] = { 0 };struct S s = { "Kevin",24,95.5f };// 把结构体类型的数据,转换成字符串 "Kevin 24 95.5"sprintf(buf, "%s%d%.2f\n", s.name, s.age, s.score);printf("%s", buf);// 以字符串形式打印// 将 buf 里面的字符串还原成结构体类型,并存入struct S tmp = { 0 };sscanf(buf, "%s%d%f", tmp.name, &(tmp.age), &(tmp.score));printf("%s%d%.2f\n", tmp.name, tmp.age, tmp.score);// 以结构体类型打印return 0;}

5. 文件的随机读写

5.1 fseek() 函数

相当于把指针停在指定位置,可以接着在该处对其进行读写

int fseek ( FILE * stream, long int offset, int origin )

作用:设置流 stream 的文件位置为,从基点处给定的偏移量
参数 stream:指向 FILE 对象的指针,该 FILE 对象标识了流。
参数 offset:相对 oringin 的偏移量,以字节为单位。
参数 origin:表示开始添加偏移 offset 的位置。它一般指定为下表常量之一
返回值:如果成功返回 0,否则返回非零。
头文件:
常量描述
SEEK_SET文件的开头
SEEK_CUR文件指针的当前位置
SEEK_END文件的末尾

5.2 ftell() 函数

long int ftell ( FILE * stream )

作用:返回给定流 stream 的当前文件位置
参数:指向 FILE 对象的指针,该 FILE 对象标识了流。
返回值:返回位置标识符的当前值,即离文件开始处的偏移量,类型为长整型
头文件:
int main(){FILE* pf = fopen("test.txt", "rb");if (NULL == pf){perror("fopen");return 1;}// 读文件int ch = fgetc(pf);printf("%c\n", ch);//a!!指针会停在前一步操作结束的位置!!fseek(pf, 3, SEEK_SET);ch = fgetc(pf);printf("%c\n", ch);//dint pos = ftell(pf);printf("%d\n",pos);//4fseek(pf, -4, SEEK_END);ch = fgetc(pf);printf("%c\n", ch);//Drewind(pf);ch = fgetc(pf);printf("%c ", ch);//afclose(pf);pf = NULL;return 0;}------------test.txt: abcdefgGFEDCBA

5.3 rewind() 函数

void rewind(FILE *stream)

作用:设置文件位置为给定流 stream 的文件的开头。
头文件:

6. 文本文件和二进制文件

根据数据的组织形式,数据文件被称为 文本文件 或者 二进制文件
数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是 二进制文件
如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是 文本文件

一个数据在内存中要怎么储存呢?

字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。

:如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),而二进制形式输出,则在磁盘上只占4个字节(VS2019测试)。


大小端相关知识点戳这里‍


测试代码:

#include int main(){ int a = 10000; FILE* pf = fopen("test.txt", "wb"); fwrite(&a, 4, 1, pf);//二进制的形式写到文件中 fclose(pf); pf = NULL; return 0;}
用 VS 查看测试代码生成的二进制编码:

step 1:运行测试代码后,在源文件中添加现有项,找到 test.txt 文件
step 2:右击 test.txt 打开方式,选择二进制编辑器

测试结果:


7. 文件读取结束的判定

7.1 常被误用的 feof

该函数应用于当 文件已经读取结束时,判断其结束原因,是因为读取失败结束,还是遇到文件尾结束

(有很多人错误使用,想要通过 feof 的返回值去判断文件是否结束…不是这样用的嗷

那怎么判断文件是否读取到结束呢?

文本文件读取是否结束,判断返回值是否为 EOF ( eg: fgetc ),或者 NULL ( eg: fgets )
二进制文件读取是否结束,判断返回值是否小于实际要读的个数( eg: fread )

8. 文件缓冲区

ANSIC 标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的。

测试代码:

#include //VS2019 WIN11环境测试int main(){FILE* pf = fopen("test.txt", "w");fputs("abcdef", pf);//先将代码放在输出缓冲区printf("睡眠10秒-已经写数据了,打开test.txt文件,发现文件没有内容\n");Sleep(10000);printf("刷新缓冲区\n");fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到文件(磁盘)//注:fflush 在高版本的VS上不能使用了printf("再睡眠10秒-此时,再次打开test.txt文件,文件有内容了\n");Sleep(10000);fclose(pf);//注:fclose在关闭文件的时候,也会刷新缓冲区pf = NULL;return 0;}
这里可以得出一个 结论

因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候 关闭文件
如果不做,可能导致读写文件的问题。