
大家好,很高兴又和大家见面啦!!!
在上一篇博客中,我们深入探讨了C语言文件操作中的定位函数,包括
fseek 进行精确定位ftell 获取当前文件位置rewind 快速返回到文件开头这些函数共同构成了文件随机读写的基石,让我们能够在文件中自由导航。
然而,仅仅掌握文件定位是不够的——在实际文件操作过程中,我们经常会遇到一些关键问题:
这些问题的答案都离不开对文件结束判定和缓冲区机制的深入理解。
今天,我们就将围绕这些实际问题展开探讨,帮助大家掌握文件操作中的状态判定技巧。
feof
该函数的用法如下:
stream :指向识别流的 FILE 对象指针0该函数的用法简单的理解就是检查当前流中是否设置了文件末尾指示器。
这里我们需要注意的是文件末尾指示器表示的并不是光标处于文件的末尾
怎么来理解文件末尾指示器与光标处于文件末尾这二者的区别呢?
下面我们看图:
graph LR
a--->b--->c--->d--->e[文件末尾<br>光标此时指向位置]上图中展示的是光标处于文件末尾,但是此时并没有设置 eof ,因此我们通过 feof 进行检测时的结果应该是 0;
当我们在该位置进行了一次读取操作后:
graph LR
a--->b--->c--->d--->e[文件末尾<br>光标此时指向位置]
arrow[进行一次读取操作]
A[a]--->B[b]--->C[c]--->D[d]--->E[eof<br>文件末尾<br>光标此时指向位置]可以看到,该位置上多了一个 eof 标志,这就表示此时设置了一个 eof 指示器,我们可以通过 feof() 检测出来;
下面我们就来通过代码来对其进行测试,进一步验证我们的说法:
void test1() {
FILE* pf = fopen("C:\\Users\\LIQIAN\\Desktop\\data.txt", "r");
if (pf == NULL) {
perror("fopen");
return;
}
// 通过 fgetc 移动
int ch = fgetc(pf);
// 文件中的最后一个字符是 'a',因此我们将字符a作为循环结束标志
while (ch != 'a') {
printf("ch = %c->%3d\n", ch, ch);
ch = fgetc(pf);
}
// 通过 feof 检测是否设置了 eof
int set_eof = feof(pf);
if (set_eof == 0) {
printf("\n当前光标处于文件末尾,即字符a的后面\n");
printf("\nch = %c->%3d\n", ch, ch);
// 再一次在该位置进行读取操作
ch = fgetc(pf);
// 再一次检测 eof
set_eof = feof(pf);
}
// eof 的整型值为 -1,我们通过 ch 的值来观察
if (set_eof) {
printf("ch = %3d\n", ch);
}
fclose(pf);
}这里的测试逻辑比较简单:
fgetc 函数的读取操作,将光标移动至文件的末尾feof 检测一下是否设置了文件末尾指示器 ch 的值是否为 -1下面我们就来看一下测试结果:

可以看到,当 fgetc 函数读取完 a 后,并不会直接设置 eof ,但是此时的光标确实位于 a 的右侧,也就是文件末尾;
当我们再一次对该位置进行读取操作后,此时光标仍处于该位置,但不同的是,这时流中设置了 eof ,因此我们通过 feof 可以检测出来;
这里需要注意: 光标所在的位置一定是在数据的左侧,我们在进行读取/写入数据时,光标会在其右侧进行数据的读取/写入操作 当完成对应操作后,光标会往右侧进行移动
这里我们通过图示来说明光标的位置:
graph LR
a[光标]--->b[数据]
ps[此时光标可以对该数据进行读取<br>也可以在该位置进行写入]--->b
b--->c[完成一次读取/写入操作后]--->d[数据]--->e[光标]再直观一点,大家可以打开一个文本编辑器,然后你可以看一下,每完成一次输入,光标是否位于输入的内容右侧!!!这里我就不给大家演示了,大家可以自己动手操作一下。
回到正题,从上面的测试中我们可以得到两个结论:
eofeof ,并且我们可以通过 feof 检测出来这个函数的使用比较简单,下面我们再来看一下另一个指示器函数 ferror;
ferror
该函数的用法如下:
stream :指向识别流的 FILE 对象指针0从函数的用法介绍中,我们不难看到,ferror 与 feof 的使用方法是一致的,他们之间的区别就是检测的对象不同:
feof 检测的是文件末尾指示器 eofferror 检测的是错误指示器 error在前面的介绍中我们有介绍过,对于 fgetc ,不管是发生了读取错误,还是读取到了文件末位,其返回值都是 EOF 因此我们使用该函数时,一定要对 EOF 的类型进行判定:
void test2() {
FILE* pf = fopen("C:\\Users\\LIQIAN\\Desktop\\data_.txt", "w");
if (pf == NULL) {
perror("fopen");
return;
}
// 通过 fgetc 读取文件中的内容
int ch = fgetc(pf);
if (ch == -1) {
int set_eof = feof(pf);
printf("\nset_eof = %d\n", set_eof);
int set_error = ferror(pf);
printf("\nset_error = %d\n", set_error);
}
else {
printf("ch = %c -> %d\n", ch, ch);
}
fclose(pf);
}这次的测试逻辑也很简单:
w 打开文件 data_.txtfgetc 进行一次读取操作 EOF 可以被 feof 检测EOF 可以被 ferror 检测下面我们就来看一下测试结果:

