本篇目标:
1.文件的作用与什么是文件
-
学会文件的打开,关闭 ,读写的操作
-
⽂件读取结束的判定
-
知道文件缓冲区的存在及作用
一.认识文件
1.文件的作用与概念
作用: 如果没有文件,我们写的程序数据是存储在电脑的内存中,如果程序退出,内存回收,数
据就丢失 了,等再次运行程序,是看不到上次程序的数据的,所以要想将数据进行持久化的保
存,我们就可以使用⽂件进行操作。
什么是文件?
磁盘(硬盘)上的文件是文件,然而在程序设计中,我们⼀般谈的⽂件有两种:程序文件 、数据文
**件,**以下是关于文件的具体分类。
2.文件分类
程序文件包括源文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序
(windows 环境后缀为.exe)。
数据文件:程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或 者输出内容的文
件,本篇是以数据文件为主的。
根据数据的组织形式,数据⽂件被分为**⼆进制文件** 和文本文件。
二进制文件:数据在内存中以二进制的形式存储,如果不加转换的输出到外存的⽂件中。
文本文件:在外存上以ASCII码的形式存储,则需要在存储前转换,以ASCII字符的形式存储的⽂
件。
例如有一整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符⼀个字
节),而⼆进制形式输出,则在磁盘上只占4个字节,可以通过如图进行理解:

二.常见文件操作
1.文件的打开与关闭
1.1.流的概念
概念:我们程序的数据需要输出到各种外部设备,例如我们使用printf向显示屏上输出内容,也需
要从外部设备获取数据,但是不同的外部设备的输⼊输出操作各不相同,为了方便程序员对各种
设备进行方便的操作,我们抽象出了流的概念,我们可以把流想象成流淌着字符的河。
⼀般情况下,我们要想向流⾥里写数据,或者从流中读取数据,都是要先打开流,然后操作。
1.2.标准流
这是可能就会有人有疑问:为什么我们从键盘输入数据,向屏幕上输出数据,并没有打开流呢?
那是因为C语⾔程序在启动的时候,默认打开了3个流:
stdin- 标准输⼊流,在⼤多数的环境中从键盘输⼊,scanf函数就是从标准输⼊流中读取数据。
stdout- 标准输出流,⼤多数的环境中输出⾄显示器界面,printf函数就是将信息输出到标准输出 流中。
stderr- 标准错误流,大多数环境中输出到显示器界面。
并且stdin 、 stdout 、 stderr 三个流的类型是: FILE * ,通常称为文件指针。
FILE* 的⽂件指针来维护流的各种操作的。

1.3.文件指针
概念:每个被使⽤的文件都在内存中开辟了⼀个相应的文件信息区 ,⽤来存放文件的相关信息(如
⽂件的名字,文件状态及文件当前的位置等),这些信息是保存在⼀个结构体变量中的。该结构
体类型是由系统声明的,取名FILE,所以我们通过这个文件指针+流的方式就可以直接访问到这个
文件了。
例如下面我们可以简单的看一下里面的内容:
cpp
#ifndef _FILE_DEFINED
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
#define _FILE_DEFINED
#endif
下⾯我们可以创建⼀个FILE*的指针变量:
cpp
FILE* pf; //⽂件指针变量
定义pf是⼀个指向FILE类型数据的指针变量。可以使pf指向某个⽂件的⽂件信息区(是⼀个结构体
变 量)。通过该⽂件信息区中的信息就能够访问该⽂件。也就是说,通过⽂件指针变量能够间接
找到与它关联的⽂件,看图理解吧:

1.4.文件开关
1.4.1.fopen
作用:打开一个文件,将打开的文件和⼀个流进行关联, 并返回⼀个指向该文件的FILE*指针变量
语法:
cpp
FILE * fopen ( const char * filename, const char * mode );
filename :表示被打开的文件的名字。
mode :表示对打开的文件的操作方式,具体见下面的表格。
|-------------|----------------------|----------|
| 操作方式 | 含义 | 若文件不存在 |
| "r"(只读) | 为了输⼊数据,打开⼀个已经存在的⽂本⽂件 | 出错 |
| "w"(只写) | 为了输出数据,打开⼀个⽂本⽂件 | 建⽴⼀个新的文件 |
| "a"(追加) | 向⽂本⽂件尾添加数据 | 建⽴⼀个新的文件 |
返回值:
• 文件成功打开,该函数将返回⼀个指向FILE对象的指针,该指针可用于后续操作中标识对应的流。
• 打开失败,则返回NULL指针,所以⼀定要对 fopen 的返回值做判断,来验证文件是否打开成 功。
1.4.2.fclose
作用:关闭参数 stream 关联的文件,并取消其关联关系,与该流关联的所有内部缓冲区均会解除关 联并刷新:任何未写⼊的输出缓冲区内容将被写⼊,任何未读取的输⼊缓冲区内容将被丢弃。
注:缓冲区后面后讲
语法:
int fclose ( FILE * stream );
参数:
stream :指向要关闭的流的 FILE 对象的指针。
返回值:
成功关闭 stream 指向的流会返回0,否则会返回EOF。
1.5.代码演示
cpp
#include <stdio.h>
int main()
{
FILE* fp = fopen("test.txt", "w"); //以"r"的形式打开⽂件,如果⽂件不存在,则打开失败
if (fp == NULL)
{
perror("fopen\n");
return 1;
}
printf("打开文件成功,可以对文件进行操作\n");
//对文件的读写操作
//...
fclose(fp); //不再使用文件时,需要关闭文件
fp = NULL; //将指针置为NULL,避免成为野指针。
return 0;
}
2.文件的顺序读写
2.1.fprintf
功能: 将格式化后的数据写入指定文件流,它与 printf 类似,但可以输出到任意输出流(如磁盘文件、标准输出、标准错误等),⽽不仅限于控制台。
语法:
cpp
int fprintf ( FILE * stream, const char * format, ... );
参数:
• stream :FILE 对象的指针,表示要写入的文件流。
• format :格式化字符串,通常是写入的文本和格式说明符(如 %d 、 %s 等)。
• ... :可变参数列表,提供与格式字符串中说明符对应的数据。
返回值:
• 成功时,返回写入的字符总数。
• 失败时,先设置对应流的错误指示器,再返回负值。
代码操作:
cpp
#include <stdio.h>
int main()
{
FILE* fp = fopen("test.txt", "w"); //以"r"的形式打开⽂件,如果⽂件不存在,则打开失败
if (fp == NULL)
{
perror("fopen\n");
return 1;
}
printf("打开文件成功,可以对文件进行操作\n");
// 对文件进行写入操作
fprintf(fp, "Hello, File!\n");
fprintf(fp, "This is a test file.\n");
// 添加新的fprintf写入操作
fprintf(fp, "Adding another line with fprintf.\n");
fclose(fp); //不再使⽤文件时,需要关闭⽂件
fp = NULL; //将指针置为NULL,避免成为野指针。
return 0;
}
注意:此时我们打开test.txt文件会发现我们利用fprintf输入的内容,但是如果我们输入其他的内
容,原来的内容又会被覆盖,这是因为以'w'的方式打开文件的特点就是这样的,或者我们可以
用**'a'的方式在文件末尾添加内容.**
2.2.fscanf
**功能:从指定文件流中读取格式化数据的函数,**它类似于 scanf ,但可以指定输⼊源 (如⽂件、
标准输⼊等),⽽非仅限于控制台输⼊,适用于从文件解析结构化数据(如整数、浮点 数、字符
串等)。
语法:
cpp
int fscanf ( FILE * stream, const char * format, ... );
参数:
• stream :指 向FILE 对象的指针,表示要读取的文件流(如 %d 、 %f 、 %s 等)。
•format :格式化字符串,定义如何解析入数据(如 • stdin 、文件指针等)。
•... :可变参数列表,提供存储数据的变量地址(需与格式字符串中的说明符匹配)。
返回值:
• 成功时,函数返回成功填充到参数列表中的项数。
• 失败时,发生读取错误,会在对应流上设置错误指时符,则返回 EOF ;到达文件末尾,会在对
应流上设置文件结束指示符,则返回 EOF 。
代码操作:
cpp
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
FILE* fp = fopen("test.txt", "w+");
if (fp == NULL) return 1;
// 恢复你的 3 行写入
fprintf(fp, "Hello, File!\n");
fprintf(fp, "This is a test file.\n");
fprintf(fp, "Adding another line with fprintf.\n");
// 指针归位
rewind(fp);
char buffer[100];
while (fscanf(fp, " %99[^\n]", buffer) == 1)
{
printf("读取到的内容: %s\n", buffer);
}
fclose(fp);
fp = NULL;
return 0;
}
2.3.fread
功能:函数用于从 stream 指向的文件流中读取数据块,并将其存储到 ptr 指向的内存缓冲区中。
cpp
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
参数:
• ptr :指向内存区域的指针,用于存储从文件中读取的数据。
• size :要读取的每个数据块的⼤小(以字节为单位)。
• count :要读取的数据块的数量。
• stream :指向FILE类型结构体的指针,指定了要从中读取数据的⽂件流。
返回值:返回实际读取的数据块数量。
使⽤注意事项:
• 在使⽤ fread() 之前,需要确保文件已经以二进制可读方式打开。
• ptr 指向的内存区域必须足够大,以便存储指定数量和大小的数据块。
• 如果 fread() 成功读取了指定数量的数据块,则返回值等于 count,如果读取数量少于,则可能
已经到达文件结尾或者发⽣了错误。
代码操作:
cpp
#include <stdio.h>
int main()
{
int data[5] = {0}; // 假设文件中包含 5 个整数数据
// 打开文件
FILE *fp = fopen("data.bin", "rb");
// 检查文件是否成功打开
if (fp == NULL) {
perror("fopen");
return -1;
}
// 从文件中读取整数数据块,fread() 函数读取这些数据:
size_t num_read = fread(data, sizeof(int), 5, fp);
// 检查读取是否成功
if (num_read != 5)
{
if (feof(fp))
printf("Reached end of file\n");
else if (ferror(fp))
printf("Error reading file\n");
}
else
{
// 输出读取的数据
for (int i = 0; i < 5; i++) {
printf("Data[%d]: %d\n", i, data[i]);
}
}
// 关闭文件
fclose(fp);
return 0;
}
2.4.fgets
功能:从 stream 指定输⼊流中读取字符串,读取到换行符、文件末尾(EOF)或达到指定字符数 (包含结尾的空字符 \0 ),然后将读取到的字符串存储到str指向的空间中。
语法:
cpp
char * fgets ( char * str, int num, FILE * stream );
参数:
• str :是指向字符数组的指针,str指向的空间用于存储读取到的字符串。
• num :最大读取字符数(包含结尾的 \0 ,实际最多读取num-1 个字符)
• stream :输⼊流的文件指针(如⽂件流或 stdin)。
返回值:
• 成功时返回 str 指针。
• 失败时,例如在尝试读取字符时遇到⽂件末尾,则设置⽂件结束指⽰器,并返回NULL,需通过
feof()检测;发⽣读取错误,则设置流错误指⽰器,并返回NULL,通过 ferror() 检测。
代码演示:
cpp
#include <stdio.h>
int main()
{
FILE* fp = fopen("test.txt", "r");
if (fp == NULL)
{
perror("fopen\n");
return 1;
}
char arr1[] = "*************";
fgets(arr1, sizeof(arr1), fp);
char arr2[] = "*************";
fgets(arr2, sizeof(arr1), fp);
fclose(fp); //不再使⽤⽂件时,需要关闭⽂件
fp = NULL; //将指针置为NULL,避免成为野指针。
return 0;
}
三.文件缓冲区
3.1.概念
ANSI C标准采⽤"缓冲⽂件系统"处理数据⽂件的,所谓缓冲⽂件系统是指系统⾃动地在内存中为程 序中每⼀个正在使⽤的⽂件开辟⼀块"⽂件缓冲区"。从内存向磁盘输出数据会先送到内存中的缓冲 区,装满缓冲区后才⼀起送到磁盘上。如果从磁盘向计算机读⼊数据,则从磁盘⽂件中读取数据输⼊ 到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲 区的⼤⼩根据C编译系统决定的。
其实就是一块内存区域,用来临时存放数据的。
为什么需要文件缓冲区?
根本原因是为了解决 CPU/内存 与 物理磁盘 之间巨大的速度差。
-
内存的速度是纳秒级别的(极快)。
-
磁盘 I/O 的速度是毫秒级别的(极慢,比内存慢几十万倍)。
如果你的 C 代码里写了一个循环,每次循环都用 fprintf 向文件写入 1 个字节。如果没有缓冲
区,CPU 就得频繁地命令操作系统的磁盘驱动去"写 1 个字节",这就像是你开着一辆 10 吨的大卡
车,每次只运送一颗螺丝钉,极其浪费系统资源和时间。
有了缓冲区之后: 你的代码调用 fprintf 时,数据实际上并没有立刻写进磁盘,而是被
悄悄塞进了这块"缓冲区"内存里。等缓冲区塞满了 ,或者你下达了特定指令,C 标准库才会把这整
整一车的数据,一次性打包"批发"写入物理磁盘。
可以如图加深理解:

满足刷新的条件:
<1>.全缓冲
-
场景 :对磁盘文件 的读写(如
fopen("test.txt", "w"))。 -
规则 :只有当缓冲区被完全填满(通常是 4KB 或 8KB,即
_bufsiz的大小),或者程序正常 -
结束、调用
fclose时,数据才会真正写入磁盘。
<2>. 行缓冲
-
场景 :标准输入输出设备(如终端屏幕
stdout、键盘stdin)。 -
规则 :遇到一个换行符
\n,就会立刻触发刷新,把数据推出去。
<3>. 无缓冲
-
场景 :标准错误输出流(
stderr)。 -
规则:根本没有集装箱。写一个字节,就立刻送达一个字节。因为错误信息通常非常紧急,哪
-
怕程序下一秒就崩溃,错误日志也必须立刻显示出来。
3.2.fflush
功能:强制刷新参数stream 指定流的缓冲区,确保数据写入底层设备。
语法:
cpp
int fflush ( FILE * stream );
参数:
stream :指向文件流的指针(如 stdout 、文件指针等)。
返回值:成功返回 0 ,失败返回 EOF。
代码操作:
cpp
#include <stdio.h>
#include <windows.h>
// VS2022 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;
}