今天在開發程序中呼叫一個庫函式結果庫函式有throw操作,當前代碼沒有對throw進行捕獲操作,導致行程在main 函式中捕獲到例外導致行程crash,所以借此記錄下c++關于try,throw,catch的用法,
程式運行時常會碰到一些例外情況,例如:
- 做除法的時候除數為 0;
- 用戶輸入年齡時輸入了一個負數;
- 用 new 運算子動態分配空間時,空間不夠導致無法分配;
- 訪問陣列元素時,下標越界;打開檔案讀取時,檔案不存在,
這些例外情況,如果不能發現并加以處理,很可能會導致程式崩潰,
所謂“處理”,可以是給出錯誤提示資訊,然后讓程式沿一條不會出錯的路徑繼續執行;也可能是不得不結束程式,但在結束前做一些必要的作業,如將記憶體中的資料寫入檔案、關閉打開的檔案、釋放動態分配的記憶體空間等,
一發現例外情況就立即處理未必妥當,因為在一個函式執行程序中發生的例外,在有的情況下由該函式的呼叫者決定如何處理更加合適,尤其像庫函式這類提供給程式員呼叫,用以完成與具體應用無關的通用功能的函式,執行程序中貿然對例外進行處理,未必符合呼叫它的程式的需要,
此外,將例外分散在各處進行處理不利于代碼的維護,尤其是對于在不同地方發生的同一種例外,都要撰寫相同的處理代碼也是一種不必要的重復和冗余,如果能在發生各種例外時讓程式都執行到同一個地方,這個地方能夠對例外進行集中處理,則程式就會更容易撰寫、維護,
鑒于上述原因,c++ 引入了例外處理機制,其基本思想是:函式 A 在執行程序中發現例外時可以不加處理,而只是“拋出一個例外”給 A 的呼叫者,假定為函式 B,
拋出例外而不加處理會導致函式 A 立即中止,在這種情況下,函式 B 可以選擇捕獲 A 拋出的例外進行處理,也可以選擇置之不理,如果置之不理,這個例外就會被拋給 B 的呼叫者,以此類推,
如果一層層的函式都不處理例外,例外最侄訓被拋給最外層的 main 函式,main 函式應該處理例外,如果main函式也不處理例外,那么程式就會立即例外地中止,
C++例外處理基本語法
C++ 通過 throw 陳述句和 try...catch 陳述句實作對例外的處理,throw 陳述句的語法如下:
throw 運算式;
該陳述句拋出一個例外,例外是一個運算式,其值的型別可以是基本型別,也可以是類,
try...catch 陳述句的語法如下:
try {
陳述句組
}
catch(例外型別) {
例外處理代碼
}
...
catch(例外型別) {
例外處理代碼
}
catch 可以有多個,但至少要有一個,
不妨把 try 和其后{}中的內容稱作“try塊”,把 catch 和其后{}中的內容稱作“catch塊”,
try...catch 陳述句的執行程序是:
- 執行 try 塊中的陳述句,如果執行的程序中沒有例外拋出,那么執行完后就執行最后一個 catch 塊后面的陳述句,所有 catch 塊中的陳述句都不會被執行;
- 如果 try 塊執行的程序中拋出了例外,那么拋出例外后立即跳轉到第一個“例外型別”和拋出的例外型別匹配的 catch 塊中執行(稱作例外被該 catch 塊“捕獲”),執行完后再跳轉到最后一個 catch 塊后面繼續執行,
例如下面的程式:
1 #include <iostream> 2 using namespace std; 3 int main() 4 { 5 double m ,n; 6 cin >> m >> n; 7 try { 8 cout << "before dividing." << endl; 9 if( n == 0) 10 throw -1; //拋出int型別例外 11 else 12 cout << m / n << endl; 13 cout << "after dividing." << endl; 14 } 15 catch(double d) { 16 cout << "catch(double) " << d << endl; 17 } 18 catch(int e) { 19 cout << "catch(int) " << e << endl; 20 } 21 cout << "finished" << endl; 22 return 0; 23 }
程式的運行結果如下:
9 6↙ before dividing. 1.5 after dividing. finished
說明當 n 不為 0 時,try 塊中不會拋出例外,因此程式在 try 塊正常執行完后,越過所有的 catch 塊繼續執行,catch 塊一個也不會執行,
程式的運行結果也可能如下:
9 0↙ before dividing. catch\(int) -1 finished
當 n 為 0 時,try 塊中會拋出一個整型例外,拋出例外后,try 塊立即停止執行,該整型例外會被型別匹配的第一個 catch 塊捕獲,即進入 catch(int e) 塊執行,該 catch 塊執行完畢后,程式繼續往后執行,直到正常結束,
如果拋出的例外沒有被 catch 塊捕獲,例如,將catch(int e),改為catch(char e),當輸入的 n 為 0 時,拋出的整型例外就沒有 catch 塊能捕獲,這個例外也就得不到處理,那么程式就會立即中止,try...catch 后面的內容都不會被執行,
能夠捕獲任何例外的 catch 陳述句
如果希望不論拋出哪種型別的例外都能捕獲,可以撰寫如下 catch 塊:
catch(...) { ... }
這樣的 catch 塊能夠捕獲任何還沒有被捕獲的例外,例如下面的程式:
1 #include <iostream> 2 using namespace std; 3 int main() 4 { 5 double m, n; 6 cin >> m >> n; 7 try { 8 cout << "before dividing." << endl; 9 if (n == 0) 10 throw - 1; //拋出整型例外 11 else if (m == 0) 12 throw - 1.0; //拋出 double 型例外 13 else 14 cout << m / n << endl; 15 cout << "after dividing." << endl; 16 } 17 catch (double d) { 18 cout << "catch (double)" << d << endl; 19 } 20 catch (...) { 21 cout << "catch (...)" << endl; 22 } 23 cout << "finished" << endl; 24 return 0; 25 }
程式的運行結果如下:
9 0↙ before dividing. catch (...) finished
當 n 為 0 時,拋出的整型例外被catchy(...)捕獲,
程式的運行結果也可能如下:
0 6↙ before dividing. catch (double) -1 finished
當 m 為 0 時,拋出一個 double 型別的例外,雖然catch (double)和catch(...)都能匹配該例外,但是catch(double)是第一個能匹配的 catch 塊,因此會執行它,而不會執行catch(...)塊,
由于catch(...)能匹配任何型別的例外,它后面的 catch 塊實際上就不起作用,因此不要將它寫在其他 catch 塊前面,
例外的再拋出
如果一個函式在執行程序中拋出的例外在本函式內就被 catch 塊捕獲并處理,那么該例外就不會拋給這個函式的呼叫者(也稱為“上一層的函式”);如果例外在本函式中沒有被處理,則它就會被拋給上一層的函式,
例如下面的程式:
1 #include <iostream> 2 #include <string> 3 using namespace std; 4 class CException 5 { 6 public: 7 string msg; 8 CException(string s) : msg(s) {} 9 }; 10 double Devide(double x, double y) 11 { 12 if (y == 0) 13 throw CException("devided by zero"); 14 cout << "in Devide" << endl; 15 return x / y; 16 } 17 int CountTax(int salary) 18 { 19 try { 20 if (salary < 0) 21 throw - 1; 22 cout << "counting tax" << endl; 23 } 24 catch (int) { 25 cout << "salary < 0" << endl; 26 } 27 cout << "tax counted" << endl; 28 return salary * 0.15; 29 } 30 int main() 31 { 32 double f = 1.2; 33 try { 34 CountTax(-1); 35 f = Devide(3, 0); 36 cout << "end of try block" << endl; 37 } 38 catch (CException e) { 39 cout << e.msg << endl; 40 } 41 cout << "f = " << f << endl; 42 cout << "finished" << endl; 43 return 0; 44 }
程式的輸出結果如下:
salary < 0 tax counted devided by zero f=1.2 finished
CountTa 函式拋出例外后自行處理,這個例外就不會繼續被拋給呼叫者,即 main 函式,因此在 main 函式的 try 塊中,CountTax 之后的陳述句還能正常執行,即會執行f = Devide(3, 0);,
第 35 行,Devide 函式拋出了例外卻不處理,該例外就會被拋給 Devide 函式的呼叫者,即 main 函式,拋出此例外后,Devide 函式立即結束,第 14 行不會被執行,函式也不會回傳一個值,這從第 35 行 f 的值不會被修改可以看出,
Devide 函式中拋出的例外被 main 函式中型別匹配的 catch 塊捕獲,第 38 行中的 e 物件是用復制建構式初始化的,
如果拋出的例外是派生類的物件,而 catch 塊的例外型別是基類,那么這兩者也能夠匹配,因為派生類物件也是基類物件,
雖然函式也可以通過回傳值或者傳參考的引數通知呼叫者發生了例外,但采用這種方式的話,每次呼叫函式時都要判斷是否發生了例外,這在函式被多處呼叫時比較麻煩,有了例外處理機制,可以將多處函式呼叫都寫在一個 try 塊中,任何一處呼叫發生例外都會被匹配的 catch 塊捕獲并處理,也就不需要每次呼叫后都判斷是否發生了例外,
有時,雖然在函式中對例外進行了處理,但是還是希望能夠通知呼叫者,以便讓呼叫者知道發生了例外,從而可以作進一步的處理,在 catch 塊中拋出例外可以滿足這種需要,例如:
1 #include <iostream> 2 #include <string> 3 using namespace std; 4 int CountTax(int salary) 5 { 6 try { 7 if( salary < 0 ) 8 throw string("zero salary"); 9 cout << "counting tax" << endl; 10 11 } 12 catch (string s ) { 13 cout << "CountTax error : " << s << endl; 14 throw; //繼續拋出捕獲的例外 15 } 16 cout << "tax counted" << endl; 17 return salary * 0.15; 18 } 19 int main() 20 { 21 double f = 1.2; 22 try { 23 CountTax(-1); 24 cout << "end of try block" << endl; 25 } 26 catch(string s) { 27 cout << s << endl; 28 } 29 cout << "finished" << endl; 30 return 0; 31 }
程式的輸出結果如下:
CountTax error:zero salary
zero salary
finished
第 14 行的throw;沒有指明拋出什么樣的例外,因此拋出的就是 catch 塊捕獲到的例外,即 string("zero salary"),這個例外會被 main 函式中的 catch 塊捕獲,
函式的例外宣告串列
為了增強程式的可讀性和可維護性,使程式員在使用一個函式時就能看出這個函式可能會拋出哪些例外,C++ 允許在函式宣告和定義時,加上它所能拋出的例外的串列,具體寫法如下:
void func() throw (int, double, A, B, C);
或
void func() throw (int, double, A, B, C){...}
上面的寫法表明 func 可能拋出 int 型、double 型以及 A、B、C 三種型別的例外,例外宣告串列可以在函式宣告時寫,也可以在函式定義時寫,如果兩處都寫,則兩處應一致,
如果例外宣告串列如下撰寫:
void func() throw ();
則說明 func 函式不會拋出任何例外,
一個函式如果不交待能拋出哪些型別的例外,就可以拋出任何型別的例外,
函式如果拋出了其例外宣告串列中沒有的例外,在編譯時不會引發錯誤,但在運行時, Dev C++ 編譯出來的程式會出錯;用 Visual Studio 2010 編譯出來的程式則不會出錯,例外宣告串列不起實際作用,
C++標準例外類
C++ 標準庫中有一些類代表例外,這些類都是從 exception 類派生而來的,常用的幾個例外類如圖 1 所示,