可以看到,对于仅写入模式打开的文件,我们是无法进行读取操作的,因此 fgetc 在进行读取操作时会发生错误,并设置 error ,我们可以通过 ferror 检测出来;
clearerr不管是 eof 还是 error ,当文件中存在这两个指示器时,我们都无法对文件进行下一步的操作,那如果我想继续对文件进行写入,应该如何处理呢?
这里就涉及到了一个内容—— 指示器清除操作;
在上一篇的内容中我们介绍了3个能够清除指示器的函数:
fseek 能够清除 eoffsetpos 能够清除 eofrewind 能够清除 eof 和 error这三者的清除原理都相同——通过改变位置指示器(光标)的位置来清除流中设置的eof 和 error
那如果我们不想移动光标,直接将指示器清除,可以做到吗?
答案是当然可以,这里就可以调用 clearerr 来清除这些指示器;

该函数的用法如下:
stream :指向识别流的 FILE 对象指针这里我们通过直接在前面的测试代码中加入 clearerr ,之后再进行指示器检测:
void test3() {
FILE* pf = fopen("C:\\Users\\LIQIAN\\Desktop\\data_.txt", "w");
if (pf == NULL) {
perror("fopen");
return;
}
// 通过 fgetc 读取文件中的内容
int ch = fgetc(pf);
if (ch == -1) {
int set_eof = feof(pf);
printf("\nset_eof = %d\n", set_eof);
int set_error = ferror(pf);
printf("\nset_error = %d\n", set_error);
// 通过 clearerr 清除指示器
clearerr(pf);
set_eof = feof(pf);
printf("\nset_eof = %d\n", set_eof);
set_error = ferror(pf);
printf("\nset_error = %d\n", set_error);
}
else {
printf("ch = %c -> %d\n", ch, ch);
}
fclose(pf);
}下面我们直接来看测试结果:

可以看到,clearerr 完成了错误指示器的清除。
在介绍这个内容前,我们先来看一段代码:
void test4() {
FILE* pf = fopen("C:\\Users\\LIQIAN\\Desktop\\data.txt", "r");
if (pf == NULL) {
perror("fopen");
return;
}
// 通过 fgetc 读取文件中的内容
int ch = fgetc(pf);
for (int i = 0; i < 3; i++) {
printf("ch = %c -> %3d\n", ch, ch);
ch = fgetc(pf);
}
// 检测此时流中是否存在指示器
printf("\n检测流中是否存在指示器:\n");
int set_eof = feof(pf);
printf("\nset_eof = %d\n", set_eof);
int set_error = ferror(pf);
printf("\nset_error = %d\n", set_error);
// 若不存在指示器
if (set_eof == 0 && set_error == 0) {
printf("\nch = %c -> %3d\n", ch, ch);
// 向文件中写入字符 !
ch = fputc('!', pf);
}
// 若返回值为 -1 ,检测是哪种类型的指示器
if (ch == -1) {
printf("\n检测指示器类型:\n");
// 检测指示器的类型
set_eof = feof(pf);
printf("\nset_eof = %d\n", set_eof);
set_error = ferror(pf);
printf("\nset_error = %d\n", set_error);
// 清除指示器
clearerr(pf);
printf("\n清除指示器后,再一次检测是否存在指示器:\n");
set_eof = feof(pf);
printf("\nset_eof = %d\n", set_eof);
set_error = ferror(pf);
printf("\nset_error = %d\n", set_error);
}
else {
printf("\nch = %c -> %d\n", ch, ch);
}
fclose(pf);
}这时有朋友会好奇,这段代码不就是测试 clearerr 函数的吗?有啥好看的?
别着急,下面我们来看一下运行结果:

