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 );
}
