ifstream.eof() 函式運用說明

 

假如有一個資料檔案 data.txt 內容如下:
------------------------------
1 2
3 4
5 6
------------------------------
想要用下面的程式片段把資料讀進來

#include <iostream> #include <fstream> #include <cstdlib> #include <cassert> using namespace std;

void main() { int x, y; ifstream ifs("data.txt"); assert(ifs); while (!ifs.eof()) { ifs >> x >> y; cout << x << " " << y << endl; } system("pause"); }

這個程式會有兩種結果
  1. 如果 data.txt 最後一列 6 之後, 沒有任何空格或是換行字元,
    程式輸出為
    1 2
    3 4
    5 6
  2. 如果 data.txt 最後一列 6 之後, 有一個空格 (或是換行字元)
    程式輸出為
    1 2
    3 4
    5 6
    5 6

會有這樣的表現, 理由如下:

  1. 如果 data.txt 最後一列 6 之後, 沒有任何空格或是換行字元:

    迴圈執行第三次時
    ifs >> x >> y;
    讀入 5 ==> x, 6 ==> y
    事實上 >> 已經讀到檔案結束, 發現 6 後面沒有別的數字了,
    這個時候呼叫 ifs.eof(), 就會得到 true
  2. 如果 data.txt 最後一列 6 之後, 有一個空格 (或是換行字元)

    迴圈執行第三次時
    ifs >> x >> y;
    讀入 5 ==> x, 6 ==> y
    這個時候 >> 因為讀到 6 後面的空格, 就發現 6 後面沒有別
    的數字了, 所以檔案指標停在那個空格上, 但是還沒有讀到
    檔案結尾, 所以 ifs.eof() 會得到 false

    如果接下去執行到
    char cbuf;
    ifs.get(cbuf);
    就會讀入空格, 但是還是沒有讀到檔案結束,
    這個時候 如果呼叫 ifs.eof() 還是會得到 false

    當 ifs.eof() 為 false 時,
    迴圈會執行第四次, 不過此時執行
    ifs >> x >> y;
    時, 因為跳過一格空白後就發現檔案結束了,
    所以兩個讀取動作都會失敗, 變數 x 和 y 裡面
    的資料不會有變化, 所以就會看到 5 6 多印一次
如何解決這個問題呢? (最好是不管 6 之後有沒有空格或是換行都只會
讀入三列資料, 迴圈只能進入三次) 你可以試看看下面的程式
ifs >> x >> y;
while (!ifs.eof())
{
    cout << x << " " << y << endl;
    ifs >> x >> y;
}

請注意:

  1. ifs.eof() 並不會去由檔案裡讀取任何資料, 它只是回報上一次
    由檔案串流讀取資料時的狀態

  2. 如果你的 data.txt 裡面是全空的 (0 個 byte 的檔案)
    int x, y;
    ifstream ifs("data.txt");
    while (!ifs.eof())
    {
        ifs >> x >> y;
        cout << x << " " << y << endl;
    }
    這個迴圈還是會執行一次, 第一次 ifs.eof() 一定是 false, 因為執行之前
    沒有執行任何真正由串流讀取資料的動作

  3. 上面這段程式如果檔案裡只有一列資料, 而且第二筆資料之後就是檔案結尾, 則迴圈一次都不會進去, 其它狀況程式的表現都很好
再試看看下面的程式:
while (!ifs.eof())
{
    ifs >> x >> y;
    if (!ifs.fail())
        cout << x << " " << y << endl;
}
這個程式就可以把各種情況都考慮進來了, 至於為什麼在這裡會使用 ifs.fail() 來測試呢 ? 到底這個函式在做什麼 ? 用 Google 可能不是很容易找到滿意的答案, 建議你自己測試一下, 例如你可以執行下面這段程式
while (!ifs.eof())
{
    ifs >> x >> y;
    cout << "good()=" << ifs.good() << ",";
    cout << "fail()=" << ifs.fail() << ",";
    cout << "bad()=" << ifs.bad() << ",";
    cout << "eof()=" << ifs.eof() <<endl;
}
觀察一下資料檔案內各種資料的可能性, 大概就可以找到可用的方法了!!

其實有更簡單的寫法, 先前我們看到開啟檔案串流以後可以用

if (ifs) {...}

來測試串流是否成功開啟, 請再測下面的寫法

while (ifs >> x >> y)
{
    cout << x << " " << y << endl;
}
在由輸入串流中讀取資料的時候, 我們會在讀取資料之候回傳那個串流物件 (請注意看 return 的描述), 那麼 if (ifs) 或是 while (ifs) 代表什麼意思呢? 你可以參考 operator bool 或是 operator!, 基本上是當串流讀取失敗時 (fail bit 或是 bad bit 為 1) 會回傳 false, 關於 operator 的運作等到 operator overloading 時才會說明

C++ 物件導向程式設計課程 首頁

製作日期: 03/19/2014 by 丁培毅 (Pei-yih Ting)
E-mail: pyting@mail.ntou.edu.tw TEL: 02 24622192x6615
海洋大學 電機資訊學院 資訊工程學系 Lagoon