可以看到,正常情况下,由于文件是 r 模式打开,因此我们是无法写入成功的,所以 fputc 的返回值应该是 EOF 并且流中还会设置一个 error 指示器。
但是在本次的测试中,fputc 的返回值表示写入成功,而我们打开文件后发现,文件中并没有被写入 ! 这是为什么呢?
这个就是我们现在要介绍的——文件缓冲区.
ANSIC 标准采用 缓冲文件系统 处理的数据文件,所谓缓冲文件系统是指系统自动地在程序中为每一个正在使用的文件开辟一块 文件缓冲区 。
简单的理解,文件缓冲区 就是文件数据的一个 中转站:
graph LR
a[程序数据区]--->b1[输出缓冲区]--->c[磁盘]--->b2[输入缓冲区]--->a上图中的 输出缓冲区 指的是我们通过函数向文件写入数据时系统开辟的一块 文件缓冲区;
输入缓冲区 指的是我们通过函数从文件中读取数据时系统开辟的一块 文件缓冲区;
也就是说,我们不管是向文件写入数据,还是从文件中读取数据,系统并不会直接实现这一操作,而是通过缓冲区这一中转站,临时存放写入或读取的数据,之后再一起将所有的数据转移到其对应的位置;
这就能解释,为什么前面的测试中,fputc 函数的返回值不是 EOF 而是我们写入的 ! 对应的 ASCII 码值—— 33。
这是因为函数在执行写入操作时,实际上是先将数据写入到 缓冲区 中,此时的行为肯定是写入成功的,因此 fputc 函数返回了写入成功时的值;
而当我们结束程序运行后,通过 fclose 关闭了文件,此时系统会将缓冲区的数据写入到文件中,但是由于之前我们是以 r 模式(只读)打开的文件,也就是说,此时的文件是不接收写入操作,因此数据才没有写入成功;
fflush
该函数的用法如下:
stream :指向特定缓冲区的流的 FILE 对象指针0EOF 并设置 error 指示器从函数的介绍中我们可以知道,该函数主要是作用于与流关联的缓冲区:
fflush 来清除输入缓冲区的行为是无效的;下面我们就来简单测试一下该函数:
void test5() {
FILE* pf = fopen("C:\\Users\\LIQIAN\\Desktop\\data_.txt", "w");
if (pf == NULL) {
perror("fopen");
return;
}
// 写入数据
printf("写入数据:\n");
for (int i = 0; i < 3; i++) {
int ch = 'a' + i;
// 通过 fputc 写入数据后,光标会向后移动
ch = fputc(ch, pf);
printf("ch = %c -> %3d\n", ch, ch);
}
// 更新缓冲区,将输出缓冲区中的内容写入文件中
int flush = fflush(pf);
printf("flush = %d\n", flush);
fclose(pf);
}为了更好的观察 fflush 的工作过程,我们这里采用调式的方式进行查看:

此时,代码执行到了 159 行,也就是还未通过 fflush 更新缓冲区,这时如果我们打开文件,我们就可以看到,此时的文件中不存在任何数据;

当我们继续往后执行时,此时执行到了 162 行,也就是我们还未执行 fclose 关闭文件的操作,这时我们再打开文件时,我们就能看到文件中已经写入了我们先前写入的数据;
从这一次的测试中我们就能清晰的感受到 fflush 的作用——将缓冲区中的数据写入文件中;
今天的内容到这里就全部结束了。通过本文的学习,我们深入探讨了C语言文件操作中两个关键而常被误解的主题:文件结束判定和缓冲区机制。掌握这些知识对于编写健壮、可靠的文件操作代码至关重要。
下面让我们简要回顾一下本文的核心内容:
feof 函数:用于检测文件末尾指示器是否被设置。关键点在于:光标位于文件末尾并不等同于设置了 eof 指示器。只有在尝试读取超出文件末尾的数据后,eof 指示器才会被设置。因此,while (!feof(fp)) 是一个常见的错误用法。
ferror 函数:用于检测文件操作过程中是否发生了错误。当文件读写函数(如fgetc )返回 EOF 时,应使用 ferror 来区分是遇到了文件末尾还是发生了错误。
clearerr 函数:用于清除流的错误指示器和文件末尾指示器。当需要在不移动文件指针的情况下重置流状态以便继续操作时,这个函数非常有用。
理解这些机制有助于避免常见的编程陷阱:
fgetc)的返回值,再使用 feof 或 ferror 来确定结束的具体原因。
fflush 或妥善关闭文件。
✨ 如果觉得本文对您有帮助,请点个赞支持一下吧! 您的肯定是我持续分享的动力。
🔔 想了解更多C语言和编程技巧?欢迎关注我! 后续会带来更多深度、实用的技术文章。
📢 觉得文章有价值?不妨转发给更多需要的朋友! 知识共享,共同进步。
💾 遇到文件操作难题?赶紧收藏本文吧! 方便日后查阅,随时复习这些关键概念。
💬 有任何疑问、见解或想分享的经验吗? 欢迎在评论区留言交流,我们一起探讨,共同成长!
感谢您的阅读,期待下次再见!🚀