当前位置: 首页 > news >正文

【C语言加油站】C语言文件操作完全指南:feof、ferror与缓冲区机制详解

结束判定

  • 导读
  • 一、`feof`
    • 1.1 函数介绍
    • 1.2 函数使用
  • 二、`ferror`
    • 2.1 函数介绍
    • 2.2 函数使用
  • 三、`clearerr`
    • 3.1 函数介绍
    • 3.2 函数的使用
  • 四、文件缓冲区
    • 4.1 缓冲区介绍
    • 4.2 `fflush`
    • 4.3 函数使用
  • 结语

文件操作

导读

大家好,很高兴又和大家见面啦!!!

在上一篇博客中,我们深入探讨了C语言文件操作中的定位函数,包括

  • 如何使用 fseek 进行精确定位
  • 通过 ftell 获取当前文件位置
  • 利用 rewind 快速返回到文件开头

这些函数共同构成了文件随机读写的基石,让我们能够在文件中自由导航。

然而,仅仅掌握文件定位是不够的——在实际文件操作过程中,我们经常会遇到一些关键问题:

  • 如何准确判断何时到达文件末尾?
  • 如何识别和处理可能发生的错误?
  • 为什么有时文件操作的结果与预期不符?

这些问题的答案都离不开对文件结束判定和缓冲区机制的深入理解。

今天,我们就将围绕这些实际问题展开探讨,帮助大家掌握文件操作中的状态判定技巧。

一、feof

1.1 函数介绍

feof

该函数的用法如下:

  • 向函数中传入一个参数:
    • stream :指向识别流的 FILE 对象指针
  • 函数会在该流中检测,是否设置了文件末尾指示器:
    • 已设置,返回一个非零值
    • 未设置,返回 0

1.2 函数使用

该函数的用法简单的理解就是检查当前流中是否设置了文件末尾指示器。

这里我们需要注意的是文件末尾指示器表示的并不是光标处于文件的末尾

怎么来理解文件末尾指示器与光标处于文件末尾这二者的区别呢?

下面我们看图:

a
b
c
d
文件末尾
光标此时指向位置

上图中展示的是光标处于文件末尾,但是此时并没有设置 eof ,因此我们通过 feof 进行检测时的结果应该是 0

当我们在该位置进行了一次读取操作后:

a
b
c
d
文件末尾
光标此时指向位置
进行一次读取操作
a
b
c
d
eof
文件末尾
光标此时指向位置

