C++ 中的文件 IO 流

文件流類與文件流對象

  • C++ 中對文件的操作是由文件流類完成的
  • 通過文件流類可以將文件和流聯繫起來
  • 文件流類分爲輸入流,輸出流和輸入/輸出流
  • 再對文件進行操作之前,需要將文件流說明爲 ifstream,ofstream 或者 fstream 的對象

文件操作過程

在 C 語言中,對文件的操作步驟爲:

  • 打開文件
  • 進行讀寫
  • 關閉文件

而在 C++ 中,對文件的操作步驟爲:

  • 定義文件流對象
  • 打開文件
  • 進行讀寫
  • 關閉文件

定義流對象

ifstream ifile;
ofstream ofile;
fstream f;

只是在定義流對象之前之前需要包含對應的頭文件。

打開文件

void open(const std::string& __s, ios_base::openmode __mode = ios_base::in)
void open(const std::string& __s, ios_base::openmode __mode = ios_base::out | ios_base::trunc)
void open(const std::string& __s, ios_base::openmode __mode = ios_base::in | ios_base::out)

打開文件的函數爲 open(),第一個參數表示要打開的文件名,第二個參數表示文件的打開方式,可以看出三種不同的文件流對象,具有不同的默認打開方式。

主要的文件打開方式爲:

文件打開方式 描述
ios::in 0x01 以讀方式打開文件,若文件不存在則報錯
ios::out 0x02 以寫方式打開文件,若文件不存在則創建
ios::app 0x08 在文件末尾添加內容,如果文件不存在,則報錯
ios::trunc 0x10 若文件存在,則清除文件所有內容,若文件不存在,則創建新文件
ios::binary 0x80 以二進制方式打開文件,缺省時以文本方式打開文件
ios::nocreate 0x20 打開一個已有文件,如果文件不存在,則打開失敗
ios::noreplace 0x40 如果要打開的文件已經存在,則打開失敗
  • 通常很少單獨使用某一種打開方式,一般會將幾種打開方式進行組合:
ios::in | ios::out 以讀寫方式打開文件,ifstream 對象默認的打開方式
ios::in | ios::binary 以二進制讀方式打開文件
ios::out | ios::binary 以二進制寫方式打開文件
ios::in | ios::out | ios::binary 以二進制讀寫方式打開文件
ios_base::out | ios_base::trunc ofstream 對象默認的打開方式
  • C++ 中文件都是藉助文件流類打開的,而類中存在構造函數,因此也可以直接利用構造函數打開
  • C 中是通過判斷指針是否爲空來判斷是否正確地打開了文件,而 C++ 中則是通過類對象來進行判斷的

關閉文件

文件操作之後,應使用 close() 來關閉文件。

#include <iostream>
#include <fstream>

using namespace std;

int main()
{
    ifstream ifile("test.txt");
    ofstream ofile("out.txt");
    char buf[100];

    if(!ifile && !ofile)
        cout<<"error"<<endl;

    while(ifile)
    {
        ifile>>buf;
        ofile<<" "<<buf;
    }

    ifile.close();
    ofile.close();

    return 0;
}

使用上邊的程序可以將 test.txt 中的內容對應的拷貝到 out.txt 文件中,只是 out.txt 中沒有換行。

流文件狀態與判斷

標識位

在 ios_base.h 中,給出了一個判斷流文件狀態的標誌位:

enum _Ios_Iostate
  { 
    _S_goodbit 		= 0,
    _S_badbit 		= 1L << 0,
    _S_eofbit 		= 1L << 1,
    _S_failbit		= 1L << 2,
    _S_ios_iostate_end = 1L << 16,
    _S_ios_iostate_max = __INT_MAX__,
    _S_ios_iostate_min = ~__INT_MAX__
  };

/// Indicates a loss of integrity in an input or output sequence (such
/// as an irrecoverable read error from a file).
static const iostate badbit =	_S_badbit;

/// Indicates that an input operation reached the end of an input sequence.
static const iostate eofbit =	_S_eofbit;

/// Indicates that an input operation failed to read the expected
/// characters, or that an output operation failed to generate the
/// desired characters.
static const iostate failbit =	_S_failbit;

/// Indicates all is well.
static const iostate goodbit =	_S_goodbit;

上邊的頭文件中以枚舉的形式給出了幾個標誌位,這幾個標誌位各自代表有各自的含義。

函數

eof()

如果讀取文件到達文件末尾,返回 true

bad()

如果讀寫過程中出錯,返回 true

fail()

如果讀寫過程中出錯或者格式錯誤,返回 true

good()

如果發生了之前的任何情況,該函數都返回 false

clear()

