现在位置: 首页 > C 教程 > 正文

C 库函数 - feof()

C 标准库 - <stdio.h> C 标准库 - <stdio.h>

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() 接收一个参数:

参数类型说明
streamFILE *文件指针,由 fopen() 函数返回,指向要检测的已打开文件

返回值

返回值说明如下:

返回值含义
非零值文件已经读到末尾,没有更多内容可读了
0文件还没读完,可以继续读取

关键要点:feof() 只有在「已经尝试读取过文件末尾之后」才会返回非零值。如果只是刚好读完了最后一个字符但还没有尝试继续读,feof() 依然返回 0。这个时机问题下一节会详细说明。


实例

下面的实例演示了 feof() 函数的正确用法。

程序逐字符读取文件内容,每读一个字符就用 feof() 检查一次是否到了末尾。

实例

#include <stdio.h>

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() 放在循环条件里

很多初学者会写出下面这种写法,看起来更简洁,但其实是错的:

错误示例

#include <stdio.h>

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> C 标准库 - <stdio.h>