getline函式

鴨脖發表於2012-04-21
在我的印象中,getline函式經常出現在自己的視野裡,模糊地記得它經常用來讀取字串

。但是又對它的引數不是很瞭解,今天又用到了getline函式,現在來細細地總結一下:

首先要明白設計getline函式的目的,其實很簡單,就是從流中讀取字串。而且讀取的方

式有很多,包括根據限定符,根據已讀取的字元的個數。從這個函式的名稱來看,它的直觀

意義是從流中讀取一行,但是大家不要被這表面的現象所迷惑。其實如果讓我來為這個函式

去一個名字的話,或許我會取一個getString,因為它的目的本來就是從流中讀取字元的序

列,而不是像get函式那樣一次讀取一個字元。

另外要注意,C++中有兩個getline函式,一個是在string標頭檔案中,定義的是一個全域性的

函式,函式宣告是istream& getline ( istream& is, string& str, char delim )與
istream& getline ( istream& is, string& str );另一個則是istream的成員函式,函

數宣告是istream& getline (char* s, streamsize n )與istream& getline (char* 

s, streamsize n, char delim );注意第二個getline是將讀取的字串儲存在char陣列

中而不可以將該引數宣告為string型別,因為C++編譯器無法執行此預設轉換。

下面根據一個例子簡單地介紹一下該函式:
test.txt檔案如下所示:

abcd
efgh
ijk

現在先嚐試全域性函式getline。從函式宣告中我們觀察到兩種函式宣告的不同主要體現在參

數的個數上,如果是兩個引數的話,那麼預設的限定符便是‘\n’了,但是如果宣告瞭限

定符,'\n'是否仍然有效呢?我寫了如下程式做測試:

int main(){
int n = 6;
string tem;
ifstream infile("test.txt");
for(int i = 0;i<n;i++){
//getline(infile,tem);
getline(infile,tem,'\t');
cout<<tem;
}
return 0;
}

輸出結果是:
abcd
efg

從中可以看出換行符確實失效了。所以getline函式的限定符只有一個,是相互覆蓋的。

再來看一下istream的getline函式:

int main(){
char a[3];
ifstream infile("test.txt");
infile.getline(a,3,'c');
cout<<a;
}

輸出結果是a
其實istream的getline是在全域性函式的getline函式的基礎上,又多了一個終止讀取的條

件,即根據已讀取的字元的個數來判定,實際上是讀取n-1個字元,因為最後要為‘\0’留

下一個位置。其他地方二者基本相同。

原理想必也很簡單。每一次getline,檔案指標都不斷向下走,相當於不斷的呼叫get函式

並且將已經讀取的字元儲存下來。當遇到限定符或者已讀取的字元個數達到了引數的要求(

或者是由於檔案的原因),那麼便終止讀取。如果是碰到了限定符,那麼該字元便會被 

extracted and discarded,也就是檔案指標向下再移一位,但是並不儲存該字元,也就

是每次getline之後,檔案指標會停留在限定符的後面(遇到限定符的情況)。

但是看下面的這個情況:

int main(){
int n = 13;
string tem;
ifstream infile("test.txt");
for(int i = 0;i<n;i++){
//getline(infile,tem);
getline(infile,tem,'\t');
cout<<tem<<endl;
}
return 0;
}

按照我的理解的話,那麼檔案中總共11個字母,當檔案指標停在‘\t’之後,k之前的時候

,剛好是第八次,第九次getline的時候,由於在讀過k之後,遇到了檔案結束符,所以get

指標應該停留在k之後,這個時候再getline的話應該是無效的,但是輸出結果跟我想的不

一樣:

a
b
c
d
e
f
g
h
i
j
k
k
k
k
k

這說明第九次getline之後,get指標所指向的位置並沒有改變,這說明我想的思路有問題

,於是我在網上看了getline函式的原始碼,其中有一篇註釋比較好的:

