電腦遊戲製作開發設計論壇 首頁 電腦遊戲製作開發設計論壇
任何可以在PC上跑的遊戲都可以討論,主要以遊戲之製作開發為主軸,希望讓台灣的遊戲人有個討論、交流、教學、經驗傳承的園地
 
 常見問題常見問題   搜尋搜尋   會員列表會員列表   會員群組會員群組   會員註冊會員註冊 
 個人資料個人資料   登入檢查您的私人訊息登入檢查您的私人訊息   登入登入 

Google
[C++疑難雜症]cin錯誤處理較佳版和limits數值極限

 
發表新主題   回覆主題    電腦遊戲製作開發設計論壇 首頁 -> 遊戲程式初級班:語法及基礎概念
上一篇主題 :: 下一篇主題  
發表人 內容
yag
Site Admin


註冊時間: 2007-05-02
文章: 688

2673.35 果凍幣

發表發表於: 2007-6-22, AM 10:37 星期五    文章主題: [C++疑難雜症]cin錯誤處理較佳版和limits數值極限 引言回覆

嗯...這篇我曾經花了一個多小時打到快完...結果被我一時手賤按到F5而毀掉了...今天總算重拾心情再寫一遍O_O"

首先嘛,要先感謝snowmhiau的幫忙測試,她都會主動細心地幫我測試我的範例是否有什麼bug,感謝她的熱心幫忙。

接著請大家看到[8]Switch的範例,在此範例中輸入賭注的地方有個cin的錯誤處理:
代碼:
if( cin.fail() )
{
   cin.clear();
   char ch1;
   cin >> ch1;
   system( "cls" );
   cout << "錯誤的輸入格式!" << endl;
   goto Loop;
}

這就是我在[疑難雜症]cin無限迴圈錯誤中所提過的處理方式。

不過這種處理方式雖然可以避免無限迴圈,但在[8]Switch的範例中卻會造成兩種輸入上的不合理現象:
一、當輸入了一個超過int值域的賭注金額時,必須再隨便輸入一個字元才會繼續下去,但因為並沒有任何的提示出現,所以會讓人誤以為程式當了。
二、當輸入像1.2.3.4.5.6.7.8.9.10.11.12這樣一長串奇怪的「.」+數字時,程式會自動連下12次賭注,賭注金額分別從1~12。

而造成這種現象的,就是上面那種錯誤處理方式,我們為了避免無限迴圈,所以在if( cin.fail() )中宣告了一個char ch1;,然後再把會造成無限迴圈的字元用cin >> ch1;讀出來,這在處理要求整數輸入時卻輸入浮點數的錯誤很有用,像是2.3,它會把「.」給讀到ch1中。

但是當我們輸入的是一個超過極限的數字時,cin產生了錯誤,卻不會留住這個過大的數字,因此我們的cin >> ch1;就變成了畫蛇添足之舉,程式到此會在沒有提示訊息的情況下停住,直到我們隨便輸入一個字元後才繼續往下執行。

而輸入像1.2.3.4.5這樣的字串,程式也會讀入1後把「.」丟給ch1,再讀入2後把「.」丟給ch1,一直讀下去,因為我們並沒有將此次輸入的後面部份全部給丟掉才會造成這種狀況。

那麼,為了處理這兩個問題,我到處查資料然後測試,花了兩個多小時的時間。一開始我是想使用cin.seekg(),這個成員函式可以讓我們移動輸入緩衝區裡的指標,我的想法是,只要將此指標在有錯誤時移到最後面,就可以省略掉錯誤出現後所有跟此錯誤同時輸入的資料,不過我在這嘗試了很久,始終不得其法,在它的第二個參數ios_base::seekdir _Way上我用ios_base::end代入,要嘛出現錯誤要嘛得到的結果跟預期中的不同。

之後我又試過兩、三個看起來相關的成員函式,也是都達不到預期的效果,一直到我嘗試使用cin.ignore(),這個函式可以讓我們忽略掉「一個輸入」,使用這個函式可以很輕易地解決掉我們的第一個問題:過大數字。不過第二個問題依然存在,我們只要輸入1.2.3.4.5這樣的字串,cin會自動把它分解成9個輸入(5個數字+4個「.」),而cin.ignore()只會幫我們忽略掉一個輸入,也就是每次遇到「.」時忽略掉,後面那個輸入就又變正常的,所以程式會自動下5次的賭注。

cin.ignore()實際上有兩個參數,第一個參數讓我們選擇要忽略掉的數量,而第二個參數讓我們選擇忽略到何為止,此二參數都有預設值,第一參數預設為1第二參數預設為EOF

EOF的意思是End Of File,也就是檔案結尾的意思,這在我們讀檔時會讀到,不過在一般的輸入中我們必須按下Ctrl + Z(畫面上會出現^Z)才能代表EOF。換句話說,假設我在錯誤處理中寫了cin.ignore(10);,那麼因為一般輸入我們是按Enter來執行,並不會用到^Z,所以我們在一次的錯誤之後,會接連把之後9個不知道是正確還是錯誤的輸入全部忽略掉,而且還不會有提示出現,就像我們當初的第一種特殊情況一樣,程式直接停在那。

