會錯意表錯情,搭錯車上錯床——“度日如年”的故事及“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 );

}

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM