会错意表错情,搭错车上错床——“度日如年”的故事及“feof()”的故事

1. “度日如年”的故事

  一个幼儿园小盆友,看到了“度日如年”这个成语,以为是天天过年的意思,于是活学活用、借题发挥:“祝大家在新的一年里天天‘度日如年’”。
这就是会错了意,表错了情。
  “度日如年”的故事讲完了,下面是这个故事的C语言版。

2. feof()的故事

View Code
/*
例10.2 将一个磁盘文件中的信息复制到另一个磁盘文件中。今要求将上例建立的file1.dat文件中的内容复制到另一个磁盘文件file2.dat中。
*/
#include <stdio.h>
#include <stdlib.h>
int main()
{FILE *in,*out;
char ch,infile[10],outfile[10];
printf("输入读入文件的名字:");
scanf("%s",infile);
printf("输入输出文件的名字:");
scanf("%s",outfile);
if((in=fopen(infile,"r"))==NULL)
{printf("无法打开此文件\n");
exit(0);
}
if((out=fopen(outfile,"w"))==NULL)
{printf("无法打开此文件\n");
exit(0);
}
while(!feof(in))
{ch=fgetc(in);
fputc(ch,out);
putchar(ch);
}
putchar(10);
fclose(in);
fclose(out);
return 0;
}

  这段代码毛病很多,这里只谈其核心部分,即while语句部分。
  这条语句貌似首先“检查in所指的文件是否结束”,如果“!foef(in)”不为0,则从in流中读一个字符,然后写入out流。看起来很美,并且据说“运行结果是将file1.dat文件中的内容复制到file2.dat中。”
  然而,这只不过是一厢情愿的错觉而已。如果仔细检查一下就会发现,文件file2.dat比文件file1.dat长一个字节;如果手头有UltraEdit之类的十六进制编辑器,不难发现多出的字符的值很可能是FFH,即十进制的255。
  这个多出来的字符是怎么来的呢?主要原因有两个:对feof()函数的误解和对fgetc()函数的不求甚解:

为了知道对文件的访问是否完成,只须看文件读写位置是否移到文件的末尾。用feof函数可以检查到文件读写位置标记是否移到文件的末尾,即磁盘文件是否结束。feof(in)是检查in所指向的文件是否结束。如果是,则函数值为1(真),否则为0(假)。

    ————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p340~341

fgetc:
调用形式:fgetc(fp)
功能:从fp指向的文件读入一个字符
返回值:读成功,带回所读的字符,失败则返回文件结束标志EOF(即-1)

    ————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p338

  在这种错误认识的指导下,由于会错了意,难免会表错情。

3. feof()函数及fgetc()函数的真正含义

  首先,feof()函数并非“检查”“文件读写位置标记是否移到文件的末尾”,feof()函数检查的是流的end-of-file标记。end-of-file标记和读写位置标记虽然同属于FILE类型结构体记录的内容,但它们根本就是两回事。如果feof()函数检测到了end-of-file标记,返回一个int类型的非零值(不一定时1),否则返回int类型的0。
那么,end-of-file标记是记录流控制数据的FILE类型结构体对象中固有的吗?也不是,这个end-of-file标记是由fgetc()这样的函数所设置的。当fgetc()函数发现输入流中不存在数据后,除了返回一个EOF,还会设置FILE对象中的end-of-file标记,在很多实现中这个标记用一个“位”表示。
  由此可见,即使流中没有数据的情况下,feof()函数也不一定返回非零值。只有在流中没有数据并且fgetc()之类的函数继续读取失败之后,fgetc()函数才能检查到流的end-of-file标记。
  也就是说,feof()函数并不能告诉你流是否已经到了结尾,它所能告诉你的只不过是,而且仅仅是,前一次读取失败的原因是否因为到了流的结尾(读取失败的另一个原因是发生了错误)。
  为了说明这一点,下面进行一项测试。
  首先,在D:盘的根目录下建立一个文本文件ABC.TXT,并在其中写入ABC三个字符。
  然后,运行下面程序:

#include <stdio.h>
#include <stdlib.h>
int main( void )
{
   FILE *abc;
   
   if((abc = fopen("D:\\ABC.TXT","rb")) == NULL )
   {
      printf("打开文件失败\n");
      return !0;
   }
   
   for(int i = 0 ; i < 5 ; i++ )
   {
      int ch;
      int eof_before_read,eof_after_read;
      eof_before_read = feof(abc) ;
      ch = fgetc( abc );
      eof_after_read = feof(abc) ;
      printf(
             "读入%c(%d)前后feof()的值分别为:%d,%d\n",
             ch,ch,eof_before_read,eof_after_read
            );
   } 
   fclose(abc);
   return 0;
}

    这段程序的运行结果是:
  读入A(65)前后feof()的值分别为:0,0
  读入B(66)前后feof()的值分别为:0,0
  读入C(67)前后feof()的值分别为:0,0
  读入(-1)前后feof()的值分别为:0,16
  读入(-1)前后feof()的值分别为:16,16
  由此不难看出,在读入C之后(已经到了流的结尾),feof()函数的返回值依然是0,只是再次试图读取字符之后,feof()的返回值才成了16。这是由于fgetc()函数发现已经没有字符可读,在对应的FILE结构体数据中设置了end-of-file标记的缘故。

4. feof()函数的真正用途

  feof()函数只能事后诸葛亮地告诉我们读入是如何结束的,它根本不能用于拷贝的循环控制。把feof()函数用于拷贝的循环控制,不但是会错意表错情,而且简直是搭错了车上错了床。
  feof()函数的正确用法之一是:

 

#include <stdio.h>
#include <stdlib.h>

void file_copy(FILE * , FILE * );

int main( void )
{
   FILE *abc;
   FILE *abc_b;
   
   if((abc = fopen("D:\\ABC.TXT","rb")) == NULL )
   {
      printf("打开文件失败\n");
      return EXIT_FAILURE;
   }
   
   if((abc_b = fopen("D:\\ABC_B.TXT","wb")) == NULL )
   {
      printf("打开文件失败\n");
      fclose(abc);
      return EXIT_FAILURE;
   }

   file_copy( abc_b ,  abc  );
   
   if( feof(abc) != 0 )
   {
      printf("拷贝正常结束\n");
      fclose(abc);
      fclose(abc_b);
      return EXIT_SUCCESS;
   }     
      
   if( ferror (abc) != 0 )
   {
      printf("拷贝过程中发生错误,目标文件可能并不正确\n");
      fclose(abc);
      fclose(abc_b);
      return EXIT_FAILURE;
   }      

}

void file_copy( FILE * t, FILE *s )
{  
   int ch;
   while( (ch = fgetc(s) ) != EOF )
      fputc( ch , t );

}

 

 

posted @ 2012-01-27 14:09  garbageMan  阅读(4771)  评论(18编辑  收藏  举报