文字檔案 I/O


在 C++ 要讀寫檔案,是將之連結至串流,基於串流的 I/O 架構與相關說明,可以在〈Input/output library〉找到。

在〈終端機輸入輸出〉中,談過 coutostream 實例,cinistream 實例,這兩個實例是定義在 iostream 標頭。

istream 型態是定義在 istream 標頭,它是 basic_istream 模版類別的 basic_istream<char> 特化版本,basic_istream 是字元輸入串流的基礎模版類別;ostream 型態是定義在 ostream 標頭,它是 basic_ostream 模版類別的 basic_ostream<char> 特化版本,basic_ostream 是字元輸出串流的基礎模版類別。

在文字檔案串流的處理方面,basic_ifstream 繼承了 basic_istream,而 ifstream 型態是 basic_ifstream<char> 特化版本,用來進行文字檔案輸入串流操作,basic_ofstream 繼承了 basic_ostream,而 ofstream 型態是 basic_ofstream<char> 特化版本,用來進行文字檔案輸出串流操作,ifstreamofstream 定義在 fstream 標頭之中。

使用 ifstream 建立實例時,可以指定連結的檔案名稱,如果沒有指定檔案名稱,會建立一個沒有連結檔案的串流,後續必須以 open 來連結檔案:

void open( const char *filename,
           ios_base::openmode mode = ios_base::in );

void open( const std::string &filename,                                  
           ios_base::openmode mode = ios_base::in );

例如,可以使用下面這個片段來開啟檔案輸入串流:

ifstream in;
in.open("filename");

如果開啟失敗,串流物件在布林判別場合會是 false,可以使用下面的片段來判斷:

if(in) {
    ... 進行檔案處理
}

類似地,使用 ofstream 建立實例時,可以指定連結的檔案名稱,如果沒有指定檔案名稱,會建立一個沒有連結檔案的串流,後續必須以 open 來連結檔案:

void open( const char *filename,
           ios_base::openmode mode = ios_base::out );

void open( const std::string &filename,                                  
           ios_base::openmode mode = ios_base::out );

mode 決定檔案的開啟模式,是由 ios 類別定義的常數來決定,下面列出 openmode 的值與用途:

  • ios::in:輸入(basic_ifstream 預設)
  • ios::out:寫入(basic_ofstream 預設)
  • ios::ate:開啟後移至檔案尾端
  • ios::app:附加模式
  • ios::trunc:如果檔案存在,清除檔案內容
  • ios::binary:二進位模式

當然,程式的世界實際上並沒有文字檔案這東西,資料都是二進位,字元串流只是在讀取或寫入的過程,會進行文字編碼的轉換,例如 int 數字 9,在寫入的操作中,會轉換為編碼 57 的位元組資料,至於本身是 char 的資料,就直接以對應的位元組寫出。

因為 ifstreamofstream 各是 istreamostream 的子類別,>><< 運算子也可以用在 ifstreamofstream 實例上,結果就是使用 ifstreamofstream 時,可以如同使用 cincout 一樣地操作。

來看個讀寫檔案的範例:

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

struct Account {
    string id;  
    string name; 
    double balance;
    Account(string id = "", string name = "", double balance = 0.0) : 
        id(id), name(name), balance(balance) {};
};

void print(ostream &out, Account &acct) {
    out << acct.id << " " 
        << acct.name << " " 
        << acct.balance; 
}

void read(istream &in, Account &acct) {
    in >> acct.id >> acct.name >> acct.balance; 
}

int main() { 
    Account acct = {"123-456-789", "Justin Lin", 1000};

    ofstream out("account.data"); 
    print(out, acct);
    out.close();      // 記得關閉檔案

    Account acct2;
    ifstream in("account.data");
    read(in, acct2);
    in.close();       // 記得關閉檔案

    print(cout, acct2);

    return 0; 
}

因為 ifstreamofstream 各是 istreamostream 的子類別,cincout 也各是 istreamostream 實例,因此 printread 對它們來說是通用的,執行過後,account.data 檔案中會存有「123-456-789 Justin 0」,而最後標準輸出中,也會顯示「123-456-789 Justin 0」。