C++ fstream 詳解


最近在寫哈夫曼壓縮,遇到了一個比較讓人頭疼的問題,那就是對文件的讀寫操作,尤其是以二進制的形式來讀寫,無奈C++Primer第五版上寫的並不詳細,很多讓人困惑的地方沒有涉及或者沒有講清楚。於是這幾天我一直在網上看了很多這方面相關的文章,同時自己也在電腦上試了很多,覺得理解了不少,很多困惑也迎刃而解。

我們都知道,C語言里面對文件的操作是通過文件指針,以及一些相關的函數,那么C++中是如何對文件進行操作的呢?沒錯,就是通過 fstream 這個文件流來實現的。第一次聽到文件流這個名字時,我也是一臉懵逼,總感覺這東西太抽象了,其實我們可以簡單地把它理解成一個類,這樣是不是清楚多了,當我們使用#include <fstream> 時,我們就可以使用其中的 ifstream,ofstream以及fstream 這三個類了,也就可以用這三個類來定義相應的對象了,例如:

ifstream fin();

這樣我們就定義了一個叫做fin的對象了,它就和我們自己定義的其他對象一樣,可以調用ifstream類中的一些函數,可以使用ifstream類中定義的一些操作符等等。那么我們定義完這個對象后,怎么和某個文件關聯起來呢?畢竟我們最終是希望它能幫我們實現對文件的操作。沒錯,我們可以調用它的一個成員函數來實現,如下:

fin.open("test.txt");

open()這個成員函數重載了好幾種形式,上面這種是最簡單的,它中間的參數是一個常量字符指針,你可以填入所要打開文件的相對路徑(比如和你的.cpp文件在同一目錄下你就可以直接輸入文件名,注意必須是完整的文件名,包含文件拓展名,否則找不到這個文件的),也可以填入絕對路徑(比如 "D:\CodeBlocks\Myprojects\0407test\test.txt"),當然,你也可以填入一個指向文件名字符串的指針常量,或者內容等於文件名的string常量,例如:

const char * c = "test.txt";

fin.open(c);

或者

const string s = "test.txt";

fin.open(s); 

注意必須是指向常量的指針或string

如果不是const string 那么得這樣用

string s = "test.txt";

fin.open(str.c_str()); 這里的c_str()是 string 的一個成員函數,作用就是變成一個const string ,其實也就相當於上面的了

另外,open()函數里還可以加上其他參數,例如打開的方式,等等。但是文件名是最基本的一個,如果其他參數未加,將使用缺省值,這個函數的聲明,

void open ( const char * filename,  
            ios_base::openmode mode = ios_base::in | ios_base::out );  
  
void open(const wchar_t *_Filename,  
        ios_base::openmode mode= ios_base::in | ios_base::out,  
        int prot = ios_base::_Openprot); 

參數:

filename    操作文件名

mode        打開文件的方式

prot          打開文件的屬性   //基本很少用到,在查看資料時,發現有兩種方式

ios::in 為輸入(讀)而打開文件
ios::out 為輸出(寫)而打開文件
ios::ate 初始位置:文件尾
ios::app 所有輸出附加在文件末尾
ios::trunc 如果文件已存在則先刪除該文件
ios::binary 二進制方式

 

 

 

 

 

 

 

這些方式是能夠進行組合使用的,以“或”運算(“|”)的方式,例如:

fin.open("test.txt", ios::in|ios::out|ios::binary) 

另外,這個類有一個構造函數允許我們在定義流對象的時候以文件名初始化,如下:

ifstream fin("test.txt");

ifstream fin("test.txt",ios::in);

const string s = "test.txt";

ifstream fin(s,ios::in);

ofstream,ftream和上面是類似的,就不詳細展開了

接下來說說幾個重要的函數,

一個是成員函數is_open(),可以判斷文件是否正確打開,如果是,返回true,否則,返回false。

然后是getline()函數,這個函數是按行讀取txt中的內容,示例如下

ifstream fin("test.txt",ios::in);
string s;
while(getline(fin,s))
        cout << s;//輸出每一行

每次從fin指向的文件中讀取一行,一行之中的所有字符都會被讀入,包括空格。但是結尾的空格不讀入,回車換行也不讀入。

我們知道,所有輸入/輸出流對象(i/o streams objects)都有至少一個流指針:

  • ifstream, 類似istream, 有一個被稱為get pointer的指針,指向下一個將被讀取的元素。
  • ofstream, 類似 ostream, 有一個指針 put pointer ,指向寫入下一個元素的位置。
  • fstream, 類似 iostream, 同時繼承了get 和 put

我們可以通過使用以下成員函數來讀出或配置這些指向流中讀寫位置的流指針

tellg() 和 tellp()
這兩個成員函數不用傳入參數,返回pos_type 類型的值(根據ANSI-C++ 標准) ,就是一個整數,代表當前get 流指針的位置 (用tellg) 或 put 流指針的位置(用tellp).
seekg() 和seekp()
這對函數分別用來改變流指針get 和put的位置。兩個函數都被重載為兩種不同的原型:
seekg ( pos_type position );
seekp ( pos_type position );
使用這個原型,流指針被改變為指向從文件開始計算的一個絕對位置。要求傳入的參數類型與函數 tellg 和tellp 的返回值類型相同。
seekg ( off_type offset, seekdir direction );
seekp ( off_type offset, seekdir direction );
使用這個原型可以指定由參數direction決定的一個具體的指針開始計算的一個位移(offset)。它可以是:

