C語言文件操作解析(五)

  

       在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.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章