C 库函数 - feof()
feof() 用于检测文件是否已经读到末尾了。
我们可以把它理解成一个「终点探测器」——每读完一部分内容就问它一次:「到头了吗?」
描述
C 库函数 int feof(FILE *stream) 测试给定流 stream 的文件结束标识符。
这里出现了两个新概念,先分别解释一下:
流(stream) 是 C 语言对输入输出的一种抽象。不管是硬盘上的文件、键盘输入、还是网络数据,在 C 语言里统一看作「流」来处理。你可以把流想象成一条水管,数据从一头流到另一头。
文件结束标识符(EOF 标记) 是每个文件流内部自带的一个状态标记。它就像一个书签,当文件内容被读完时自动翻到「已读完」那一页。
feof() 的作用就是查看这个书签——如果书签显示「已读完」,feof() 返回非零值。如果书签还在「未读完」,feof() 返回 0。
声明
下面是 feof() 函数的声明,逐部分拆解其含义:
int feof(FILE *stream)
int 是返回值类型——feof() 返回一个整数,非零表示到末尾了,0 表示还没到。
FILE *stream 是参数——你需要传入一个文件指针(就是 fopen() 打开文件时返回的那个指针),告诉 feof() 要检查哪个文件流。
参数
feof() 接收一个参数:
| 参数 | 类型 | 说明 |
|---|---|---|
| stream | FILE * | 文件指针,由 fopen() 函数返回,指向要检测的已打开文件 |
返回值
返回值说明如下:
| 返回值 | 含义 |
|---|---|
| 非零值 | 文件已经读到末尾,没有更多内容可读了 |
| 0 | 文件还没读完,可以继续读取 |
关键要点:feof() 只有在「已经尝试读取过文件末尾之后」才会返回非零值。如果只是刚好读完了最后一个字符但还没有尝试继续读,feof() 依然返回 0。这个时机问题下一节会详细说明。
实例
下面的实例演示了 feof() 函数的正确用法。
程序逐字符读取文件内容,每读一个字符就用 feof() 检查一次是否到了末尾。
实例
int main ()
{
// FILE * 是文件指针类型,用于指向一个已打开的文件
FILE *fp;
// fgetc() 返回 int 而不是 char,因为它可能需要返回 EOF(-1)
int c;
// fopen() 打开文件,"r" 表示只读模式(read)
fp = fopen("file.txt","r");
// 如果文件不存在或无权限,fopen() 返回 NULL
if(fp == NULL)
{
// perror() 打印系统错误信息,帮助定位问题
perror("打开文件时发生错误");
return(-1);
}
// 使用无限循环 + 内部判断的方式
// 这样能确保「先读取,后判断」,避免读到多余内容
while(1)
{
// 第一步:从文件中读取一个字符
c = fgetc(fp);
// 第二步:读取后立刻用 feof() 检查是否到了末尾
if( feof(fp) )
{
// 到末尾了,跳出循环,不再处理 c
break ;
}
// 第三步:确认没到末尾后,才使用 c 的值
printf("%c", c);
}
// fclose() 关闭文件,释放系统资源
fclose(fp);
return(0);
}
假设我们有一个文本文件 file.txt,它的内容如下:
这里是 runoob.com
编译并运行上面的程序,输出结果为:
这里是 runoob.com
程序正确地打印了文件的全部内容,没有多出任何乱码字符。
常见误区:为什么不能把 feof() 放在循环条件里
很多初学者会写出下面这种写法,看起来更简洁,但其实是错的:
错误示例
int main ()
{
FILE *fp;
int c;
fp = fopen("file.txt","r");
if(fp == NULL)
{
perror("打开文件时发生错误");
return(-1);
}
// 错误:把 feof() 作为循环的入口条件
while( !feof(fp) )
{
c = fgetc(fp);
printf("%c", c);
}
fclose(fp);
return(0);
}
这个错误写法的输出结果会是:
这里是 runoob.com
末尾多了一个乱码字符。为什么会这样?下面逐轮追踪循环的执行过程:
| 循环轮次 | 循环入口 feof() | fgetc() 读到 | printf() 输出 | 说明 |
|---|---|---|---|---|
| 第 1 轮 | 返回 0(还没读) | '这' | 这 | 正常 |
| 第 2 轮 | 返回 0(还没到末尾) | '里' | 里 | 正常 |
| ... | ... | ... | ... | 中间字符正常输出 |
| 最后一轮 | 返回 0(还没到末尾) | 'm' | m | 最后一个正常字符 |
| 多出的一轮 | 返回 0(注意!此时刚读完 'm',feof 还不知道已经到末尾了) | EOF(-1) | (乱码) | bug!多读了一次 |
问题的根源在于:当你刚好读完 'm'(文件的最后一个字符)时,feof() 仍然返回 0。
它必须等你「再尝试读一次」,发现已经没有内容可读了,才会把内部标记翻到「已结束」。
所以 while(!feof(fp)) 这个写法会让循环多跑一轮。在多余的那一轮里,fgetc() 返回 EOF(-1),printf 尝试把 -1 当作字符打印,就出现了乱码。
一句话记住:feof() 是「事后报告」,不是「事前预警」。它回答的是「上次读的时候是不是碰到末尾了」,而不是「下次读会不会碰到末尾」。所以永远不要用 feof() 作为循环的入口条件。
正确的模式是前面实例中的写法——先读取,再判断,确认安全后才使用读取到的内容。
什么时候使用 feof()
feof() 主要用于以下两种场景:
| 场景 | 用法 | 示例 |
|---|---|---|
| 循环读取文件 | 先读后判断(推荐) | while(1) { c = fgetc(fp); if(feof(fp)) break; ... } |
| 读取出错排查 | 区分「正常结束」和「读取出错」 | if(feof(fp)) { /* 正常读完 */ } else if(ferror(fp)) { /* 读取出错 */ } |
第二种场景中,fgetc() 返回 EOF 有两种可能:真的读完了(用 feof() 判断),或者读取过程中出了错(用 ferror() 判断)。两者需要区分处理。

C 标准库 - <stdio.h>