一、程式錯誤
編譯錯誤,即語法錯誤。程式就無法被產生運行代碼。
執行階段錯誤
不可預料的邏輯錯誤
可以預料的運行異常
例如:
動態分配空間時可能不會成功
開啟檔案可能會失敗
除法運算時分母可能為0
整數相乘可能溢出
數組越界……
二、異常
(一)、異常文法
throw 運算式;
try
{
//try語句塊
}
catch(類型1 參數1)
{
//針對類型1的異常處理
}
catch (類型2 參數2)
{
//針對類型2的異常處理
}
…
catch (類型n 參數n)
{
//針對類型n的異常處理
}
(二)、異常拋出
可以拋出內建類型異常也可以拋出自訂類型異常
throw拋出一個類對象會調用拷貝建構函式
異常發生之前建立的局部對象被銷毀,這一過程稱為棧展開
(三)、異常捕獲
一個異常處理器一般只捕捉一種類型的異常
異常處理器的參數類型和拋出異常的類型相同
…表示可以捕獲任何異常
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
|
|
#include <iostream> #include <string> using namespace std;class MyException { public: MyException(const char *message) : message_(message) { cout << "MyException ..." << endl; } MyException(const MyException &other) : message_(other.message_) { cout << "Copy MyException ..." << endl; } ~MyException() { cout << "~MyException" << endl; } const char *what() const { return message_.c_str(); } private: string message_; }; double Divide(double a, double b) { if (b == 0.0) { MyException e("division by zero"); throw e; /*throw MyException("division by zero");*/ //throw 1; } else return a / b; } int main(void) { try { cout << Divide(5.0, 0.0) << endl; } catch (MyException &e) { cout << e.what() << endl; } //catch (int) //{ // cout<<"int exception ..."<<endl; //} catch (double) { cout << "double exception ..." << endl; } catch (...) { cout << "catch a exception ..." << endl; } } |
程式自訂一個異常類型MyException,從輸出可以看出,Divide函數內先構造一個MyException對象e,調用建構函式,因為e是局部對象需要被析構,在析構前先調用拷貝建構函式構造另一個對象,這個對象將被catch 引用,最後這個對象在catch末尾也將被析構。
假設沒有構造局部對象,直接throw , 如 throw MyException("division by zero"); 那麼將不會調用拷貝建構函式,只存在一個對象,在catch的末尾被析構。
假設throw 1; 而沒有對應的catch (int) ,即使存在catch (double) 也捕獲不到,不會做類型轉換,此時會由catch (...) 捕獲到,...表示可以捕獲任何異常。
(四)、異常傳播
1、try塊可以嵌套
2、程式按順序尋找匹配的異常處理器,拋出的異常將被第一個類型符合的異常處理器捕獲
如果內層try塊後面沒有找到合適的異常處理器,該異常向外傳播,到外層try塊後面的catch塊中尋找
3、沒有被捕獲的異常將調用terminate函數,terminate函數預設調用abort終止程式的執行
可以使用set_terminate函數指定terminate函數在調用abort之前將調用的函數
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
|
|
void MyTerminate() { cout << "MyTerminate ..." << endl; }int main(void) { set_terminate(MyTerminate); try { try { throw MyException("test exception"); } catch (int) { cout << "Inner ..." << endl; cout << "catch a int exception" << endl; } //catch (MyException& e) //{ // cout<<"Inner ..."<<endl; // cout<<e.what()<<endl; // throw e; //} } catch (int) { cout << "Outer ..." << endl; cout << "catch a int exception" << endl; } catch (MyException &e) { cout << "Outer ..." << endl; cout << e.what() << endl; } } |
其中MyException類如上,程式中將內層的catch (MyException& e) 屏蔽了,所以由外層的catch (MyException& e) 捕獲,假設將兩個都注釋掉的話,因為沒有找到合適的catch, 那麼terminate 函數會被調用,並且由於事先set_terminate
函數設定了abort調用之前被調用的函數MyTerminate,故先輸出MyTerminate ...然後程式被終止。
三、棧展開
沿著嵌套調用連結向上尋找,直至為異常找到一個catch子句。這個過程稱之為棧展開。
為局部對象調用解構函式
解構函式應該從不拋出異常
棧展開期間會執行解構函式,在執行解構函式的時候,已經引發的異常但還沒處理,如果這個過程中解構函式又拋出新的異常,將會調用標準庫的terminate函數。
異常與建構函式
建構函式中可以拋出異常。如果在建構函式函數中拋出異常,則可能該對象只是部分被構造。即使對象只是被部分構造,也要保證銷毀已構造的成員。(如果成員是指標p,因為解構函式不會被調用,故不會執行一般的delete p; 很可能造成記憶體流失)
參考:
C++ primer 第四版
Effective C++ 3rd
C++編程規範