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

文件读取结束的判定方法:正确使用feof函数避免文件读取错误

目录

一、feof 函数的正确使用

1、EOF (End of File) - 文件结束标志的基本概念

2、EOF (End of File) - 文件结束标志的重要特性

3、重要原则

二、正确的文件结束判断方法(重点!!!)

1、文本文件读取结束的判断

2、二进制文件读取结束的判断

三、不能直接用 feof 函数来判断文件读取是否结束

1、核心观点

2、为什么不能直接使用 feof(fptr) 作为循环条件?

执行过程分解

过程 1:第一次循环开始前

过程 2:第一次循环执行

过程 3:第二次循环开始

过程 4:第二次循环执行

过程 5:第三次循环开始(错误的发生)

过程 6:第三次循环执行

过程 7:第四次循环开始

关键概念总结

最终输出结果

3、正确的做法(如段落中所说)

1. 对于文本文件

2. 对于二进制文件

总结

四、feof() 和 ferror() 函数

1、函数概述

feof() 函数

ferror() 函数

2、正确使用流程

3、feof() 函数详解

特性:

错误用法:

正确用法:

4、ferror() 函数详解

检测的错误类型:

使用示例:

5、clearerr() 函数

示例:

五、总结说明


一、feof 函数的正确使用

1、EOF (End of File) - 文件结束标志的基本概念

EOF(End of File)是C语言中表示文件结束的特殊标志,它是一个宏定义,通常值为 -1

2、EOF (End of File) - 文件结束标志的重要特性

  • 不是文件中的实际字符:EOF 不是存储在文件末尾的字符,而是标准库函数返回的特殊值

  • 数据类型的重要性:处理字符输入时,必须使用 int 类型而非 char 类型来接收返回值

  • 跨平台一致性:EOF 的值在所有平台上都是 -1,保证了代码的可移植性

3、重要原则

        在文件读取过程中,不应直接使用 feof 函数的返回值来判断文件是否结束,而是要根据实际情况来使用对应的解决方案,如下面第二、三大点详细所述。

feof 函数的真正作用是:当文件读取操作结束后,判断读取结束的原因是否为"遇到文件末尾"。


二、正确的文件结束判断方法(重点!!!)

1、文本文件读取结束的判断

  • 使用 fgetc 时,判断返回值是否为 EOF

  • 使用 fgets 时,判断返回值是否为 NULL

2、二进制文件读取结束的判断

  • 使用 fread 时,判断返回值是否小于实际请求读取的个数


三、不能直接用 feof 函数来判断文件读取是否结束

1、核心观点

    feof 函数的作用不是预测“是否即将到达文件末尾”,而是事后检查“上一次读取操作失败,是不是因为已经碰到了文件结尾(End-of-File)”。

        如果错误地直接用 feof 作为循环条件,会导致多读一次,也就是最后一次读取失败的数据仍然会被错误地处理。

2、为什么不能直接使用 feof(fptr) 作为循环条件?

想象一下这个错误的代码:

FILE* fptr = fopen("file.txt", "r");
char buffer[100];// !!! 错误的写法 !!!
while (!feof(fptr)) {fgets(buffer, 100, fptr);printf("%s", buffer);
}
fclose(fptr);

假设 file.txt 的内容很简单,只有两行:

Hello
World

        文件末尾有一个换行符(这是文本文件的常见情况),所以实际内容可以看作是:"Hello\nWorld\n"

执行过程分解

过程 1:第一次循环开始前
  • 文件指针:指向文件开头('H' 字符之前)

  • feof(fptr):返回 0 (false),因为还没开始读,肯定没到文件尾

  • 循环条件 !feof(fptr)!0 = true → 进入循环

过程 2:第一次循环执行
fgets(buffer, 100, fptr);  // 读取 "Hello\n" 到 buffer
printf("%s", buffer);       // 打印 "Hello\n"
  • 读取结果:成功读取第一行 "Hello\n"(包含换行符)

  • 文件指针:移动到 "Hello\n" 之后,"W" 字符之前

  • feof(fptr):仍然返回 0,因为上次读取成功,没遇到文件尾

过程 3:第二次循环开始
  • 循环条件 !feof(fptr)!0 = true → 再次进入循环

