• 会错意表错情,搭错车上错床——“度日如年”的故事及“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 );
    
    }
    
  • 相关阅读:
    POJ-1189 钉子和小球(动态规划)
    POJ-1191-棋盘分割(动态规划)
    Java实现 LeetCode 730 统计不同回文子字符串(动态规划)
    Java实现 LeetCode 730 统计不同回文子字符串(动态规划)
    Java实现 LeetCode 729 我的日程安排表 I(二叉树)
    Java实现 LeetCode 729 我的日程安排表 I(二叉树)
    Java实现 LeetCode 729 我的日程安排表 I(二叉树)
    Java实现 LeetCode 728 自除数(暴力)
    Java实现 LeetCode 728 自除数(暴力)
    Java实现 LeetCode 728 自除数(暴力)
  • 原文地址:https://www.cnblogs.com/pmer/p/2330099.html
Copyright © 2020-2023  润新知