閱讀299 返回首頁    go 阿裏雲 go 技術社區[雲棲]


小心為上:注意C++ fstream給你設下的陷阱

 

                      小心為上:注意C++ fstream給你設下的陷阱  

       

透過名字看本質:到底什麼是stream

 stream的定義

stream的中文翻譯為“流”,不是很好理解,我們來看英文關於stream的定義,比較常見的有兩個:

1.      A stream is an abstraction that represents a device on which input and output operations are performed.

2.      A stream is a "stream of data" in which character sequences "flow."

 

英文看起來比較累,總結一下,提煉如下幾個關鍵點:

1.      abstraction that represents a device:代表一個設備;

2.      stream of data:數據流,隱含了FIFO的一個特性;

3.      character sequences flow:字符序列在其中流動,而不是二進製在其中流動;

 

多說無益,還是看圖說話:

 

 

注:上圖中暗含一個玄機:stream的數據是設備數據的一個子集,因為stream隻是代表一個設備,而並不是完全等於一個設備。

 

關公戰秦瓊: stream vs buffer

一個容易和stream混淆的概念就是大家常見的bufferbuffer的定義為(參見WIKI Data Buffer):A buffer is a region of memory used to temporarily hold data while it is being moved from one place to another.

英文看起來也比較累,還是總結一下:

1.      region of memory:一塊內存區域,因此就隱含了隨機訪問和二進製的操作方式;

2.      one place to another:數據移動過程中使用

 

經過對兩個術語定義的分析總結,相信大家都明白了這兩者之間的差異了,其實這就是一個關公戰秦瓊的例子,因為它們兩個實際上並不是一個競爭關係。

匯總對比點如下:

對比點

stream

buffer

作用

代表一個設備

數據臨時存儲

訪問方式

FIFO

隨機訪問

數據內容

字符流

二進製

當然,並不是說streambuffer就毫無關係了,stream為了提高性能,實現的時候就用到了buffer

小心為上:注意fstream設下的陷阱

2.1  charwchar_t的操作

當你使用<<輸出的時候,任何字符都可以輸出;但當使用>>進行輸入的時候,開頭的空白字符缺省情況下是跳過的。

 

例如:假設文件中有這麼一個字符串“       test”,則使用>>讀入的時候,直接就讀到了字母‘t’,而不會是空格。

避免這個陷阱的招數:

1.      清除skipws標誌:unsetf(ios::skipws)

2.      使用get()函數

2.2  指針類型的操作

1.      char*

char*類型的數據輸出時會將字符串全部輸出,但輸入的時候你千萬要注意,輸入的時候默認會將前麵的空白字符全部去掉;而且輸入時默認是以空格來作為分隔符的,例如:“This is a test C-string.”可以全部輸出,但如果文件中有這麼一串字符串,那麼用<<是無法全部讀入的,隻能讀入“This”“is”“a”“test”“C-string.

避免這個陷阱的招數:

1.      如果想改變默認去掉頭部空白字符的操作方式,請參考skipws

2.      如果想改變以空格作為結束符的操作方式,對不起,用<<是沒有辦法的,隻能用istream& istream::get (char* str, streamsize count, char delim)或者

istream& istream::getline (char* str, streamsize count, char delim)

 

2.      void*

直接輸出指針地址。

 

3.      其它指針

不管是指向int/double等標準數據類型,還是自定義的struct/class類型,都是輸出指針指向的地址,而不是輸出指針指向的對象。

2.3  eoffail

2.3.1  eof

eof從字麵意思來看,當然是end of file,用於表明當前已經到了文件末尾,不能再讀了。

但這裏有一個很迷惑的陷阱:隻要遇到結束符,流就會將狀態置為EOF,而不管置位前的操作是否成功。

例如,使用getline函數讀取文件的最後一行,如果這一行是因為遇到了EOF而結束的,那麼getline操作是成功的,但eof還是會置位。

 

因此,不能在調用函數後通過eof來判斷函數調用是否讀到文件末尾了,而應該直接判斷調用本身是否成功,具體樣例請看fail

2.3.2  fail

導致fail標誌位置位的有如下常見的情況:

1.      文件不存在;

2.      文件不能創建;

3.      eof標誌位置位;

4.      非法的格式,例如當你期望數字的時候,而文件裏麵卻是字母

 

注意第三種情況,在文件eof的時候也會同時置fail,所以,循環讀取文件的時候,要將faileof結合起來使用:在循環判斷中使用failfail失敗後再使用eof

錯誤的用法:

1:    std::ifstream file("test.txt");

2:    std::string word;

3:    double value;

4:    while ( true ) {

5:      // A word and a double value were both read successfully

6         file >> word >> value;

7         if( file.eof() )

8             break;

9:    }

 

正確的用法

1:    std::ifstream file("test.txt");

2:    std::string word;

3:    double value;

4:    while (file >> word >> value) {

5:      // A word and a double value were both read successfully

6:    }

7:    if (!file.eof()) throw std::runtime_error("Invalid data from file");

 

2.4  fstreambinary打開方式

文件流的打開方式中有一個binary,從字麵意思來看,應該是按照二進製打開文件,然後進行二進製讀寫。

然而這樣理解的話,就陷入了C++的陷阱:binary實際上和二進製讀寫沒有關係,binary隻是為了告訴係統是否將不同操作係統間特定的字符替換,最典型的是換行符,在windows上是/r/n,而在Unix類係統上是/n,如果加了這個binary標誌,流就不會自動替換。

 

C++fstream流如何進行二進製讀寫呢?其實很簡單:隻需要調用get/readput/write即可。也就是說:是否是二進製讀寫和文件打開方式無關,而是和調用函數有關。如果使用<<>>,則就是按照字符流進行讀寫;如果使用get/readput/write,則按照二進製讀寫。

 

前麵說過流是character sequences flow,那為什麼這裏又說fstream能夠按照二進製讀寫呢?由於沒有研究過相關的實現代碼,因此這裏無法給出分析,個人推斷應該是流的內部實現將字符流轉換為二進製了。

 

最後更新:2017-04-02 06:51:29

  上一篇:go HGE 係列教材(6) --- 程序流程與細節
  下一篇:go HGE 係列教材(4) --- 初探 HGE Core Functions 層