引言
文件操作是实现数据持久化、跨程序数据交换及复杂数据处理的关键。今天我们来学习一下C语言中的文件操作。
什么是文件
文件是存储在计算机存储设备上的数据集合,可以是文本、图片、视频、音频等多种形式,用于存储和传输信息。 这个数据集有一个名称,叫做文件名。
在程序设计中,我们一般谈的文件有两种:程序文件和数据文件。
1.文件名
一个文件要有一个唯一的文件标识,以便于用户识别和引用。
文件名包含3部分:文件路径+文件名主干+文件后缀
例如:c:\code:test.txt
2.程序文件
程序文件包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。
3.数据文件
文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输入内容的文件。
在本篇博客中,我们讨论的是数据文件。
在以前各章所处理数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运⾏结果显示到显示器上。
其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上文件。
二进制文件和文本文件
根据数据的组织形式,数据文件被称为文本文件或者二进制文件。
数据在内存中以二进制的形式存储,如果不加转换的输出到外存的文件中,就是二进制文件。
如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。
⼀个数据在文件中是怎么存储的呢?
字符⼀律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。
如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符⼀个字节),而⼆进制形式输出,则在磁盘上只占4个字节。
字符1的二进制序列:00110001,字符0的二进制序列:00110000
测试代码:
int main()
{
int a = 10000;
FILE* pf = fopen("test.txt", "wb");
fwrite(&a, 4, 1, pf);//二进制的形式写到文件中
fclose(pf);
pf = NULL;
return 0;
}
我们可以在使用的编译器中去查看test.txt文件
(1)右击源文件,添加现有项,将test.txt添加进入
(2)右击test.txt文件,选择打开方式,选择二进制编辑器
直接点击打开是不行的。
选择打开方式,选择二进制编辑器
10 27 00 00便是10000以小端存储的十六进制形式。
文件的作用
如果没有文件,我们写的程序的数据是存储在电脑的内存中,如果程序退出,内存回收,数据就丢失了,等再次运行程序,是看不到上次程序的数据的,如果要将数据进行持久化的保存,我们就需要使用文件。
文件的打开和关闭
1.流和标准流
1.1 流
我们程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输入输出操作各不相同,为了方便程序员对各种设备进行方便的操作,我们抽象出了流的概念,我们可以把流想象成流淌着字符的河。
C程序针对文件、画面、 键盘等的数据输入输出操作都是通过流操作的。一般情况下,我们要想向流里写数据,或者从流中读取数据,都是要打开流,然后操作。
流的概念极大地简化了程序员在处理各种输入输出操作时的复杂性,使得无论数据是来源于文件、键盘、屏幕还是其他设备,都可以通过统一的方式进行处理。
1.2 标准流
那么,当我们从键盘输入数据,向屏幕上输出数据,并没有打开流呢?
那是因为C语言程序在启动的时候,默认打开了3个流:
stdin-标准输入流,在大多数的环境中从键盘输入,scanf函数就是从标准输入流中读取数据。
stdout-标准输出流,大多数的环境中输出至显示器界面,printf函数就是将信息输出到标准输出流中。
stderr-标准错误流,大多数环境中输出到显示器界面。
这是默认打开了这三个流,我们使用scanf、printf等函数就可以直接进进行输入输出操作的。stdin、stdout、stderr三个流的类型是: FILE* ,通常称为文件指针。
在C语言中,就是通过 FILE* 的文件指针来维护流的各种操作的。
2.文件指针
在缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名 FILE 。
例如,VS2022编译环境提供的 stdio.h 头文件中有以下的文件类型申明:
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
FILE* pf;//文件指针变量
不同的C编译器的FILE类型包含的内容不完全相同,但是大同小异。每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息,使用者不必关心细节。
一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。我们来看看如何创建一个FILE的指针变量.
FILE* pf; //文件指针变量
定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件,以此来进行相关操作。
3.文件的打开和关闭
文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。
在编写程序的时候,在打开文件的同时,都会返回⼀个 FILE* 的指针变量指向该文件,也相当于建立了指针和文件的关系。
ANSIC规定使用 fopen 函数来打开文件, fclose 函数来关闭文件。
这类函数的使用都需要包含头文件 stdio.h 。
3.1 fopen
fopen 是 C 语言标准库中用于打开文件的一个函数,它定义在 <stdio.h> 头文件中。fopen 函数用于根据指定的模式(如只读、只写、读写等)打开一个文件,并返回一个指向 FILE 类型的指针,该指针可用于后续的文件操作(如读取、写入、定位等)。如果文件打开失败,fopen 将返回 NULL。
函数原型:
FILE * fopen ( const char * filename, const char * mode );
参数:
filename:要打开文件的名称,包括路径(如果文件不在当前工作目录下)。
mode:指定文件打开的模式。
作用:
使用给定的模式 mode 打开 filename 所指向的文件
返回值:
成功时,返回一个指向 FILE 对象的指针,该对象可用于后续的文件操作。
失败时,返回 NULL。
下表是常见的文件打开模式:
文件使用方式 | 含义 | 如果指定文件不存在 |
"r"(只读) | 为了输入数据,打开一个已经存在的文本文件 | 出错 |
“w”(只写) | 为了输出数据,打开一个文本文件 | 建立一个新的文件 |
“a”(追加) | 向文本文件尾添加数据 | 建立一个新的文件 |
“rb“(只读) | 为了输入数据,打开一个二进制文件 | 出错 |
”wb“(只写) | 为了输出数据,打开一个二进制文件 | 建立一个新的文件 |
”ab“(追加) | 向一个二进制文件尾添加数据 | 建立一个新的文件 |
”r+“(读写) | 为了读和写,打开一个文本文件 | 出错 |
”w+“(读写) | 为了读和写,建立一个新的文件 | 建立一个新的文件 |
”a+“(读写) | 打开一个文件,在文件尾进行读写 | 建立一个新的文件 |
”rb+“(读写) | 为了读和写打开一个二进制文件 | 出错 |
”wb+“(读写) | 为了读和写,新建一个新的二进制文件 | 建立一个新的文件 |
”ab+“(读写) | 打开一个二进制文件,在文件尾进行读和写 | 建立一个新的文件 |
3.2 fclose
fclose 是 C 语言和 C++ 中标准库函数的一部分,用于关闭一个已经打开的文件。这个函数在 <stdio.h> 头文件中定义。fclose 函数的主要作用是释放与文件相关联的所有资源,包括文件描述符、缓冲区等,并确保所有的输出操作都已经被执行到文件中。
函数原型:
int fclose ( FILE * stream );
参数:
stream:指向 FILE 对象的指针,该 FILE 对象指定了要被关闭的流
作用:
关闭流 stream。刷新所有的缓冲区
返回值:
如果流成功关闭,则该方法返回零。
如果失败,则返回 EOF。
接下来是fopen和fclose的具体使用示例:
#include <stdio.h>
int main()
{
FILE* pFile;
//打开⽂件
pFile = fopen("myfile.txt", "w");
//⽂件操作
if (pFile != NULL)
{
fputs("fopen example", pFile);
//关闭⽂件
fclose(pFile);
}
return 0;
}
文件的顺序读写
1.顺序读写函数介绍
函数名 | 功能 | 适用于 |
fgetc | 字符输入函数 | 所有输入流 |
fputc | 字符输出函数 | 所有输出流 |
fgets | 文本行输入函数 | 所有输入流 |
fputs | 文本行输出函数 | 所有输出流 |
fscanf | 格式化输入函数 | 所有输入流 |
fprintf | 格式化输出函数 | 所有输出流 |
fread | 二进制输入 | 文件 |
fwrite | 二进制输出 | 文件 |
上面说的适用于所有输入流⼀般指适用于标准输入流和其他输入流(如文件输入流);所有输出流⼀般指适用于标准输出流和其他输出流(如文件输出流)。
以上函数的使用均需要包含头文件 stdio.h 。
2.单字符输入输出
2.1 fputc
fputc 是 C 语言标准库中的一个函数,用于将一个字符写入到指定的文件流中。这个函数是文件输出操作的基础,允许程序向文件逐个字符地写入数据。
函数原型:
int fputc ( int character, FILE * stream );
参数:
char:这是一个整型参数,表示要写入文件的字符。该字符以其对应的 int 值进行传递。
stream:这是一个指向 FILE 对象的指针,该对象标识了要写入的文件流。
作用:
把参数 char 指定的字符(一个无符号字符)写入到指定的流 stream 中。
返回值:
如果没有发生错误,则返回被写入的字符。如果发生错误,则返回 EOF,并设置错误标识符。
下面是fputc的使用示例:
int main()
{
FILE* pf = fopen("example.txt", "w"); // 打开文件用于写入
if (pf == NULL)
{
perror("fopen fail:");
return 1;
}
// 写入字符到文件
fputc('H', pf);
fputc('e', pf);
fputc('l', pf);
fputc('l', pf);
fputc('o', pf);
fputc('\n', pf);
// 关闭文件
fclose(pf);
return 0;
}
2.2 fgetc
fgetc 是 C 语言标准库中的一个函数,用于从指定的文件流中读取一个字符。这个函数是文件输入输出操作中非常基础且常用的一个,它提供了逐字符读取文件内容的能力。
函数原型:
int fgetc ( FILE * stream );
参数:
stream :一个指向 FILE 对象的指针,该对象标识了要进行读取操作的文件流。
作用:
从指定的流 stream 获取下一个字符(一个无符号字符)。
返回值:
该函数以无符号 char 强制转换为 int 的形式返回读取的字符,如果到达文件末尾或发生读错误,则返回 EOF。
下面是fgetc的使用示例:
#include<stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("example.txt", "r");
if (pf == NULL)
{
perror("fopen fail:");
return 1;
}
fputc('w', pf);
fputc('o', pf);
fputc('r', pf);
fputc('l', pf);
fputc('d', pf);
int ch = fgetc(pf);
printf("%c ", ch);
ch = fgetc(pf);
printf("%c ", ch);
ch = fgetc(pf);
printf("%c ", ch);
ch = fgetc(pf);
printf("%c ", ch);
ch = fgetc(pf);
printf("%c ", ch);
//关闭文件
fclose(pf);
pf = NULL;//防止野指针
return 0;
}
3.文本行输入输出
3.1 fputs
fputs 是一个在 C 语言标准库中定义的函数,用于将一个字符串写入到指定的文件中。
函数原型:
int fputs ( const char * str, FILE * stream );
参数:
s:指向要写入文件的字符串的指针。这个字符串以 null 字符('\0')结尾,但 fputs 不会在写入时将 null 字符写入文件。
stream:指向 FILE 对象的指针,该对象标识了要写入的文件流。
作用:
把字符串写入到指定的流 stream 中,但不包括空字符。
返回值:
该函数返回一个非负值,如果发生错误则返回 EOF。
下面是fputs的使用示例:
#include<stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("example.txt", "w");
if (pf == NULL)
{
perror("fopen fail:");
return 1;
}
fputs("hello world", pf);
//关闭文件
fclose(pf);
pf = NULL;//防止野指针
return 0;
}
3.2 fgets
fgets 是一个在 C 语言标准库中定义的函数,用于从指定的文件流中读取一行数据。
函数原型:
char * fgets ( char * str, int num, FILE * stream );
参数:
str:指向一个字符数组的指针,该数组至少应包含 num 个字符的大小,用于存储读取的数据。
num:指定要读取的最大字符数(包括最后的空字符 '\0')。
stream:指向 FILE 对象的指针,该对象标识了要被读取的输入流。
作用:
从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。
返回值:
成功时,fgets 返回指向 str 的指针。
如果发生错误或到达文件末尾而没有读取任何字符,则返回 NULL。
下面是fgets的使用示例:
#include<stdio.h>
int main()
{
FILE* pf = fopen("example.txt", "r");
if (pf == NULL)
{
perror("fopen fail:");
return 1;
}
fputs("hello world", pf);
char arr[] = "##########";
fgets(arr, 5, pf);
puts(arr);
fclose(pf);
pf = NULL;
return 0;
}
输出结果为:
我们通过监视窗口来观察一下:
我们可以看到'\0
也会默认添加进去,因此只会显示4个字符。
4.格式化输入输出
4.1 fprintf
fprintf 是一个在 C 语言标准库中定义的函数,用于向指定的文件流中写入格式化的数据。
函数原型:
int fprintf ( FILE * stream, const char * format, ... );
参数:
FILE *stream:指向 FILE 对象的指针,该对象标识了要被写入数据的文件流。
const char *format:格式化字符串,指定了后续参数应该如何被格式化和写入文件。
...:可变参数列表,表示要写入文件的数据。
作用:
按照一定格式向输出流输出数据。
返回值:
如果成功,则返回写入的字符总数,
如果失败,则返回 EOF。
下面是 fprintf 的具体使用:
#include<stdio.h>
typedef struct student
{
char name[20];
int id;
float score;
}stu;
int main()
{
stu s = { "qing", 231212, 80.0 };
FILE* pf = fopen("example.txt", "w");
if (pf == NULL)
{
perror(" fopen fail");
return 1;
}
fprintf(pf, "%s %d %f", s.name, s.id, s.score);
fclose(pf);
pf = NULL;
return 0;
}
4.2 fscanf
fscanf 是一个在 C 语言标准库中定义的函数,用于从文件读取格式化输入的函数。
函数原型:
int fscanf ( FILE * stream, const char * format, ... );
参数:
FILE *stream:这是一个指向 FILE 类型的指针,指定了要从中读取数据的文件或流。
const char *format:这是一个格式字符串,用于指定输入数据的格式。
...:这是一个可变参数列表,其数量和类型应与 format 字符串中的格式说明符一一对应。
作用:
按照一定格式从输入流输入数据。
返回值:
如果成功,该函数返回成功匹配和赋值的个数。
如果到达文件末尾或发生读错误,则返回 EOF。
下面是fscanf的使用示例:
#include<stdio.h>
typedef struct student
{
char name[20];
int id;
float score;
}stu;
int main()
{
stu s = { "qing", 231212, 80.0 };
FILE* pf = fopen("example.txt", "r");
if (pf == NULL)
{
perror("fopen fail:");
return 1;
}
fscanf(pf, "%s %d %f", s.name, &(s.id), &(s.score));
printf("%s %d %f", s.name, s.id, s.score);
fclose(pf);
pf = NULL;
return 0;
}
5.二进制输入输出
5.1 fwrite
fwrite 是一个在 C 语言标准库中定义的函数,用于将数据块写入到指定的文件中。
函数原型:
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
参数:
ptr:指向要写入文件的数据的指针。
size:指定了每个数据项的大小(以字节为单位)。
count:这个参数指定了要写入的数据项的数量。
stream:指向 FILE 对象的指针,该对象标识了要写入的文件流。
作用:
把 ptr 所指向的数组中的数据写入到给定流 stream 中。
返回值:
如果成功,该函数返回一个 size_t 对象,表示元素的总数,该对象是一个整型数据类型。如果该数字与 nmemb 参数不同,则会显示一个错误。
下面是fwrite的使用示例:
typedef struct student
{
char name[20];
int id;
float score;
}stu;
int main()
{
stu s = { "qing", 231212, 80.0 };
FILE* pf = fopen("example.txt", "wb");
if (pf == NULL)
{
perror("fopen fail");
return 1;
}
fwrite(&s, sizeof(s), 1, pf);
fclose(pf);
pf = NULL;
return 0;
}
虽然我们看不懂,但是电脑可以精准识别。
5.2 fread
fread 是一个在 C 语言标准库中定义的函数,从文件流中读取数据。
函数原型:
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
参数:
ptr:指向用于接收数据的缓冲区的指针。这个缓冲区必须足够大,以存储读取的数据。
size:指定要读取的每个数据单元的大小(以字节为单位)。
count:指定要读取的数据单元的数量。
stream:指向 FILE 对象的指针,该对象标识了要从中读取数据的文件流。
作用:
从给定流 stream 读取数据到 ptr 所指向的数组中。
返回值:
成功读取的元素总数会以 size_t 对象返回,size_t 对象是一个整型数据类型。如果总数与 count 参数不同,则可能发生了一个错误或者到达了文件末尾。
下面是fread的使用示例:
typedef struct student
{
char name[20];
int id;
float score;
}stu;
int main()
{
stu s = { "qing",231212,80.0 };
FILE* pf = fopen("example.txt", "w");
if (pf == NULL)
{
perror(" fopen fail");
return 1;
}
fread(&s, sizeof(s), 1, pf);
printf("%s %d %f", s.name, s.id, s.score);
fclose(pf);
pf = NULL;
return 0;
}
6. 字符串格式化输入输出
6.1 sscanf
sscanf 是 C 语言标准库中的一个函数,用于从字符串中读取格式化输入。
函数原型:
int sscanf ( const char * s, const char * format, ...);
变量:
str:指向要解析的字符串的指针。
format:格式字符串,指定了要匹配的格式规则。
...:可变参数列表,用于接收解析后的数据。
作用:
将格式化数据转换为字符串
返回值:
如果成功,则返回写入的字符总数,不包括字符串追加在字符串末尾的空字符。
如果失败,则返回 EOF。
下面是sscanf的使用示例:
typedef struct student
{
char name[20];
int id;
float score;
} stu;
int main()
{
char str[100] = "qing 231212 80.0";
stu s = { 0 }; // 初始化结构体为0
// 从字符串中读取数据到结构体
if (sscanf(str, "%s %d %f", s.name, &s.id, &s.score) == 3)
{
printf("%s %d %.2f\n", s.name, s.id, s.score);
}
else
{
printf("读取失败\n");
}
return 0;
}
6.2 sprintf
sprintf 是 C 语言中的一个标准库函数,用于将格式化的数据写入字符串中。
函数原型:
int sprintf ( char * str, const char * format, ... );
变量:
str:指向字符数组的指针,用于存储格式化后的字符串。
format:格式字符串,用于指定输出格式。
...:可变数量的参数,用于指定要写入字符串的数据。
作用:
将格式化数据转换为字符串。
返回值:
如果成功,则返回写入的字符总数,不包括字符串追加在字符串末尾的空字符。
如果失败,则返回EOF。
下面是sprintf的使用示例:
typedef struct student
{
char name[20];
int id;
float score;
}stu;
int main()
{
char str[100] = { 0 };
stu s = { "qing", 231212, 80.0 };
sprintf(str, "%s %d %f", s.name, s.id, s.score);
printf("%s\n", str);
return 0;
}
6.3 对比
下表展示了scanf与printf,fscanf与fprintf,sscanf与sprintf之间的区别:
函数 | 功能 |
scanf | 针对标准输入(键盘)的格式化输入函数 |
printf | 针对标准输入出(屏幕)的格式化输出函数 |
fscanf | 针对所以输入流的格式化输入函数 |
fprintf | 针对所以输出流的格式化输出函数 |
sscanf | 从一个字符串中读取一个格式化数据 |
sprintf | 把一个格式化数据转换为字符串 |
文件的随机读写
1.fseek
fseek 是 C 语言标准库函数之一,用于移动文件内部的位置指针。
函数原型:
int fseek(FILE *stream, long int offset, int whence)
参数:
stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
offset – 这是相对 whence 的偏移量,以字节为单位。
whence – 这是表示开始添加偏移 offset 的位置。
作用:
移动文件指针到指定的位置,以便进行读写操作。
返回值:
成功时,返回
0
。失败时,返回非
0
值。
whence偏移offset的三种位置:
常量 | 描述 |
SEEK_SET | 文件的开头 |
SEEK_CUR | 文件指针的当前位置 |
SEEK_END | 文件的末尾 |
下面是fseek的使用示例:
我们假设文件中有“hello world”
int main()
{
FILE* pf = fopen("example.txt", "r");
if (pf == NULL)
{
perror("fopen fail:");
return 1;
}
// 从起始位置偏移4个字节
fseek(pf, 4, SEEK_SET);
int c1 = fgetc(pf);
printf("%c ", c1);
// 从结束位置偏移5个字节
fseek(pf, -5, SEEK_END);
int c2 = fgetc(pf);
printf("%c ", c2);
// 从当前位置偏移2个字节
fseek(pf, 2, SEEK_CUR);
int c3 = fgetc(pf);
printf("%c ", c3);
return 0;
}
1.从起始位置偏移4个字节输出 e 。
2.从结束位置偏移5个字节输出 c 。此时偏移指向 d 。
3.从当前位置偏移2个字节,输出 f 。
2.ftell
ftell 是 C 语言标准库函数之一,用于返回文件指针相对于起始位置的偏移量。
函数原型:
long int ftell ( FILE * stream );
参数:
stream :指向 FILE 对象的指针,该对象标识了要操作的文件流。
作用:
返回文件指针相对于起始位置的偏移量。
返回值:
如果成功,该函数返回位置标识符的当前值。
如果发生错误,则返回 -1L。
下面是ftell的使用示例:
假设文件里有“abcdefg”:
int main()
{
FILE* pFile;
long size;
pFile = fopen("example.txt", "rb");
if (pFile == NULL)
perror("Error opening file");
else
{
fseek(pFile, 0, SEEK_END); //non-portable
size = ftell(pFile);
fclose(pFile);
printf("文件长度为: %ld bytes.\n", size);
}
return 0;
}
输出结果为:
3.rewind
rewind 是 C 语言标准库函数之一,用于让文件指针的位置回到文件的起始位置。
函数原型:
void rewind(FILE *stream);
参数:
stream :指向 FILE 对象的指针,该对象标识了要操作的文件流。
作用:
用于让文件指针的位置回到文件的起始位置。
返回值:
该函数没有返回值。
下面是rewind的使用示例:
int main()
{
int n;
FILE* pFile;
char arr[27];
pFile = fopen("example.txt", "w+");
for (n = 'A'; n <= 'Z'; n++)
fputc(n, pFile); //放入26个字母
rewind(pFile); //回到起始位置,方便读取
fread(arr, 1, 26, pFile); //读取
fclose(pFile);
arr[26] = '\0'; //字符串的结束标识
printf(arr);
return 0;
}
输出结果为:
文件读取结束的判断
1.被错误使用的feof
在文件读取过程中,不能用feof函数的返回值来判断文件是否结束。
feof函数的作用是:当文件读取结束时,判断是否已经到达文件的末尾。
但需要注意的是,feof 只有在尝试读取已经到达文件末尾之后才会返回非零值(通常为1),表示已经到达文件末尾。
2.常见的结束标志
1.文本文件读取是否结束,判断返回值是否为EOF,或者NULL。
·fgetc判断是否为EOF。
·fgets判断返回值是否为NULL。
2.二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。
·fread判断返回值是否小于要读的个数
文件缓冲区
ANSIC 标准采用“缓冲文件系统” 处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每⼀个正在使用的文件开辟⼀块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才⼀起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的。
我们可以通过如下代码来证明文件缓冲区的存在:
#include <stdio.h>
#include <windows.h>
//VS2019 WIN11环境测试
int main()
{
FILE* pf = fopen("example.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语言在操作文件时,需要做刷新缓冲区或者在文件操作结束的时候关闭文件,如果不做可能会导致读写文件的问题。
结束
求点赞收藏加关注!!!
感谢各位大佬们的支持!!!