ios::beg 從流開始位置計算的位移
ios::cur 從流指針當前位置開始計算的位移
ios::end 從流末尾處開始計算的位移

文本中的字符是從position = 0開始的,例如

//假設test.txt中的內容是HelloWorld
ifstream fin("test.txt",ios::in);
cout << fin.tellg();//輸出0,流置針指向文本中的第一個字符,類似於數組的下標0

char c;
fin >> c;
fin.tellg();//輸出為1,因為上面把fin的第一個字符賦值給了c,同時指針就會向后 移動一個字節(注意是以一個字節為單位移動)指向第二個字符

fin.seekg(0,ios::end);//輸出10,注意最后一個字符d的下標是9,而ios::end指向的是最后一個字符的下一個位置

fin.seekg(10,ios::beg);//和上面一樣,也到達了尾后的位置

//我們發現利用這個可以算出文件的大小

int m,n;
m = fin.seekg(0,ios::beg);
n =  fin.seekg(0,ios::end);
//那么n-m就是文件的所占的字節數

我們也可以從文件末尾出發,反向移動流指針,
fin.seekg(-10,ios::end);//回到了第一個字符

tellp()以及seekp()的用法類似

順便講一下 << 以及 >> 這兩個操作符

由於ifstream以及ofstream分別繼承於istream和ostream,所以他們也分別繼承了相應的運算符,這就是為什么我們可以有下面這種操作的原因了:

ifstream fin("test.txt");

char c;

fin >> c;

 這其實和我們從控制台讀取文件是一個道理,我們利用istream 中的對象cin 和其中的操作符 >> 來從控制台讀取數據

這里我們用fin對象和 >> 操作符來從相應的文件中讀取數據,相當於流的方向從控制台改變到文件中去了

不過這里應該要注意一下 >> 這個操作符的特點,有如下例子:

假如test.txt中的內容是Hello World

ifstream fin("test.txt");

string s;

fin >> s;

cout << s;

我們發現輸出並不是Hello World,而是Hello,這是為什么呢?

幾番試驗后,我們發現,文件的每個空白之后, ">>" 操作符會停止讀取內容, 直到遇到另一個>>操作符. 因為我們讀取的每一行都被換行符分割開(是空白字符), ">>" 操作符只把這一行的內容讀入變量。這就是這個代碼也能正常工作的原因。如果你想把整行讀入一個char數組, 我們沒辦法用">>"?操作符,因為每個單詞之間的空格(空白字符)會中止文件的讀取。為了驗證,有如下示例:

char  c[20] ;

fin >> c;

我們想包含整個句子, 所以當我們想讀取整行時,我們得用上面提到的getline()。這就是我們要做的:

fin.getline(c, 20); 

cout << c;

這是函數參數. 第一個參數顯然是用來接受的char數組. 第二個參數是在遇到換行符之前,數組允許接受的最大元素數量

現在我們得到了想要的結果:Hello World。

或者這樣用也行:

string s;

getline(fin,s);

cout << s;

注意這樣操作后流指針會指向文件的尾后

 

接下來說說本文的重點了,那就是以二進制的形式讀寫文件

這里的重點就是兩個函數的使用,分別是read()和write()函數

二進制文件會復雜一點, 但還是很簡單的。 首先你要注意我們不再使用插入和提取操作符(譯者注:<< 和 >> 操作符). 你可以這么做,但它不會用二進制方式讀寫。你必須使用read() 和write() 方法讀取和寫入二進制文件. 創建一個二進制文件, 看下一行。

ofstream fout("file.dat", ios::binary); 

這會以二進制方式打開文件, 而不是默認的ASCII模式。首先從寫入文件開始。函數write() 有兩個參數。 第一個是指向對象的char類型的指針, 第二個是對象的大小(譯者注:字節數)。 為了說明,看例子:

int number = 30;

fout.write((char *)(&number), sizeof(number));

第一個參數寫做"(char *)(&number)". 這是把一個整型變量轉為char *指針。如果你不理解,可以立刻翻閱C++的書籍,如果有必要的話。

第二個參數寫作"sizeof(number)". sizeof() 返回對象大小的字節數. 就是這樣!

這樣就寫入了整個結構! 接下來是輸入. 輸入也很簡單,因為read()函數的參數和 write()是完全一樣的, 使用方法也相同。

ifstream fin("file.dat", ios::binary);

fin.read((char *)(&obj), sizeof(obj));

我不多解釋用法, 因為它和write()是完全相同的。二進制文件比ASCII文件簡單, 但有個缺點是無法用文本編輯器編輯。 接着, 我解釋一下ifstream 和ofstream 對象的其他一些方法作為結束.

 

參考文章: 

http://blog.csdn.net/kingstar158/article/details/6859379

http://www.cnblogs.com/greatverve/archive/2012/10/29/cpp-io-binary.html 

http://blog.csdn.net/lightlater/article/details/6364931 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM