getline函數(精華版)


在我的印象中,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文件如下所示:
 
a bcd
e fgh
i jk
 
現在先嘗試全局函數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;
}
的輸出結果為:
a bcd
e fgh
i jk
i jk
i jk
i jk
 
不管按照我的想法還是按照對上面源碼的理解,結果都不應該是這個樣子。是源碼錯了,還
 
是我的理解有問題?希望知道的朋友能指導一下。
 
 
==========================================================================
好吧,可能是編譯器的問題,用比的編譯器編譯運行了一下,結果和我的想法是一致的,跟源碼所要表達的也是一致的
,所以我原先的想法是沒錯的,結貼啦~
所以如果你不斷的從文件流中getline的話,如果你想判斷是否已經達到文件結尾的話,那么只需判斷getline所得到的字符串是否為
空就ok了~
再補充一下,由於getline函數將istream參數作為返回值,和輸入操作符一樣也把它作為判斷條件。所以如果到達文件結尾的話,那么返回的文件流包含的字符為空,這個false是等價的 ,所以我們也可以用while(getline(infile,str))來對文件流是否達到結尾進行判定。
  


免責聲明!

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



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