_Myt& getline(_Elem *_Str, streamsize _Count, _Elem _Delim)   
{// get up to _Count characters into NTCS, discard _Delim   
    _DEBUG_POINTER(_Str);    //判斷傳入指標的合法性  
    ios_base::iostate _State = ios_base::goodbit;    
    _Chcount = 0; //從輸入流中讀取的字元數  
    const sentry _Ok(*this, true);  
    /*注:上面這句很關鍵,它關係到下面的if是否執行,也就是是否讀輸入流。這句從

語法上看,是 
    sentry是一個class, _Ok是sentry類的一個const物件,構造這個物件時需要傳入兩個

引數 
    第一個是流物件自身的引用,第二個表示對空白字元(如空格、製表符)的處理方式

,為true時意味著不忽略空白字元,即一個字元一個字元的從輸入流中提取。 
    */  
      
    if (_Ok && 0 < _Count)   
    /* 

************************************************************************** 
    * sentry類內部過載了一個型別轉換運算子,它把sentry類的例項轉換成了一個bool

表示式。 
    * 這個表示式返回sentry類的私有成員_Ok的值。 
    bool sentry::operator bool() const 
    * { // test if _Ipfx succeeded 
    *       return (_Ok); 
    *   } 
    * _Ok這個成員的值由sentry類的建構函式 
    * 在初始化時設定,設定的過程比較麻煩,這裡不做贅述(其實我也沒看十分明白)。 
    * 但可以肯定的是,當輸入流的狀態是正常時,這個成員的值也是true, 
    * 反之,則是false。  
    *  
    * _Count是呼叫者傳入的第二個引數,這裡用做迴圈計數器的初值,以後每讀一個字

符, 
    * _Count的值會減一。 
    

****************************************************************************

**/  
    {  
    // state okay, use facet to extract   
    int_type _Metadelim = _Traits::to_int_type(_Delim);   
    int_type _Meta = _Myios::rdbuf()->sgetc();//從輸入流讀一個字元   
    for (; ; _Meta = _Myios::rdbuf()->snextc()) //snextc()從輸入流中讀取下一

個字元  
        if (_Traits::eq_int_type(_Traits::eof(), _Meta))   
              {// end of file, quit   
                _State |= ios_base::eofbit;   
                break;   
               }//注:遇到檔案尾,getline結束   
        else if (_Meta == _Metadelim) {  
             // got a delimiter, discard it and quit   
            ++_Chcount;    //讀取字元數+1  
            _Myios::rdbuf()->sbumpc();  
            /*注:上面這句把結束符讀掉了,如果不指定結束符,那就是把'\n'讀掉了

。  
            但回車符本身並沒有拷貝到緩衝區中, 
            這樣下次的讀操作將從回車符後面的第一個字元開始, 
            */  
            break;   
        }/* 注:遇到結束符,getline結束,注意這裡的順序,它是先判斷是否遇到結束

符,後判斷是否讀入了指定個數的。 */  
        else if (--_Count <= 0)   
        {// buffer full, quit   
            _State |= ios_base::failbit;   
            break;   
        }  
        //注:讀到了指定個數,執行到這裡已經隱含了在指定個數的最後一位仍然不是

結束符,  
        //因此該部分將輸入流狀態置為了錯誤。  
        //這直接導致了接下來的getline(或者get)以及>>運算子等讀操作都不能正確執

行)   
        else {  
            // got a character, add it to string   
            ++_Chcount;  //讀取字元數加1  
            *_Str++ = _Traits::to_char_type(_Meta);   
        }//注:這一分支將讀取到的單個字元拷貝到緩衝區中  
    }   
    *_Str = _Elem();  //  
    /* add terminating null character /*注:前面這句為字串加入了終止符'\0' 
    因為_Elem()構造了一個ascii碼為0的字元物件*/  
    _Myios::setstate(_Chcount == 0 ? _State | ios_base::failbit : _State);  
    /*注:如果沒有讀入任何字元,要保持執行這一次getline之前的輸入流狀態, 
    否則根據這一次getline執行的情況,設定輸入流為相應狀態。 */  
    return (*this);   //返回輸入流物件本身  
}   

但是我覺得這其中還是有問題,因為:
sbumpc: advances the get pointer and returns the character pointed by it 

before the call.
snextc: advances the get pointer and returns the character pointed by it 

after the call.
由於是傳引用,所以不論呼叫哪個,都會改變原檔案流中get的指標所指向的位置。而且,

告訴大家一個更為驚奇的結果便是:
下面程式:
int main(){
int n = 6;
string tem;
ifstream infile("test.txt");
for(int i = 0;i<n;i++){
getline(infile,tem);
//getline(infile,tem,'\t');
cout<<tem<<endl;
}
return 0;
}
的輸出結果為:
abcd
efgh
ijk
ijk
ijk
ijk

不管按照我的想法還是按照對上面原始碼的理解,結果都不應該是這個樣子。是原始碼錯了,還

是我的理解有問題?希望知道的朋友能指導一下。


==========================================================================
好吧,可能是編譯器的問題,用比的編譯器編譯執行了一下,結果和我的想法是一致的,跟原始碼所要表達的也是一致的
,所以我原先的想法是沒錯的,結貼啦~
所以如果你不斷的從檔案流中getline的話,如果你想判斷是否已經達到檔案結尾的話,那麼只需判斷getline所得到的字串是否為
空就ok了~
再補充一下,由於getline函式將istream引數作為返回值,和輸入操作符一樣也把它作為判斷條件。所以如果到達檔案結尾的話,那麼返回的檔案流包含的字元為空,這個false是等價的 ,所以我們也可以用while(getline(infile,str))來對檔案流是否達到結尾進行判定。

相關文章