今天一個學生寫了如下程式碼片段,其目的將一個文字檔的內容輸出到螢幕上。
ifstream in("file3.txt")char buf[3];while(!in.eof()) { in.getline(buf, sizeof(buf));cout<<buf<<endl;}
file3.txt的內容如下:
abcefg
在執行的時候程式輸出一個ab之後便進入了死迴圈。原因何在?
經過一個小時的查資料和看代碼,終於大致搞清問題所在,與getline的實現機制有關。
getline成員函數的聲明如下
basic_istream<Elem, Tr>&
getline( char_type *_Str, streamsize _Count, char_type _Delim);
第一個參數是字元緩衝區地址,第二個是緩衝區長度,第三個是分隔字元(預設是斷行符號)。
其實現的大致流程是:
1、首先判斷istream的failbit位是否為1,為1的話意味著輸入資料流的狀態有錯誤,則不進行讀操作,getline函數結束執行
2、從當前位置開始從輸入資料流中依次讀取單個字元並拷貝到緩衝區,直到遇到下列條件滿足時,迴圈結束。
(1)遇到檔案尾時停止讀操作,並設定流對象的結束標記為1
(2)讀到調用者指定的分隔字元時,此時將分隔字元之前的字元拷貝到緩衝區中,但分隔字元本身不拷貝進去,並且下次讀操作將從分隔字元後的下一個字元開始。
(3)已經讀了n-1個字元(n是調用者傳入的第二個實參_Count的初值),此時要把流對象的錯誤標誌位置1(為什麼要這麼幹,我也不知道,個人覺得這麼設計不太合理....)
當迴圈結束後,gelline函數會在字串的尾部加一個C風格的結束符'\0'。
這樣,學生遇到的現象就可以解釋了。
首先,由於file3.txt檔案是存在的,所以in對象開始時的狀態是正常的,因此第一次getline將會執行,由於緩衝區的長度是3,因此在讀完ab兩個字元之後getline內部的迴圈便終止了。此時getline會把in對象的failbit設為1,但檔案還未讀到尾部,所以in.eof()為false,這樣在第二次進入while迴圈體時,迴圈條件!in.eof()為true,於是繼續執行getline函數
,但是由於第一次的getline操作已經把in對象的failbit設為1,第二次的getline便不進行任何讀操作了,此時流的指標和流的狀態均未發生變化,於是第三次迴圈時與第二次迴圈一樣,迴圈條件為真,可以進入迴圈體,in對象的failbit設為1,getline函數不進行讀取操作,如是反覆,便導致了死迴圈。
知道了病因,於是便有了下面的解決方案
while(!in.eof()){in.getline(buf,sizeof(buf));//將if(in.fail() && in.gcount()==(sizeof(buf)-1)) in.clear(); cout<<buf<<endl;}
這裡的clear成員函數的作用是將in對象的狀態設回正常狀態。gcount函數返回上次讀操作中從輸入資料流中提取的字元數(包括分隔字元)。
請讀者自行分析和改進這個解決方案。