天天看點

轉載:cin深入分析(下) – cin的錯誤處理

在前一節中我們有幾個例子中提到了cin函數出錯,以緻不再執行讀操作(程式8)。而且我們經常會看到程式中會出現cin.clear(),cin.ignore(), cin.fail()等函數。這些函數都是與cin的錯誤處理有關的。這一節我們來分析一下cin的錯誤處理機制,并且學習幾個重要的函數:cin.fail(), cin.bad(), cin.good(), cin.clear(),  cin.ignore()等。

程式執行時有一個标志變量來标志輸入的異常狀态,其中有三位标志位分别用來标志三種異常資訊,他們分别是:failbit,eofbit,badbit。這三個标志位在标志變量中是這樣配置設定的:

____________________________________

|     2     |     1    |     0     |

|  failbit  |  eofbit  |   badbit  |

|___________|__________|___________|

看一下這幾個标志位的作用(引用msdn):

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類的資料定義(引用msdn):

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. (引用msdn)

其中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.  (引用msdn)

改函數取goodbit的情況,即三個标志位都0(即沒有任何異常情況)時傳回true,否則傳回false。

5、void ios::clear(iostate _State=goodbit);

該函數用來重置辨別變量,_State是用來重置的值,預設為goodbit,即預設時将所有标志位清零。使用者也可以傳進參數,如:clear(failbit),這樣就将辨別變量置為failbit(即:001)。

我們一般是用它的預設值,當cin出現異常,我們用該函數将所有标志位重置。如果cin出現異常,沒有重置标志的話沒法執行下一次的cin操作。如上一節的程式2的測試二為什麼第二次輸入操作沒有執行?程式8中 cin>>ch 為什麼沒有執行?都是這個原因!!!

是以經常在程式中使用 cin.clear(), 為了重置錯誤标志!

6、另外還有一個函數 void ios::setstate(iostate _State);

這個函數也是用來設定辨別變量的,但與clear()不同。clear()是将所有标志清零,在置以參數新的标志。而該函數不清零其他的标志,而隻是将參數對應的标志位置位。這個函數不是經常使用,這裡不再贅述。

  在搞清楚了這幾個函數後,對cin輸入操作的錯誤處理就有了比較深的了解了。下面我們回過頭來看看上一節程式8的測試,因為第一次用getline()讀取字元串超長,是以導緻出現異常,大家可以檢視一下标志位來驗證一下!是以會導緻後面的 cin>>ch 語句沒有執行。那我們利用前面學習的clear()函數來強制重置錯誤标志,看看會出現什麼情況呢?

程式9:

#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 = traits_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. (引用msdn)

這個函數用來丢棄輸入緩沖區中的字元,第一參數定義一個數,第二個參數定義一個字元變量。下面解釋一下函數是怎樣執行的:函數不停的從緩沖區中取一個字元,并判斷是不是_Delim,如果不是則丢棄并進行計數,當計數達到_Count退出,如果是則丢棄字元退出。例:cin.ignore(5, ‘a’); 函數将不斷從緩沖區中取一個字元丢棄,直到丢棄的字元數達到5或者讀取的字元為’a'。下面我們看個程式例子:

程式10:

cin.ignore(5, ‘a’);

測試一輸入:

c[enter]

程式結束。

【分析】程式開始時緩沖區是空的,cin.ignore()到緩沖區中取資料,沒有則請求從鍵盤輸入,每次從鍵盤輸入一個字元,如果不是’a'則丢棄,是以該測試中共輸入了5次,直到計數達到5。

測試二輸入:

a[enter]

【分析】前面兩個字元不是’a'丢棄且計數沒達到5,第三次輸入為’a', 丢棄該字元程式結束!

丢棄一個字元:

我們看看這個函數的預設值,第一個參數預設為1,第二個參數預設為EOF。是以cin.ignore()就是丢棄緩沖區中的第一個字元,這在程式中也是比較常用的!我們回過頭看看程式5,程式5中用cin.get()讀取字元,第一次讀取時用回車符結束,而get函數不丢棄回車符,是以回車符仍殘留在緩沖區中,導緻第二次讀取資料直接從緩沖區中取得回車符!這與我們最初的用以是不相符的,既然cin.get()不會自動丢棄輸入結束時的回車符,這裡我們學會了ignore()函數,我們就可以自己手動求其回車符啊!是以程式5可以這樣改動:

程式11:

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

【分析】這樣程式就正常了!

繼續閱讀