在C語言中,有個符號大家都應該很熟悉,那就是EOF(End of File),即文件結束符。但是很多時候對這個理解並不是很清楚,導致在寫代碼的時候經常出錯,特別是在判斷文件是否到達文件末尾時,常常出錯。
1.EOF是什麼?
在VC中查看EOF的定義可知:
#define EOF (-1)
EOF只是代表一個整形常量-1。因此很多人認爲在文件的末尾存在這個結束標誌EOF,這種觀點是錯誤的。事實上在文件的末尾是不存在這個標誌的。那麼有人會問那下面的程序如何解釋?
char ch; while((ch=fgetc(fp))!=EOF) { printf("%c\n",ch); }
書上都通過這樣的代碼去判斷是否讀取到文本文件末尾,就是當讀取到EOF的時候就結束操作。這種理解是錯誤的。
先看一下函數fgetc的原型:
int fgetc(FILE *fp);
事實上在fgetc函數內部,每次都是讀取一個字節的數據,而且這一個字節的數據是以unsigned即無符號型處理的,然後將這一個字節的數據賦給一個int型變量作爲返回值返回,因此無論從文件中讀取的是什麼數據,作爲無符號型賦值給一個int型變量,返回值不可能是負數。比如當讀取到數據0xFA時,因爲是以無符號處理的,因此在將0xFA賦值給int型變量的時,int型變量的高位是填充0的(爲什麼填充0,跟彙編語言裏面的符號擴展類似,在後面會提到),因此返回的結果是0X00 00 00 FA,始終不會是負數.而若讀取到文件末尾的時候,即沒有數據可供讀取的時候,那麼返回EOF,即-1,這個-1是一個int型常量,二進制表示是0x FF FF FF FF。
上面的代碼具有很大的侷限性,因爲其只能判斷是否到達文本文件的末尾,而不能對二進制文件進行準確的判斷。因爲正常情況下,文本文件中無論如何是無法讀取到-1(0x FF)這個數據,因此可以判斷。但是對於二進制文件不同,很可能讀到的一個字節的數據就是0xFF,那麼返回值此時就是-1,但是此時還未到達文件末尾,造成錯誤的判斷。
那麼有沒有辦法解決?可以將ch定義爲int型即可。
下面來比較一下下面這段程序和上面程序在執行時的區別。
int ch; while((ch=fgetc(fp))!=EOF) { printf("%c\n",ch); }
假如在文件中讀取到的數據是0xFA。
那麼對於上面一段程序的執行過程是:
將0xFA先賦值給一個int型變量(假如是a),那麼此時a爲0x 00 00 00 FA,當將返回值a返回給變量ch時,由於ch是char型的,只有8位,那麼只將a的低8位賦給ch,那麼此時ch爲0x FA,而ch是作爲有符號處理的,那麼此時ch的值肯定是負數。
而若將ch定義爲int型,執行過程爲:
將0xFA先賦值給一個int型變量(假如是a),那麼此時a爲0x 00 00 00 FA,當將返回值a返回給變量ch時,由於ch也是int型的,因此ch爲0x 00 00 00 FA,是一個正數,兩段程序執行得到的結果完全不同。
下面看一下若讀取到的數據是0x FF(此時未到文件末尾)時,是什麼結果。
若ch爲char型,則當將返回值0x 00 00 00 FF返回時,取低8位賦給ch,那麼此時ch爲-1,此時會誤判爲到達文件末尾;
而若ch爲int型,則當將返回值0x 00 00 00 FF返回時,ch的值爲0x 00 00 00 FF,此時ch不爲-1,不會誤判爲文件末尾。
(當然上面所述成立必須是在讀取不出錯的情況下才成立)
所以很多情況下會用到函數feof.
二.feof
feof函數的原型是
int feof(FILE *fp);
若到達文件末尾則返回一個非零值,否則返回0。
在VC中查看feof函數的定義:
#define _IOEOF 0x0010
#define feof(_stream) ((_stream)->_flag & _IOEOF)
可知feof函數判斷是否到達文件末尾時與_flag這個標誌有關。
下面看一下這段程序:
#include<stdio.h> #include<stdlib.h> int main(void) { FILE *fp; int ch; if((fp=fopen("test.txt","w+"))==NULL) { printf("can not open file\n"); exit(0); } for(ch=65;ch<=70;ch++) { fputc(ch,fp); } rewind(fp); while(feof(fp)==0) { ch=fgetc(fp); printf("%0X\n",ch); } fclose(fp); return 0; }
執行結果是:
41
42
43
44
45
46
FFFFFFFF
Press any key to continue
爲什麼最後打印結果會多打印一個FFFFFFFF?不是隻往文件中寫入了數據65-70麼?
先看一下C++ Reference中關於feof函數的描述(C++ Reference是一個比較好的網站,裏面是關於C++所有庫函數的描述,網址在博客首頁的鏈接中有,http://www.cplusplus.com/reference/):
Checks
whether the End-of-File indicator associated with stream is set, returning a value different from zero if it is.
This indicator is generally set by a previous operation on the stream that reached the End-of-File.
從描述中可知,只有當與文件關聯的流到達文件末尾時,此時若再進行讀取操作,文件結束的標誌(上面所述的_flag)纔會被重新置位。
因此在上述程序中,當讀取完最後一個字節的數據後,文件結束標誌並沒有被置位,只有當位置指針到達末尾時,再發生讀取操作時,而此時又沒有數據可供讀取,因此返回-1,所以打印出的結果中會多一個FFFFFFFF,在這之後纔會將_flag重新置位,此時feof函數才能檢測出已經到達了文件末尾。
那麼可以通過下面的辦法解決這個問題:
ch=fgetc(fp); while(feof(fp)==0) { printf("%0X\n",ch); ch=fgetc(fp); }
這樣就不會多打印一個FFFFFFFF了。
在上面提到彙編語言中符號擴展的問題,其實在C語言中屬於數據類型轉換的範疇。下面簡要說明一下:
符號擴展只針對將字長小的數據賦給字長大的數據時存在,若是字長大的數據賦給字長小的數據,取低位即可。
下面看一段程序:
#include<stdio.h> int main(void) { unsigned char ch1=0XFF; char ch2=0XFF; char ch3=0X73; int a=ch1; int b=ch2; int c=ch3; printf("%d\n%d\n%d\n",a,b,c); return 0; }
執行結果爲:
255
-1
115
原因是由於ch1、ch2、ch3都是char型變量,只佔一個字節,區別在於ch1是無符號的,在將ch1賦值給a時,ch1是看做無符號數據進行處理的,那麼在填充a的高位是用0去填充;而對於ch2和ch3都是有符號的,那麼在填充高位時就要注意了,若ch2的最高位爲0,那麼表示ch2是正數,此時填充高位用0填充,而若ch2的最高位爲1,則填充高位數據用1填充。
如程序執行的結果所示,由於ch2的最高位爲1,那麼在填充b的高位的時候會用1去填充,那麼b爲0X FF FF FF FF;而ch3的最高位爲0,那麼填充c的高位用0填充,所以c的值爲0x 00 00 00 73.