过程 4:第二次循环执行
fgets(buffer, 100, fptr);  // 读取 "World\n" 到 buffer
printf("%s", buffer);       // 打印 "World\n"
  • 读取结果:成功读取第二行(最后一行)"World\n"

  • 文件指针:移动到 "World\n" 之后,即文件末尾(EOF)

  • feof(fptr)关键点!仍然返回 0

    • 原因:feof() 检测的是"上一次读取操作是否因为遇到文件尾而失败"

    • 但这次的 fgets() 是成功的!它成功读到了数据,只是读完这行后指针才到达EOF

    • EOF标志还没有被设置

过程 5:第三次循环开始(错误的发生)
  • 循环条件 !feof(fptr)!0 = true → 错误地再次进入循环

  • 此时文件指针已经在EOF位置,但feof()还不知道

过程 6:第三次循环执行
fgets(buffer, 100, fptr);  // 尝试读取,但已经在文件尾
  • 读取结果fgets() 读取失败,返回 NULL

  • buffer 的内容没有被清空,仍然保持着上一次的值:"World\n"

  • 文件状态:由于读取失败且原因是遇到文件尾,此时才设置EOF标志

  • printf("%s", buffer);  // 错误地再次打印 "World\n"
过程 7:第四次循环开始
  • feof(fptr):现在返回 1 (true),因为EOF标志已被设置

  • 循环条件 !feof(fptr)!1 = false → 循环结束

关键概念总结

  1. feof() 是滞后指示器:它报告的是"上一次读取操作是否因为EOF而失败",而不是"文件指针是否在EOF位置"

  2. 读取成功 vs 读取失败

    • 如果 fgets() 成功读取到数据(哪怕这是最后一行),它返回指向buffer的指针

    • 只有当下一次尝试读取时,才会因为已经在文件尾而失败

  3. 缓冲区内容持久性:当 fgets() 失败时,它不会清空buffer,buffer会保持上一次成功读取的内容

最终输出结果

Hello
World
World

第二行 "World" 被错误地打印了两次。

        这就是为什么不能直接用 while (!feof(fp)) 的原因——它总是会导致多读一次。正确的做法是检查读取函数的返回值:while (fgets(buffer, size, fp) != NULL)

3、正确的做法(如段落中所说)

        应该根据读取函数本身的返回值来判断是否成功读取,从而决定是否结束循环。feof 只在读取失败后,用来判断失败的原因是不是因为到了文件尾(而不是其他错误)。

1. 对于文本文件

  • fgetc:判断返回值是否为 EOF

    int c; // 必须是int,不能是char,为了正确接收EOF
    while ((c = fgetc(fptr)) != EOF) {putchar(c);
    }
    // 循环结束后,可以用feof或ferror判断是正常结束还是出错
    if (feof(fptr)) {printf("Reached end of file.\n");
    } else if (ferror(fptr)) {printf("An error occurred.\n");
    }
  • fgets:判断返回值是否为 NULL

    char buffer[100];
    while (fgets(buffer, 100, fptr) != NULL) {printf("%s", buffer);
    }
    if (feof(fptr)) {// 正常读到文件尾结束
    } else {// 发生错误
    }

2. 对于二进制文件

  • fread:判断返回值是否小于你期望读取的个数。如果读不满,说明要么到了文件尾,要么发生了错误。

    size_t elements_read;
    int data[10];
    while ((elements_read = fread(data, sizeof(int), 10, fptr)) > 0) {// 成功读取了 elements_read 个数据,处理它们process_data(data, elements_read);
    }
    // 循环结束后,用feof判断是正常结束还是异常
    if (feof(fptr)) {printf("Reached end of file. Last read might be incomplete.\n");
    } else {printf("An error occurred during reading.\n");
    }

总结

函数作用使用时机
fgetcfgetsfread 的返回值判断读取是否成功/结束作为循环的条件
feof判断结束原因是否为到达文件尾在读取失败后进行诊断
ferror判断结束原因是否为发生错误在读取失败后进行诊断

        所以,不能直接用 feof 的判断结果来决定是否继续读取文件,而应该依赖读取函数自身的返回值。


四、feof() 和 ferror() 函数

1、函数概述

feof() 函数

int feof(FILE *stream);
  • 作用:检查是否到达文件末尾

  • 返回值:如果设置了文件结束指示器,返回非零值;否则返回0

ferror() 函数

int ferror(FILE *stream);
  • 作用:检查文件操作是否发生错误

  • 返回值:如果设置了错误指示器,返回非零值;否则返回0

2、正确使用流程