可以看到,该位置上多了一个 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 检测是否设置了 eofint set_eof = feof(pf);if (set_eof == 0) {printf("\n当前光标处于文件末尾,即字符a的后面\n");printf("\nch = %c->%3d\n", ch, ch);// 再一次在该位置进行读取操作ch = fgetc(pf);// 再一次检测 eofset_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 可以检测出来;

这里需要注意:
光标所在的位置一定是在数据的左侧,我们在进行读取/写入数据时,光标会在其右侧进行数据的读取/写入操作
当完成对应操作后,光标会往右侧进行移动

这里我们通过图示来说明光标的位置:

光标
数据
此时光标可以对该数据进行读取
也可以在该位置进行写入
完成一次读取/写入操作后
数据
光标

再直观一点,大家可以打开一个文本编辑器,然后你可以看一下,每完成一次输入,光标是否位于输入的内容右侧!!!这里我就不给大家演示了,大家可以自己动手操作一下。

回到正题,从上面的测试中我们可以得到两个结论:

  • 当光标位于文件末尾时,流中不一定设置了 eof
  • 当此时我们对该位置进行一次读取操作时,流中会设置 eof ,并且我们可以通过 feof 检测出来

这个函数的使用比较简单,下面我们再来看一下另一个指示器函数 ferror

二、ferror

2.1 函数介绍

ferror

该函数的用法如下:

  • 向函数中传入一个参数:
    • stream :指向识别流的 FILE 对象指针
  • 函数会在该流中检测,是否设置了错误指示器:
    • 已设置,返回一个非零值
    • 未设置,返回 0

2.2 函数使用

从函数的用法介绍中,我们不难看到,ferrorfeof 的使用方法是一致的,他们之间的区别就是检测的对象不同:

  • feof 检测的是文件末尾指示器 eof
  • ferror 检测的是错误指示器 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_.txt
  • 此时文件中的原内容会被清除,并等待写入,这时的光标位于文件开头,也是文件的末尾
  • 之后我们通过 fgetc 进行一次读取操作
    • 若读取成功,则函数返回的 EOF 可以被 feof 检测
    • 若读取失败,则函数返回的 EOF 可以被 ferror 检测

下面我们就来看一下测试结果:

函数使用

可以看到,对于仅写入模式打开的文件,我们是无法进行读取操作的,因此 fgetc 在进行读取操作时会发生错误,并设置 error ,我们可以通过 ferror 检测出来;

三、clearerr

不管是 eof 还是 error ,当文件中存在这两个指示器时,我们都无法对文件进行下一步的操作,那如果我想继续对文件进行写入,应该如何处理呢?

这里就涉及到了一个内容—— 指示器清除操作;

在上一篇的内容中我们介绍了3个能够清除指示器的函数:

  • fseek 能够清除 eof
  • fsetpos 能够清除 eof
  • rewind 能够清除 eoferror

这三者的清除原理都相同——通过改变位置指示器(光标)的位置来清除流中设置的eoferror

那如果我们不想移动光标,直接将指示器清除,可以做到吗?

答案是当然可以,这里就可以调用 clearerr 来清除这些指示器;

3.1 函数介绍

clearerr
该函数的用法如下:

  • 向函数中传入一个参数:
    • stream :指向识别流的 FILE 对象指针
  • 函数会清除流中已经设置的错误指示器和文件末尾指示器

3.2 函数的使用

这里我们通过直接在前面的测试代码中加入 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 的返回值表示写入成功,而我们打开文件后发现,文件中并没有被写入 ! 这是为什么呢?

这个就是我们现在要介绍的——文件缓冲区.

4.1 缓冲区介绍

ANSIC 标准采用 缓冲文件系统 处理的数据文件,所谓缓冲文件系统是指系统自动地在程序中为每一个正在使用的文件开辟一块 文件缓冲区

  • 从内存向磁盘中输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上
  • 从磁盘向计算机读取数据,则从磁盘文件中将读取的数据输入到内存缓冲区,然后再从缓冲区逐个地将数据送到程序数据区

简单的理解,文件缓冲区 就是文件数据的一个 中转站

程序数据区
输出缓冲区
磁盘
输入缓冲区

上图中的 输出缓冲区 指的是我们通过函数向文件写入数据时系统开辟的一块 文件缓冲区

输入缓冲区 指的是我们通过函数从文件中读取数据时系统开辟的一块 文件缓冲区

也就是说,我们不管是向文件写入数据,还是从文件中读取数据,系统并不会直接实现这一操作,而是通过缓冲区这一中转站,临时存放写入或读取的数据,之后再一起将所有的数据转移到其对应的位置;

这就能解释,为什么前面的测试中,fputc 函数的返回值不是 EOF 而是我们写入的 ! 对应的 ASCII 码值—— 33

这是因为函数在执行写入操作时,实际上是先将数据写入到 缓冲区 中,此时的行为肯定是写入成功的,因此 fputc 函数返回了写入成功时的值;

而当我们结束程序运行后,通过 fclose 关闭了文件,此时系统会将缓冲区的数据写入到文件中,但是由于之前我们是以 r 模式(只读)打开的文件,也就是说,此时的文件是不接收写入操作,因此数据才没有写入成功;

4.2 fflush

fflush
该函数的用法如下:

  • 向函数中传入一个参数:
    • stream :指向特定缓冲区的流的 FILE 对象指针
  • 函数会刷新与其关联的缓冲区:
    • 刷新成功,返回 0
    • 刷新失败,返回 EOF 并设置 error 指示器

4.3 函数使用

从函数的介绍中我们可以知道,该函数主要是作用于与流关联的缓冲区:

  • 以写入方式或读写方式打开的文件,在使用该函数后,输出缓冲区中的数据会全部写入文件中;
  • 以读取方式打开的文件中,使用该函数的取决于具体的实现:
    • 在一些库的实现中是将输入缓冲区的内容全部清除;
    • 而在一些不支持该操作的库中,通过 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 更新缓冲区,这时如果我们打开文件,我们就可以看到,此时的文件中不存在任何数据;

函数使用2
当我们继续往后执行时,此时执行到了 162 行,也就是我们还未执行 fclose 关闭文件的操作,这时我们再打开文件时,我们就能看到文件中已经写入了我们先前写入的数据;

从这一次的测试中我们就能清晰的感受到 fflush 的作用——将缓冲区中的数据写入文件中;

结语

今天的内容到这里就全部结束了。通过本文的学习,我们深入探讨了C语言文件操作中两个关键而常被误解的主题:文件结束判定和缓冲区机制。掌握这些知识对于编写健壮、可靠的文件操作代码至关重要。

下面让我们简要回顾一下本文的核心内容:

  1. feof 函数:用于检测文件末尾指示器是否被设置。关键点在于:光标位于文件末尾并不等同于设置了 eof 指示器。只有在尝试读取超出文件末尾的数据后,eof 指示器才会被设置。因此,while (!feof(fp)) 是一个常见的错误用法。

  2. ferror 函数:用于检测文件操作过程中是否发生了错误。当文件读写函数(如fgetc )返回 EOF 时,应使用 ferror 来区分是遇到了文件末尾还是发生了错误。

  3. clearerr 函数:用于清除流的错误指示器和文件末尾指示器。当需要在不移动文件指针的情况下重置流状态以便继续操作时,这个函数非常有用。

  4. 文件缓冲区:C语言使用缓冲文件系统,数据读写会先经过内存中的缓冲区。这解释了为何有时写入操作看似成功(数据已进入缓冲区),但文件内容并未立即更新。fflush函数可以强制将输出缓冲区的数据立即写入磁盘。

理解这些机制有助于避免常见的编程陷阱:

  • 正确判断文件读取结束的条件,应首先检查读写函数(如 fgetc)的返回值,再使用 feofferror 来确定结束的具体原因。

  • 意识到缓冲区的存在,在需要确保数据立即写入磁盘时(如日志记录),及时使用 fflush 或妥善关闭文件。

✨ 如果觉得本文对您有帮助,请点个赞支持一下吧! 您的肯定是我持续分享的动力。

🔔 想了解更多C语言和编程技巧?欢迎关注我! 后续会带来更多深度、实用的技术文章。

📢 觉得文章有价值?不妨转发给更多需要的朋友! 知识共享,共同进步。

💾 遇到文件操作难题?赶紧收藏本文吧! 方便日后查阅,随时复习这些关键概念。

💬 有任何疑问、见解或想分享的经验吗? 欢迎在评论区留言交流,我们一起探讨,共同成长!

感谢您的阅读,期待下次再见!🚀

http://www.dtcms.com/a/495464.html

相关文章:

  • 做seo怎么设计网站响应式网站软件
  • 怎么样建网站卖东西可以入侵的网站
  • 17、Centos9 安装 1Panel
  • Linux学习笔记--GPIO控制器驱动
  • 重庆制作手机网站如何看一个站点是不是有wordpress
  • 网站如何在推广设计公司logo软件
  • 价值1w的数据分析课知识点汇总-excel使用(第一篇)
  • android取消每次u盘插入创建无用(媒体)文件夹
  • 个人如何办网站1m的带宽做网站可以吗
  • 多机部署,负载均衡
  • python通过win32com库调用UDE工具来做开发调试实现自动化源码,以及UDE的知识点介绍
  • 关于Unity中ComputeShader 线程id的理解
  • 幽冥大陆(十六)纸币器BV20识别纸币——东方仙盟筑基期
  • 设计网站的方法做彩票网站需要什么条件
  • Windows 平台 HOOK DWM 桌面管理程序,实现输出变形的桌面图像到显示器
  • Java 大视界 -- Java 大数据在智能电网电力市场交易数据分析与策略制定中的关键作用
  • 安徽和县住房城乡建设局网站wordpress移动端顶部导航栏
  • oracle:判断字段不以开头
  • 学习笔记3-深度学习之logistic回归向量化
  • 哈尔滨网站建设网站优秀个人网站设计欣赏
  • 高辐射环境下AS32S601ZIT2型MCU的抗辐照性能与应用潜力分析
  • 基于STM32F407与FT245R芯片实现USB转并口通信时序控制
  • Retrofit 与 OkHttp 全面解析与实战使用(含封装示例)
  • qiankun知识点
  • 面向接口编程与自上而下的系统设计艺术
  • 数据结构基石:单链表的全面实现、操作详解与顺序表对比
  • 网站 无限下拉做一个小程序需要多少钱
  • 【Kubernetes】常见面试题汇总(二十六)
  • 微网站设计制作wordpress在线文档
  • “自来水”用英语怎么说?