cin建有一個緩沖區,即輸入緩沖區。一次輸入過程是這樣的,當一次鍵盤輸入結束時會将輸入的資料存入輸入緩沖區,而cin函數直接從輸入緩沖區中取資料。正因為cin函數是直接從緩沖區取資料的,是以有時候當緩沖區中有殘留資料時,cin函數會直接取得這些殘留資料而不會請求鍵盤輸入,這就是為什麼有時會出現輸入語句失效的原因!
cin的一些輸入函數和操作符
1. cin<<
該操作符是根據後面變量的類型讀取資料。
輸入結束條件 :遇到Enter、Space、Tab鍵。(這個很重要!)
對結束符的處理 :丢棄緩沖區中使得輸入結束的結束符(Enter、Space、Tab)
cin讀入資料遇到空格結束;并且丢棄空格符;緩沖區有殘留資料時,讀入操作直接從緩沖區中取資料。
2. cin.get()該函數有三種格式:
cin.get(), cin.get(char ch), cin.get(array_name, Arsize)
讀取字元的情況:
輸入結束條件:Enter鍵
對結束符處理:不丢棄緩沖區中的Enter
cin.get() 與 cin.get(char ch)用于讀取字元,他們的使用是相似的,
即:ch=cin.get() 與 cin.get(ch)是等價的。
不丢棄上次輸入結束時的Enter和Space字元
讀取字元串的情況:cin.get(array_name, Arsize)是用來讀取字元串的,可以接受空格字元,遇到Enter結束輸入,按照長度(Arsize)讀取字元, 會丢棄最後的Enter字元。
3. cin.getline()cin.getline() 與 cin.get(array_name, Arsize)的讀取方式差不多,以Enter結束,可以接受空格字元。按照長度(Arsize)讀取字元, 會丢棄最後的Enter字元。
但是這兩個函數是有差別的:
cin.get(array_name, Arsize)當輸入的字元串超長時,不會引起cin函數的錯誤,後面的cin操作會繼續執行,隻是直接從緩沖區中取資料。但是cin.getline()當輸入超長時,會引起cin函數的錯誤,後面的cin操作将不再執行。
cin的一些錯誤處理函數
cin.fail(), cin.bad(), cin.good(), cin.clear(), cin.ignore()
程式執行時有一個标志變量來标志輸入的異常狀态,其中有三位标志位分别用來标志三種異常資訊,他們分别是:failbit,eofbit,badbit。這三個标志位在标志變量中是這樣配置設定的:
____________________________________
| 2 | 1 | 0 |
| failbit | eofbit | badbit |
|___________|__________|___________|
先看一下這幾個标志位的作用:
badbit, to record a loss of integrity of the stream buffer.
eofbit, to record end-of-file while extracting from a stream.
failbit, to record a failure to extract a valid field from a stream.
In addition, a useful value is goodbit, where no bits are set.
接下來我麼看幾個ios類的資料定義:
typedef T2 iostate;
static const iostate badbit, eofbit, failbit, goodbit;
這裡ios類定義了這四個常量badbit, eofbit, failbit, goodbit,其實這四個标志常量就是取對應标志位的掩碼,也即輸入的四種異常情況!
以上四個常量對應的取值為:
ios::badbit 001 輸入(輸出)流出現緻命錯誤,不可挽回
ios::eofbit 010 已經到達檔案尾
ios::failbit 100 輸入(輸出)流出現非緻命錯誤,可挽回
ios::goodbit 000 流狀态完全正常, 各異常标志位都為0
我們可以用輸出語句來驗證這幾個常量的值:
cout << ios:: failbit << endl;
cout << ios:: eofbit << endl;
cout << ios:: badbit << endl;
cout << ios:: goodbit << endl;
輸出的結果為:
4
2
1
【注意】它們不是failbit、badbit、eofbit、goodbit這四個标記位的存貯變量,而是四個标志四種異常狀态的常量,其實他們就相當于取對應狀态标志位的掩碼。如果标志變量為flag,則flag & failbit 就取得fail标志位。
我們來看幾個關于異常标志的函數:
1、iostate ios::rdstate()
取标志變量的值,我們可以用該函數取得整個标志變量的值,再與前面定義的标志位常量相與就可以獲得對應标志位的狀态。如:
void TestFlags( ios& x ) // 獲得x流的三個标志位狀态
{
cout << ( x.rdstate( ) & ios::badbit ) << endl;
cout << ( x.rdstate( ) & ios::failbit ) << endl;
cout << ( x.rdstate( ) & ios::eofbit ) << endl;
cout << endl;
}
2、bool ios::fail() const;
1 or true if rdstate & failbit is nonzero, otherwise 0 or false. 其中rdstate即通過rdstate()取得的辨別變量的值,與failbit相與,即取得failbit标志位的值,如果結果非零則放回true,否則傳回false。即該函數傳回failbit的狀态,将标志位狀态通過bool值傳回。
3、bool ios::bad() const;
1 or true if rdstate & badbit is nonzero; otherwise 0. (引用msdn)
與fail()相似。
4、bool ios::good() const;
1 or true if rdstate == goodbit (no state flags are set), otherwise, 0 or false.
改函數取goodbit的情況,即三個标志位都0(即沒有任何異常情況)時傳回true,否則傳回false。
5、void ios::clear(iostate _State=goodbit);
該函數用來重置辨別變量,_State是用來重置的值,預設為goodbit,即預設時将所有标志位清零。使用者也可以傳進參數,如:clear(failbit),這樣就将辨別變量置為failbit(即:001)。
我們一般是用它的預設值,當cin出現異常,我們用該函數将所有标志位重置。如果cin出現異常,沒有重置标志的話沒法執行下一次的cin操作。是以經常在程式中使用 cin.clear(), 為了重置錯誤标志!
6、另外還有一個函數 void ios::setstate(iostate _State);
這個函數也是用來設定辨別變量的,但與clear()不同。clear()是将所有标志清零,在置以參數新的标志。而該函數不清零其他的标志,而隻是将參數對應的标志位置位。這個函數不是經常使用,這裡不再贅述。
在搞清楚了這幾個函數後,對cin輸入操作的錯誤處理就有了比較深的了解了。下面我們回過頭來看看上一節程式8的測試,因為第一次用getline()讀取字元串超長,是以導緻出現異常,大家可以檢視一下标志位來驗證一下!是以會導緻後面的 cin>>ch 語句沒有執行。那我們利用前面學習的clear()函數來強制重置錯誤标志,看看會出現什麼情況呢?
程式:
#include <iostream>
using namespace std;
int main ()
{
char ch, str[20];
cin.getline(str, 5);
cout<<"flag1:"<<cin.good()<<endl; // 檢視goodbit狀态,即是否有異常
cin.clear(); // 清除錯誤标志
cout<<"flag1:"<<cin.good()<<endl; // 清除标志後再檢視異常狀态
cin>>ch;
cout<<"str:"<<str<<endl;
cout<<"ch :"<<ch<<endl;
return 0;
}
輸入:
12345[Enter]
輸出:
flag1:0 // good()傳回false說明有異常
flag2:1 // good()傳回true說明,clear()已經清除了錯誤标志
str:1234
ch :5
【分析】程式執行結束還是隻執行了一次讀操作,cin>>ch還是沒有從鍵盤讀取資料,但是與程式8中不同,這裡列印了ch的值為'5',而且在cin>>ch之前已經清楚了錯誤标志,也就是cin>>ch的讀操作實際上執行了。這就是前面講的cin讀取資料的原理:它是直接從輸入緩沖區中取資料的。此例中,第一次輸入"12345", 而getline(str, 5)根據參數'5'隻取緩沖區中的前4個字元,是以str取的是"1234",而字元'5'仍在緩沖區中,是以cin>>ch直接從緩沖區中取得資料,沒有從鍵盤讀取資料!
也就是目前一次讀取資料出錯後,如果緩沖區沒有清空的話,重置錯誤标志還不夠!要是能将緩沖區的殘留資料清空了就好了哦!下面我們再來看一個很重要的函數!
7、basic_istream& ignore(streamsize _Count = 1,int_type _Delim = _type::eof());
function: Causes a number of elements to be skipped from the current read position.
Parameters:
_Count, The number of elements to skip from the current read position.
_Delim, The element that, if encountered before count, causes ignore to return and allowing all elements after _Delim to be read.
這個函數用來丢棄輸入緩沖區中的字元,第一參數定義一個數,第二個參數定義一個字元變量。下面解釋一下函數是怎樣執行的:函數不停的從緩沖區中取一個字元,并判斷是不是_Delim,如果不是則丢棄并進行計數,當計數達到_Count退出,如果是則丢棄字元退出。例:cin.ignore(5, 'a'); 函數将不斷從緩沖區中取一個字元丢棄,直到丢棄的字元數達到5或者讀取的字元為'a'。下面我們看個
程式
#include <iostream>
using namespace std;
int main ()
{
cin.ignore(5, 'a');
return 0;
}
輸入:
c[enter]
c[enter]
c[enter]
c[enter]
c[enter]
程式結束。
【分析】程式開始時緩沖區是空的,cin.ignore()到緩沖區中取資料,沒有則請求從鍵盤輸入,每次從鍵盤輸入一個字元,如果不是'a'則丢棄,是以該測試中共輸入了5次,直到計數達到5。
輸入:
c[enter]
c[enter]
a[enter]
程式結束。
【分析】前面兩個字元不是'a'丢棄且計數沒達到5,第三次輸入為'a', 丢棄該字元程式結束!
丢棄一個字元:
我們看看這個函數的預設值,第一個參數預設為1,第二個參數預設為EOF。是以cin.ignore()就是丢棄緩沖區中的第一個字元,這在程式中也是比較常用的!我們回過頭看看程式5,程式5中用cin.get()讀取字元,第一次讀取時用回車符結束,而get函數不丢棄回車符,是以回車符仍殘留在緩沖區中,導緻第二次讀取資料直接從緩沖區中取得回車符!這與我們最初的用以是不相符的,既然cin.get()不會自動丢棄輸入結束時的回車符,這裡我們學會了ignore()函數,我們就可以自己手動求其回車符啊!是以程式5可以這樣改動:
程式:
#include <iostream>
using namespace std;
int main()
{
char c1, c2;
cin.get(c1);
cin.ignore(); // 用該函數的預設情況,丢棄一個字元,即上次輸入結束的回車符
cin.get(c2);
cout<<c1<<" "<<c2<<endl; // 列印兩個字元
cout<<(int)c1<<" "<<(int)c2<<endl; // 列印這兩個字元的ASCII值
return 0;
}
輸入:
a[Enter]
b[Enter]
輸出:
a
b
97 98
【分析】這樣程式就正常了!
清空整個緩沖區:
其實該函數最常用的方式是這樣的,将第一個參數設的非常大,将第二個參數設為'/n',這樣就可以緩沖區中回車符中的所有殘留資料,因為一般情況下前面輸入殘留的資料是沒有用的,是以在進行新一次輸入操作前将緩沖區中所有資料清空是比較合理。
如:cin.ignore(1024, '/n');
或者:cin.ignore(std::numeric_limits<std::streamsize>::max(), '/n');