標誌位被置位後,可以使用 clear() 來重置標誌位

#include <iostream>
#include <fstream>

using namespace std;

int main()
{
    cout<<cin.eof()<<" "<<cin.bad()<<" "<<cin.fail()<<" "<<cin.good()<<endl;
    cin.clear();

    int i;
    cin>>i;

    cout<<cin.eof()<<" "<<cin.bad()<<" "<<cin.fail()<<" "<<cin.good()<<endl;
    cin.clear();

    return 0;
}

結果爲:

0 0 0 1
abc
0 0 1 0

!cin

在打開文件的時候,我們使用了 !cin 的形式來判斷文件是否打開。我們知道 cin 其實是一個流對象,那麼我們又爲什麼能夠使用流對象進行邏輯判斷呢?

這隻能說明在 C++ 中爲流對象提供了某種轉換函數,使之可以將一個流對象轉換成爲可以進行邏輯操作的類型。

在 basic_ios.h 文件中存在兩個重載函數,它們爲這種行爲做出瞭解釋:

operator void*() const
{ return this->fail() ? 0 : const_cast<basic_ios*>(this); }
// 函數會在 while(cin) 或者 if(cin) 時被調用,會將流對象轉化爲 void* 類型
// 重載 void * 的可以實現連等式
// 看着像是運算符重載,但是 C++ 內部並沒有 void * 這個運算符

bool operator!() const
{ return this->fail(); }
// 函數會在 while(!cin) 或者 if(!cin) 時被調用,會將流對象轉化爲 bool 類型
// 這個是對 ! 的重載

可以對自定義類進行上述兩個函數的重載,測試結果:

#include <iostream>
#include <fstream>

using namespace std;

class TEST
{
public:
    operator void *() const
    {
        cout<<"operator void *() const"<<endl;
        return (void *)(this);
    }

    bool operator !() const
    {
        cout<<"bool operator !() const"<<endl;
        return true;
    }
};

int main()
{
    TEST t;

    if(t)
        cout<<"yes"<<endl;
    else
        cout<<"no"<<endl;

    if(!t)
        cout<<"yes"<<endl;
    else
        cout<<"no"<<endl;

    return 0;
}

結果爲:

operator void *() const
yes
bool operator !() const
yes

不得不說,C++ 真的很神奇。

文件讀寫

就像 cout 和 cin 一樣,如果要讀取數據,可以使用文件流類的 get,getline 和 >> 進行操作;如果要寫入數據,可以使用 put,write 和 << 進行操作。

流狀態的查詢和控制

#include <iostream>
#include <fstream>

using namespace std;

int main()
{
    ifstream ifile("test.txt");
    ofstream ofile("out.txt");
    char buf[100];

    if(!ifile && !ofile)
        cout<<"error"<<endl;

    while(ifile >> buf,!ifile.eof())
    {
        ofile<<" "<<buf;
        if(ifile.bad())
            throw runtime_error("IO stream corrupted");
        if(ifile.fail())
        {
            cerr<<"bad data";
            ifile.clear(istream::failbit);
            continue;
        }
    }

    ifile.close();
    ofile.close();

    return 0;
}
  • 上面的循環會不斷迭代,直到到達文件結束符或者發生不可恢復的讀取錯誤爲止。
  • 循環條件使用了逗號操作符,因此會返回最右邊操作數作爲整個表達式的結果。
  • 如果 ifile 到達文件結束符,條件則爲假,退出循環,如果 ifile 沒有到達文件結束符,則都會進入循環
  • 在循環中,首先檢查流是否已破壞,如果流已經被破壞,則拋出異常,退出循環
  • 如果輸入無效,則輸出警告並清除 failbit 狀態,continue 下一次循環

文件讀寫實例

#include <iostream>
#include <fstream>

using namespace std;

int main()
{
    ifstream ifile("test.txt");
    ofstream ofile("out.txt");
    char buf[1024];

    if(!ifile && !ofile)
        cout<<"error"<<endl;

    while(ifile.getline(buf,1024,'\n'))
    {
        ofile<<buf<<" "<<endl;
        if(ifile.eof())
            break;
    }

    ifile.close();
    ofile.close();

    return 0;
}

隨機讀寫函數

成員函數 描述
tellg() 返回當前指針位置(輸入流操作)
seekg() 絕對移動
seekg() 相對操作
seekp() 絕對移動(輸出流操作)
seekp() 相對操作
tellp() 返回當前指針位置

g 表示 get,爲輸入,p 表示 put,爲輸出。參照位置爲:

成員 意義
ios::beg = 0 相對於文件頭
ios::cur = 1 相對於當前位置
ios::end = 2 相對於文件尾
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章