圖1:常用的例外類
bad_typeid、bad_cast、bad_alloc、ios_base::failure、out_of_range 都是 exception 類的派生類,C++ 程式在碰到某些例外時,即使程式中沒有寫 throw 陳述句,也會自動拋出上述例外類的物件,這些例外類還都有名為 what 的成員函式,回傳字串形式的例外描述資訊,使用這些例外類需要包含頭檔案 stdexcept,
下面分別介紹以上幾個例外類,本節程式的輸出以 Visual Studio 2010為準,Dev C++ 編譯的程式輸出有所不同,
1) bad_typeid
使用 typeid 運算子時,如果其運算元是一個多型類的指標,而該指標的值為 NULL,則會拋出此例外,
2) bad_cast
在用 dynamic_cast 進行從多型基類物件(或參考)到派生類的參考的強制型別轉換時,如果轉換是不安全的,則會拋出此例外,程式示例如下:
1 #include <iostream> 2 #include <stdexcept> 3 using namespace std; 4 class Base 5 { 6 virtual void func() {} 7 }; 8 class Derived : public Base 9 { 10 public: 11 void Print() {} 12 }; 13 void PrintObj(Base & b) 14 { 15 try { 16 Derived & rd = dynamic_cast <Derived &>(b); 17 //此轉換若不安全,會拋出 bad_cast 例外 18 rd.Print(); 19 } 20 catch (bad_cast & e) { 21 cerr << e.what() << endl; 22 } 23 } 24 int main() 25 { 26 Base b; 27 PrintObj(b); 28 return 0; 29 }
程式的輸出結果如下:
Bad dynamic_cast!
在 PrintObj 函式中,通過 dynamic_cast 檢測 b 是否參考的是一個 Derived 物件,如果是,就呼叫其 Print 成員函式;如果不是,就拋出例外,不會呼叫 Derived::Print,
3) bad_alloc
在用 new 運算子進行動態記憶體分配時,如果沒有足夠的記憶體,則會引發此例外,程式示例如下:
1 #include <iostream> 2 #include <stdexcept> 3 using namespace std; 4 int main() 5 { 6 try { 7 char * p = new char[0x7fffffff]; //無法分配這么多空間,會拋出例外 8 } 9 catch (bad_alloc & e) { 10 cerr << e.what() << endl; 11 } 12 return 0; 13 }
程式的輸出結果如下:
bad allocation
ios_base::failure
在默認狀態下,輸入輸出流物件不會拋出此例外,如果用流物件的 exceptions 成員函式設定了一些標志位,則在出現打開檔案出錯、讀到輸入流的檔案尾等情況時會拋出此例外,此處不再贅述,
4) out_of_range
用 vector 或 string 的 at 成員函式根據下標訪問元素時,如果下標越界,則會拋出此例外,例如:
1 #include <iostream> 2 #include <stdexcept> 3 #include <vector> 4 #include <string> 5 using namespace std; 6 int main() 7 { 8 vector<int> v(10); 9 try { 10 v.at(100) = 100; //拋出 out_of_range 例外 11 } 12 catch (out_of_range & e) { 13 cerr << e.what() << endl; 14 } 15 string s = "hello"; 16 try { 17 char c = s.at(100); //拋出 out_of_range 例外 18 } 19 catch (out_of_range & e) { 20 cerr << e.what() << endl; 21 } 22 return 0; 23 }
程式的輸出結果如下:
invalid vector <T> subscript invalid string position
如果將v.at(100)換成v[100],將s.at(100)換成s[100],程式就不會引發例外(但可能導致程式崩潰),因為 at 成員函式會檢測下標越界并拋出例外,而 operator[] 則不會,operator [] 相比 at 的好處就是不用判斷下標是否越界,因此執行速度更快,
轉自:http://c.biancheng.net/view/422.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/95048.html
標籤:C++
上一篇:MFC底層視窗實作
