C++知識積累:cin、getline解析
在進行cin和getline的分析之前,一個必須清楚的是標準輸入緩衝區,使用者從鍵盤輸入字串,通過換行符將輸入的字串寫入到輸入緩衝區中,這裡需要特別注意的是,將字串寫入輸入緩衝區時會將末尾的換行符一起寫入輸入緩衝區,什麼意思呢?
舉個例子,在控制檯中輸入“abcd”然後回車,這時候寫入輸入緩衝區的字串實際上是“abcd\n”,會一同將最後的回車換行符一起寫入輸入緩衝區。cin、getline等實際上都是對輸入緩衝區進行操作的。
1. cin簡介
cin是C++程式語言中的標準輸入流物件,即istream類的物件。cin主要用於從標準輸入讀取資料,這裡的標準輸入,指的是終端的鍵盤。常用的cin方法用cin>>、cin.get以及cin.getline。
1.1 cin>>
總結:
以cin>>a為例,從輸入緩衝區中讀取資料,遇到space(enter、tab)時讀取結束,並將space(enter、tab)之前的資料寫入a中,但是會將space(enter、tab)遺留在輸入緩衝區中。
另一方面,如果輸入緩衝區中的資料以space(enter、tab)開頭,那麼cin>>會拋棄掉這些space(enter、tab),直到遇到非space(enter、tab)的資料才進行讀取,此時前面被拋棄掉的space(enter、tab)也不在輸入緩衝區中了。
舉個例子,如下所示:
這裡的cin>>a;cin>>b;cin>>c和cin>>a>>b>>c;是同樣的效果。一開始由於輸入緩衝區中沒有資料,cin會一直阻塞直到資料到來,輸入“aaa+空格+bbb+換行+ccc”然後回車,這時實際上輸入到緩衝區的字串是“aaa bbb/nccc/n”;
輸入緩衝區中有資料了,此時cin>>a開始將資料讀入a中,遇到空格停止,那麼實際上讀入a的是“aaa”,此時緩衝區中剩下“ bbb/nccc/n”,特別注意這裡bbb的前面是有一個空格的,此時cin>>a即執行完畢,開始執行cin>>b;
執行cin>>b時,由於輸入緩衝區中有資料,因此cin>>b直接從輸入緩衝區中讀取資料,由於此時資料以空格符開頭,因此cin>>會拋棄開頭的空格符,然後讀取資料直到遇到換行符/n停止,那麼實際上讀入b的是“bbb”,此時緩衝區中剩下“/nccc/n”,此時cin>>b執行完畢,開始執行cin>>c;
執行cin>>c同理,最終讀入c的是"ccc",最終緩衝區中還剩下一個換行符“/n”。
補充說明
還有一點特別重要的是,cin>>的本質實際上是對cin物件的>>運算子過載,過載返回一個cin物件,其原始碼如下:
istream& operator>> (istream& is, char& ch );
istream& operator>> (istream& is, signed char& ch );
istream& operator>> (istream& is, unsigned char& ch );
istream& operator>> (istream& is, char* str );
istream& operator>> (istream& is, signed char* str );
istream& operator>> (istream& is, unsigned char* str )
因此cin>>a>>b實際上是先呼叫(cin>>a)>>b,其中cin>>a返回cin,然後再有cin>>b。
此外,cin>>還可用於真值判斷,其實所有派生自ios的類都可以被強制轉換為一個指標,如果設定了錯誤標誌位則指標為null,否則非null。cin標誌位一般有四種:ios::failbit,ios::badbit,ios::eofbit和ios::goodbit,在流輸入正常的情況下,ios::goodbit為1,其餘為0,當出現錯誤資訊時ios::failbit,ios::badbit,ios::eofbit就都不一定為0了,
流的錯誤狀態則由ios::failbit,ios::badbit,ios::eofbit組成,當錯誤標誌位非0時,如圖所示:
cin的錯誤標誌位為rdstate,a為int型資料,前面輸入的1234為int型,正常輸入,failbit、badbit和eofbit均為0 ,此時rdstate為0;當輸入"a"時,流發生錯誤,failbit被置為1,此時failbit、badbit和eofbit組成的rdstate錯誤標誌位變成了4。
這四個標誌位中,最常見的即是eofbit位,該標誌位會在讀取到檔案末尾EOF或者Ctrl+Z時置位。
1.2 cin.get()
總結:cin.get()常用的有兩種用法,一種用於讀取一個字元a=cin.get()或cin.get(a),一種用於讀取指定個數字元的cin.get(a,10),不管是哪一種,cin.get()是不存在遇到什麼字元停止的,因為使用cin.get()的時候已經決定了獲取字元的個數,要麼1個要麼就由使用者指定個數,直接從輸入緩衝區中讀入相應個數的字元即可,不管這個字元是不是space(enter、tab),此外,如果輸入緩衝區中開頭即是space(enter、tab),cin.get()也不會拋棄這些字元,依然的照單全收。
1.2.1 cin.get()讀取一個字元
cin.get(a)中a為char型別,a=cin.get()中a可以為string型別,舉個例子:
這裡一開始cin>>a阻塞等待輸入緩衝區中的資料,然後輸入“aaa+空格+bbb”然後回車,則將“aaa bbb\n”寫入輸入緩衝區中,此時輸入緩衝區中有了資料,cin>>a就開始讀取資料了,將"aaa"讀入到a中,輸入緩衝區中剩下“ bbb\n”;
然後接著的b=cin.get(),就直接讀取下一個字元,即是“ ”,可見不帶引數的cin.get()實際上與getchar()是類似的,此時輸入緩衝區中就剩下“bbb\n”了,再進行cin.get©就將“b”讀入到c中了。
1.2.2 cin.get()讀取指定個數字元
如圖所示,輸入“ab+空格+cdefg+換行+hijk+空格+lmn+換行”,輸入緩衝區中即是“ab cdefg\nhijk lmn\n”,此時執行cin.get(a,8),一定要注意,此時實際上讀取到a中的是7個字元(包括中間的空格符,即使是換行符也會被讀取)加上一個結束符‘\0’,因此a為“ab cdef”,此時緩衝區中就剩下“g\nhijk lmn\n”,再用cin>>b和cin>>c讀取到"g"以及“hijk”。
1.3 cin.getline()
總結:cin.getline()與cin.get()來獲取多個字元是相似的,不同的是,cin.getline()可以自己設定讀取的結束符,即讀取到結束符時停止讀取,將結束符之前的寫入到相應變數中,並將輸入緩衝區中的結束符拋棄掉,如果不給定結束符,則預設以換行符‘\n’作為cin.getline()的讀取結束標誌符。
如圖所示,先向緩衝區中輸入“abcd efg\nhijklm\nnopq\n”,在呼叫cin.getline(a,10)時,與cin.get()類似,會企圖從輸入緩衝區中讀入9個字元再加上一個終止符‘\0’,但是由於這裡每個顯式定義getline的讀取結束符,則預設為’\n’,因此再讀到g後面的換行符時,getline就停止了,此時就將“abcd efg”寫入a中,並且將該換行符丟棄掉,此時輸入緩衝區中就剩下“hijklm\nnopq\n”;
接著b=cin.get()讀取下一個字元,可見b讀取到的是‘h’,這也說明了前面的換行符已經被拋棄了,此時輸入緩衝區中還剩下“ijklm\nnopq\n”;
然後繼續執行cin.getline(c,10,‘x’),由於這裡顯式定義了結束符,因此遇到m後面的換行符時並不會停止讀取,最終讀取的9個字元為“ijklm\nnop”。
1.4 cin.ignore()
cin.ignore(int num)的用處在於忽略輸入緩衝區中的某些字元,這些字元包括換行符、空格符等,如果不給定引數,則預設為忽略當前輸入緩衝區中的第一個字元。
那麼cin.ignore()什麼時候用呢?比如說如下所示程式碼和執行情況:
看圖裡可知,cin>>會在輸入緩衝區中留下空格符,所以當再用cin.getline或者cin.get()的時候讀到的第一個字元就是空格符,但是這往往不是我們想要的,此時就可以再cin>>b後加上cin.ignore()來將緩衝區中的空格符去掉,這樣就能達到我們的目的了。
2.getline()
實際上,我們常常會對整行資料讀取,而要想讀取整行資料,就不能對該行資料中的空格符space、製表符tab敏感,因此cin>>顯然是不能滿足要求的,而cin.get()和cin.getline()雖然不對空格符space、製表符tab敏感,但是需要預先指定存放資料的空間大小,由於實際的輸入是不明確的,因此這裡預留的大小往往都是較大的,這種方法也並不是太好,這時候getline()就能很好的解決這個問題。
getline()並不是cin中的成員函式,C++中定義了一個在std名字空間的全域性函式getline,因為這個getline函式的引數使用了string字串,所以宣告在了標頭檔案中了。getline利用cin可以從標準輸入裝置鍵盤讀取一行,當遇到如下三種情況會結束讀操作:
(1)檔案結束;
(2)遇到行分隔符;
(3)輸入達到最大限度。
getline(cin,str)函式是處理string類的函式。第二個引數為string型別的變數。讀入時第二個引數為string型別,而不是char*,要注意區別
getline()函式的定義如下所示
1. istream& getline ( istream &is , string &str , char delim );
2. istream& getline ( istream &is , string &str );
其中,is是進行讀入操作的輸入流;str是用來儲存讀入的內容;delim是終結符,遇到該字元停止讀取操作,不寫的話預設為換行符。與cin.getline()相同,遇到終結符時會停止讀取並且輸入緩衝區中這個終結符也沒有了。
getline()的真值與cin是相同的,所以在使用while(getline(cin,line))的時候,判斷while()是否結束迴圈的條件不是輸入流是否輸入了回車(或getline函式裡你自己定義的結束符),而是getline這個函式是否輸入無效(這麼說好像有點繞,給你們來個爽快的)。直到你輸入了EOF或者ctrl+z,while迴圈才會結束,而且要注意的是,只有getline函式動作完畢時,while才會執行內部的迴圈,直到你輸入了回車或者你自己設定的結束符,str這個字串才會被輸出。並且,還有一點需要注意的是,每次呼叫getline(cin,str)後,呼叫之前的str都會被新得到的資料所覆蓋。
如圖所示:
由圖可知,getline(cin,str)是可以覆蓋str中的內容的。
此外,getline(cin,str)中的cin可以換成其他的輸入流,如stringstream,可見getline與stringstream