zoukankan      html  css  js  c++  java
  • 会错意表错情,搭错车上错床——“度日如年”的故事及“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 );
    
    }
    
  • 相关阅读:
    Win7下的DragDrop事件不触发?
    灵巧多叉树 IAgileMultiTree -- ESBasic 可复用的.NET类库(23)
    ESFramework 4.0 快速上手(01) -- Rapid引擎
    多叉树 IMultiTree -- ESBasic 可复用的.NET类库(22)
    Round缓存管理器RoundCacheManager--ESBasic 可复用的.NET类库(26)
    遵循Spring大纲的XML(反)序列化 SpringFox --ESBasic 可复用的.NET类库(27)
    如何自动以管理员身份运行.NET程序?
    音频数据编解码——在.NET中使用Speex(附下载)
    离线消息如何实现?-- ESFramework 4.0 快速上手(02)
    浅谈黑客攻击
  • 原文地址:https://www.cnblogs.com/pmer/p/2330099.html
Copyright © 2011-2022 走看看