cin.fail(), cin.bad(), cin.good(), cin.clear(), cin.ignore()

     我們經常會看到程序中會出現 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
0

 

【注意】它們不是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:
#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可以這樣改動:
程序11:
#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');

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章