#include <stdio.h>int main() {FILE *fp = fopen("example.txt", "r");if (fp == NULL) {perror("文件打开失败");return 1;}int c;// 先进行读取操作while ((c = fgetc(fp)) != EOF) {putchar(c);}// 读取结束后判断原因if (ferror(fp)) {printf("读取过程中发生I/O错误\n");} else if (feof(fp)) {printf("成功到达文件末尾\n");}fclose(fp);return 0;
}

3、feof() 函数详解

特性:

  • 不是预测函数:只有在尝试读取超过文件末尾后才会返回真

  • 状态标志:设置后保持设置状态,直到调用 clearerr() 或 rewind()

  • 使用时机:在读取操作失败后调用

错误用法:

// 错误:在读取前调用feof()
while (!feof(fp)) {c = fgetc(fp); // 可能多读一次putchar(c);
}

正确用法:

// 正确:先读取,再判断
while ((c = fgetc(fp)) != EOF) {putchar(c);
}
if (feof(fp)) {printf("正常结束\n");
}

4、ferror() 函数详解

检测的错误类型:

  • 磁盘读写错误

  • 设备错误

  • 缓冲区错误

  • 格式错误等

使用示例:

FILE *fp = fopen("file.bin", "rb");
if (fp == NULL) {perror("打开文件失败");return 1;
}char buffer[100];
size_t bytes_read = fread(buffer, 1, sizeof(buffer), fp);if (bytes_read < sizeof(buffer)) {if (feof(fp)) {printf("到达文件末尾\n");} else if (ferror(fp)) {perror("读取错误");}
}

5、clearerr() 函数

void clearerr(FILE *stream);
  • 作用:清除文件错误标志和EOF标志

  • 使用场景:在错误处理后继续文件操作

示例:

if (ferror(fp)) {perror("发生错误");clearerr(fp); // 清除错误标志// 可以继续其他操作
}

五、总结说明

  • feof 函数应在读取操作后进行调用,用于确定读取结束是否由于到达文件末尾

  • ferror 函数用于检查在文件操作过程中是否发生了错误

  • 正确的做法是:先检查读取函数的返回值,再使用 feof/ferror 确定结束原因

  • 这种判断方式适用于所有标准 I/O 读取函数,包括 fgetc、fgets、fread 等

http://www.xdnf.cn/news/18890.html

相关文章:

  • 代码随想录算法训练营30天 | ​​01背包理论基础、416. 分割等和子集
  • Pandas 高效数据处理:apply、向量化与分组
  • Android用Coil 3检查媒体资源是否有效,Kotlin
  • LeetCode 面试经典 150_双指针_验证回文串(25_125_C++_简单)(双指针)
  • 基于多通道同步分析的智能听诊系统应用程序
  • k8s数据存储
  • k8s-容器化部署论坛和商城服务(小白的“升级打怪”成长之路)
  • Rust Async 异步编程(六):Pin 和 Unpin
  • Python实现点云投影到直线、平面、柱面和球面
  • ComfyUI AI一键换装工作流无私分享
  • 《分布式系统跨服务数据一致性Bug深度复盘:从现象到本质的排查与破局》
  • 从“数据孤岛”到“业财融合”,外贸订单管理ERP重构一体化逻辑
  • 电气工程及其自动化的课程笔记
  • 接口自动化测试:测试用例也能自动生成
  • Vue3 + Golang Gin 实现客服实时聊天系统(WebSocket + Socket.IO 详解)
  • 【工具安装使用-Jetson】Jetson Orin Nano 刷机和踩坑总结
  • 从人工巡检到AI预警:智慧工地如何用技术重构施工安全体系
  • Flink 状态 RocksDBListState(写入时的Merge优化)
  • 《C++哈希表:高效数据存储与检索的核心技术》
  • 正则表达式 —— \s*
  • C# 相机内存复用(减少图像采集耗时)以及行数复用
  • HTB赛季8靶场 - Previous
  • 无障碍辅助模块|Highcharts引领可访问数据可视化的交流
  • 《李沐读论文》系列笔记:论文读写与研究方法【更新中】
  • 【每天一个知识点】大模型训推一体机
  • linux的conda配置与应用阶段的简单指令备注
  • Hadoop(四)
  • Rust爬虫实战:用reqwest+select打造高效网页抓取工具
  • HIVE创建UDF函数全流程
  • nowcoder刷题--反转链表