這樣當然不是我們想要的結果,我們會希望ignore()只處理到我們按下Enter之前的所有輸入,因此我們必須將ignore()的第二個參數改成'\n',這是代表換行字元,也就是Enter的意義。

這樣我們就得到了cin.ignore( 10, '\n' );,不過這樣只能讓我們處理到1.2.3.4.5.6這樣的輸入,再多的話還是會自動輸入賭注,可想而知我們要從第一參數著手,但該設多少才是最好的呢?

這個時候就是我們#include <limits>的時候了,這個標頭檔裡有一個樣版(Template)類別(*註1),它可以讓我們得到各種資料型態的最大值跟最小值,其用法如下:
代碼:
numeric_limits<資料型態>::max()   // 可得到最大值
numeric_limits<資料型態>::min()   // 可得到最小值

接著我查到了cin的緩衝區的資料型態名稱為streamsize,答案就呼之欲出了,叮咚~cin.ignore( numeric_lmits<streamsize>::max(), '\n' );就是最佳解囉~

這行指令可以讓我們將用Enter輸入的一整行字串在遇到錯誤時直接忽略掉直到緩衝區最大值的數量,也就是不管你輸入了多少,通通都忽略。
這麼一來,「.」造成無限迴圈解決了,數字過大時也不會停頓要求輸入任意字元了,用1.2.3.4.5.6.7.8.9.10這種手段也不會造成多次下注了(只有最前面的1會造成一次下注),問題就沒了。

所以較佳的cin錯誤處理方式如下:
代碼:
if( cin.fail() )
{
   cin.clear();
   cin.ignore( numeric_limits<streamsize>::max(), '\n' );
   cout << "錯誤的輸入格式!" << endl;
}

(*註1)所謂的樣版(Template),是泛型程式設計(Generic Programming)當中很重要的一種構件,而所謂的泛型程式設計,是指型別參數化的意思,什麼是型別參數化?講白話一點就是「寫一段程式碼,可以讓各種不同的資料型態都能使用它」,比如說,我們要寫個比大小的函式:
代碼:
int Bigger( int a, int b )
{
   if( a > b )
      return a;
   else if( b > a )
      return b;
   else
      return 0;
}

上面這個函式可以讓我們傳int型態的兩個變數a跟b進去,然後回傳較大的一個出來,如果兩個相等,那就回傳0。
有注意到什麼問題嗎?這樣說吧,當我們想要比較2.1跟2.3哪個比較大時怎麼辦?2.1跟2.3不是float就是double的型態(以常數來說預設為double),這樣就沒辦法傳到上面這個只接受int的函式裡去了,這是因為我們把「型別」給固定住了,而解決的方式就被稱為泛型程式設計,我們會把型別也當成參數一起傳進去,具體的寫法我先不介紹,但呼叫的時候大致會像這樣:Bigger<float>( 2.1, 2.3 );,應該可以了解吧?我們把float當成了參數傳進去,那麼這個Bigger樣版就可以接受2.1跟2.3了,當然int版本就用Bigger<int>( 2, 3 );就好了。
樣版在一般C++教學的書籍裡都是放在最後一、兩個章節的,所以這個我們會在把類別教完後才詳細介紹,目前就先講到此為止,只是讓大家有個概念。

因為numeric_limits<>是一個很有用的樣版,所以在此多講解一個long long的範例:
代碼:
#include <iostream>
#include <limits>

using namespace std;

int main()
{
   long long a;

   cout << sizeof( a ) << endl;
   cout << numeric_limits<long long>::max() << endl;
   cout << numeric_limits<long long>::min() << endl;

   cin.get();

   return 0;
}

要使用numeric_limits<>必定要先#include <lmits>不要忘了,還有注意是lmits(有複數s)。那麼以上的範例應該很好理解,long long是個用8 bytes存整數的資料型態,我們在[10]函式、變數範疇和常態變數的範例中有用到。

上面的範例執行後,會有以下的輸出:
代碼:
8
9223372036854775807
-9223372036854775808

第一個是代表它佔了8 bytes的記憶體,第二個是代表它可以存的最大值,第三個是代表它可以存的最小值,而範例中那行cin.get()會要求我們按任意一個鍵繼續,不過跟system( "pause" );不同的是,cin.get()並不會有任何提示訊息出現

這個樣版應該滿實用的,各位可以自行試試char、short、unsigned short、int、unsigned int、long、unsigned long、float以及double等等基本資料型態的值域為何。
回頂端
檢視會員個人資料 發送私人訊息 發送電子郵件
從之前的文章開始顯示:   
發表新主題   回覆主題    電腦遊戲製作開發設計論壇 首頁 -> 遊戲程式初級班:語法及基礎概念 所有的時間均為 台灣時間 (GMT + 8 小時)
1頁(共1頁)

 
前往:  
無法 在這個版面發表文章
無法 在這個版面回覆文章
無法 在這個版面編輯文章
無法 在這個版面刪除文章
無法 在這個版面進行投票
可以 在這個版面附加檔案
可以 在這個版面下載檔案


Powered by phpBB © 2001, 2005 phpBB Group
正體中文語系由 phpbb-tw 維護製作