各種字元讀取方法的比較(cin,getline等)
讀取一個字元或一個字串的方法有很多,也有些陷阱,下面總結一下。
(1)>>操作符
>>操作符的過載,有很多種原型,能夠從輸入流抽取各種形式的輸入(int、單個字元、字串等),也是我們最常用的一種讀取字元的方式。它需要配合輸入流物件使用(cin就是iostream類中的istream類物件靜態成員),並且返回值是輸入流的引用,所以能夠有cin>>a>>b這樣的寫法。
結束符:cin>>遇到“回車”(\n) 結束輸入,另外遇到“空格”、“TAB”(\t)、之後就不再接收字元。所以如果希望輸入帶空格的字串,應當使用其它的方法。
對字元的處理:
舉兩個例子:
#include "stdafx.h"
#include<iostream>
using namespace std;
int _tmain(int argc,_TCHAR* argv[])
{
char a[20],b[20];
cin>>a>>b;
cout<<a<<b<<endl;
return0;
}
輸入與輸出:
原因就是開始cin>>讀取輸入到a中,遇到空格後,a的輸入結束,cin>>忽略這個空格,開始讀取輸入到b。
而如果是
int _tmain(int argc,_TCHAR* argv[])
{
char a[20],b[20];
cin>>a;
cin.getline(b,20);
cout<<a<<endl;
cout<<b<<endl;
return 0;
}
如果輸入為hello,那麼結果為
如果輸入為hello world,那麼結果為,a等於hello,而b為空格world,原因是cin.getline不會忽略開頭的空格。cin>>後使用其他函式進行輸入也類似,事實上,只有cin>>會忽略有效字元前的其它字元。
(2) istream:: getline
istream::getline是istream類的public成員函式(iostream類又繼承了istream類,通過cin物件來呼叫istream::getline),其原型有兩種形式:
istream& getline (char* s, streamsize n);
istream& getline (char* s, streamsizen, char delim );
用於從輸入流讀取指定長度n-1的字串到s所指向的字元變數中。
結束符:其中第一種宣告預設\n為結束符,而第二種形式則可以通過第三個引數delim來指定結束符。
對字元的處理:前面的例子已經說明了cin.getline不會忽略有效字元前的其它字元,那麼輸入結束時的情況如何,看兩個例子便知。
int _tmain(int argc,_TCHAR* argv[])
{
chara[20],b[20];
cin.getline(a,20);
cin.getline(b,20);
cout<<a<<b<<endl;
return0;
}
輸入與輸出:
可以看出cin.getline能夠接收空格,另外a輸入結束時丟棄了結束符\n,所以b能夠進行正常的輸入(當然,如果連續輸入兩個回車,那b就為空了)。值得注意的是,這裡說的b為空是隻包含\0結束符,因為當引數n大於0時,cin.getline函式會自動往字串末尾新增\0,這也符合C風格字串的要求。
需要注意的是,當輸入字串為空,以及輸入字串的長度超出n-1時,failbit標誌位會被置位,將會影響到之後的輸入。
(3) istream:: get
istream:: get同樣是istream類的public成員函式,其也有多種原型:
能夠讀取單個字元,也能讀取C風格字串,還能直接從流快取中讀取,前兩種功能比較常用。
結束符:讀取單個字元時,也就無所謂結束符。當讀取字串時,與getline一樣,第一種宣告預設\n為結束符,而第二種形式則可以通過第三個引數delim來指定結束符。
對字元的處理:在輸入字串時,cin.get不會忽略有效字元前的字元,同時在輸入結束時會將\n留在輸入流中。看個例子驗證一下。
int _tmain(int argc,_TCHAR* argv[])
{
chara[20],b[20];
cin.get(a,20);
cin.get(b,20);
cout<<b<<endl;
cout<<a<<endl;
return0;
}
如果輸入為hello world,那麼結果為,a等於hello world,而b為\n。
原因也是遺留的\n導致了b輸入的直接結束。
但是需要注意的是,採用不帶引數的cin.get()讀入一個字元的時候,會有不同的行為,看幾個例子:
int _tmain(int argc,_TCHAR* argv[])
{
char b[20];
char a;
cin.get(b,20);
cin.get(a);
cout<<a<<b<<endl;
return0;
}
如果輸入hello world回車,則結果為:。可以看到b就等於hello world,而a等於\n,並將其原原本本的輸出,因為\n對cin.get()來說並不是結束符。
如果輸入從a到z的26個字母,則結果為:。a為第20個字母t,b為前19個字母。所以get函式與getline函式不同,輸入字串長度超出n-1也能正常執行。
如果將接收的順序反過來會怎麼樣呢,
int _tmain(int argc,_TCHAR* argv[])
{
char b[20];
char a;
cin.get(a);
cin.get(b,20);
cout<<b<<a<<endl;
return0;
}
如果輸入hello world回車,很明顯結果為:。說明a得到第一個輸入字元後,剩下的輸入給了b。
另外cin.get(a)還能寫成a=cin.get(),但是無引數形式的返回值是int型別,這裡面會發生隱式轉換。
(4)std::getline
對於string類物件,我們還經常用另一個getline函式,用於讀入字串到string類物件中。這個getline函式明顯是不屬於istream類的。
其原型如下:
istream& getline(istream& is, string& str);
istream& getline(istream&& is, string& str);
istream& getline(istream& is, string& str, chardelim);
istream& getline(istream&& is, string& str, char delim);
結束符:與getline一樣,前兩種宣告預設\n為結束符,而後兩種宣告則可以通過第三個引數delim來指定結束符。
對字元的處理:在輸入字串時,std::getline不會忽略有效字元前的字元,同時在輸入結束時\n會從輸入流中取出並丟棄。
處理方式和 istream:: getline是一樣的,因此這裡就不再給出例程,唯一要注意的就是函式原型的區別。
(5)getchar,gets(已被移除)等
需要包含<cstdio>標頭檔案,這些函式屬於C庫函式,C++程式中應當儘量避免使用。
主要看一下getchar函式,它的原型很簡單:
int getchar ( void );
對字元的處理:getchar的行為和cin.get()類似,如果stdin流(cin與其同步對應)中沒有字元,程式會等待使用者進行輸入,直到使用者輸入回車,getchar才開始從stdin流中按順序取出一個字元,餘下的字元會殘留在stdin流中,將會被後續的輸入函式取出。getchar函式的返回值是字元的ASCII碼,如出錯返回-1。如果stdin流中已經有字元,比如之前輸入殘留在流中的\n,那麼getchar會直接將其取出並返回。
看兩個例子能夠驗證以上說法:
int _tmain(int argc,_TCHAR* argv[])
{
chara,b[20],c;
a=getchar();
c=getchar();
cin.getline(b,20);
cout<<b<<a<<c<<endl;
return0;
}
輸入與輸出為:
即b等於llo world,a與c分別等於h與e。
int _tmain(int argc,_TCHAR* argv[])
{
chara,b[20];
cin>>b;
a=getchar();
cout<<a<<b<<endl;
return0;
}
輸入為hello回車,結果為。也就是a等於\n,b等於hello。
總的來說,除了cin>>之外,其他函式都不會忽略第一個有效字元之前的字元,也就是會讀取之前輸入殘留的換行符\n(除非之前使用的是getline函式),這往往是引起問題的根源。一般我們可以在兩個輸入函式之間加入一句cin.get()或cin.ignore()來吃掉這個換行符。
getline函式的作用是從輸入流中讀取一行字元,其用法與帶3個引數的get函式類似。即
cin.getline(字元陣列(或字元指標), 字元個數n, 終止標誌字元)
[例13.7] 用getline函式讀入一行字元。
- #include <iostream>
- using namespace std;
- int main( )
- {
- char ch[20];
- cout<<"enter a sentence:"<<endl;
- cin>>ch;
- cout<<"The string read with cin is:"<<ch<<endl;
- cin.getline(ch,20,'/'); //讀個字元或遇'/'結束
- cout<<"The second part is:"<<ch<<endl;
- cin.getline(ch,20); //讀個字元或遇'/n'結束
- cout<<"The third part is:"<<ch<<endl;
- return 0;
- }
enter a sentence: I like C++./I study C++./I am happy.↙
The string read with cin is:I
The second part is: like C++.
The third part is:I study C++./I am h
請仔細分析執行結果。用“cin>>”從輸入流提取資料,遇空格就終止。因此只讀取 一個字元'I',存放在字元陣列元素ch[0]中,然後在ch[1]中存放'\0'。因此用"cout<<ch"輸出時,只輸出一個字元'I'。然後用cin.getline(ch, 20, '/')從輸入流讀取19個字元 (或遇結束)。請注意:此時並不是從輸入流的開頭讀取資料。在輸入流中有一個字元指標,指向當前應訪問的字元。在開始時,指標指向第一個字元,在讀入第一個字元'I'後,指標就移到下一個字元('I'後面的空格),所以getline函式從空格讀起,遇到就停止,把字串" like c++."存放到ch[0]開始的10個數組元素中,然後用"cout<<ch"輸出這10個字元。注意:遇終止標誌字元"/"時停止讀取並不放到陣列中。再用cin.getline(ch, 20)讀19個字元(或遇'/n'結束),由於未指定以'/'為結束標誌,所以第2個'/'被當作一般字元讀取,共讀入19個字元,最後輸出這19個字元。
有幾點說明並請讀者思考:
1) 如果第2個cin.getline函式也寫成cin. getline(ch, 20, '/''),輸出結果會如何? 此時最後一行的輸出為:
The third part is: I study C++.
2) 如果在用cin.getline(ch, 20, '/')從輸入流讀取資料時,遇到回車鍵("\n"),是否 結束讀取?結論是此時"\n"不是結束標誌"\n"被作為一個字元被讀入。
3) 用getline函式從輸入流讀字元時,遇到終止標誌字元時結束,指標移到該終止標誌字元之後,下一個getline函式將從該終止標誌的下一個字元開始接著讀入,如本程式執行結果所示那樣。如果用cin.get函式從輸入流讀字元時,遇終止標誌字元時停止讀取,指標不向後移動,仍然停留在原位置。下一次讀取時仍從該終止標誌字元開始。這是getline函式和get函式不同之處。假如把例13.7程式中的兩個cin.line函式呼叫都改為以下函式呼叫:
cin.getline(ch, 20, '/');
則執行結果為:
enter a sentence: I like C++./I study C++./I am happy.↙
The string read with cin is: I
The second part is: like C++.
The third part is: (沒有從輸人流中讀取有效字元)
第2個cin. getline(ch, 20, '/')從指標當前位置起讀取字元,遇到的第1個字元就是終止標誌字元讀入結束,只把"\0"存放到ch[0]中,所以用“cout<<ch”輸出時無字元輸出。
因此用get函式時要特別注意,必要時用其他方法跳過該終止標誌字元(如用後面介紹的ignore函式,詳情請檢視:一些與輸入有關的istream類成員函式),但一般來說還是用getline函式更方便。
4) 請比較用“cin<<”和用成員函式cin.getline()讀資料的區別。用“cin<<”讀資料時以空白字元(包括空格、tab鍵、回車鍵)作為終止標誌,而用cin.getline()讀資料時連續讀取一系列字元,可以包括空格。用“cin <<”可以讀取C++的標準型別的各型別資料(如果經過過載,還可以用於輸入自定義型別的資料),而用cin.getline()只用於輸入字元型資料。