原因:程序會出現錯誤,尤其是不易察覺的錯誤。需要了解並解決這些錯誤。
通常,程序出現錯誤,都會強制退出,很難排除錯誤原因。
C語言的錯誤信息
1.函數返回值
* 通常,成功返回‘0’,失敗返回‘-1’.
* 返回值爲指針類型,成功返回非‘NULL’,失敗返回值‘NULL’。
shmat():MAP_INVALD(-1)
* 其他另類的返回值
size_t 'fread()'/'fwrite()'
2.全局變量‘errno’
異常提供一個錯誤專用通道。
優點:
1.不干擾正常的返回值。
2.必須處理異常。
``````
#include <iostream>
#include <sstream>
using namespace std;
int main(int argc,char* argv[]){
istringstream iss(argv[1]);
int a(0);
iss>>a;
iss = argv[2];
int b(0);
iss>>b;
cout<< a/b <<endl;
}
``````
>'try'-'throw'-'catch'
問題檢測與問題解決分離
* 拋出異常
``````
throw 表達式
``````
* 捕獲並處理異常
``````
try {
包含可能拋出異常的語句;
} catch (類型名[形參名]){
處理異常
}
``````
特點:
1.只要拋出異常,異常後的代碼不再執行。
2.異常的所拋出與經過的棧都會銷燬。
``````
try {
包含可能拋出異常的語句;
} catch (類型名1[形參名]){
處理異常
} catch (類型名2[形參名]){
處理異常
} catch (類型名3[形參名]){
處理異常
} catch (...){
處理異常
}
``````
* 異常捕獲具有類型匹配,只有相同的或者父類類型才能匹配到。
* 如果多個‘catch’都能接受相同異常,只有最前面的一個可以接收到。
* ‘catch(...)’只能放在所有異常捕獲的最後*
* 異常的接口聲明/異常規範
``````
返回值類型 函數() throw(異常列表);
``````
指定函數可以拋出何種異常,如果沒有'throw(異常列表)'默認可以拋出所有異常。
指定函數不拋出異常,異常列表爲空'throw()'。
>注意事項
1.如果拋出的異常一直沒有函數捕獲(catch),則會一直上傳到c++運行系統那裏,導致整個程序的終止。
2.一般在異常拋出後資源可以正常被釋放,但注意如果在類的構造函數中拋出異常,系統是不會調用它的析構函數的,處理方法是:如果在構造函數中要拋出異常,則在拋出前要記得刪除申請的資源。
3.異常處理僅僅通過類型而不是通過值來(switch-case)匹配的,所以catch塊的參數可以沒有參數名稱,只需要參數類型。
4.函數原型中的異常說明要與實現中的異常說明一致,否則容易引起異常衝突。
5.應該在throw語句後寫上異常對象時,throw先通過Copy構造函數構造一個新對象,再把該新對象傳遞給catch。
那麼當異常拋出後新對象如何釋放?
異常處理機制保證:異常拋出的新對象並非創建在函數棧上,而是創建在專用的異常棧上,因此它纔可以跨接多個函數而傳遞到上層,否則在棧清空的過程中就會被銷燬。所以從try到throw語句之間構造起來的對象的析構函數將被自動調用。但如果一直上溯到main函數後還沒有找到匹配的catch塊,那麼系統調用terminate()終止整個程序,這種情況下不能保證所以局部對象會被正確的銷燬。
6.catch塊的參數推薦採用地址傳遞而不是值傳遞,不僅可以提高效率,還可以利用對象的多態性。另外,派生類的異常捕獲要放到父類異常捕獲的前面,否則派生類的異常無法被捕獲。
7.編寫異常說明時,要確保派生類成員函數的異常說明和基類成員函數的異常說明一致,即派生類改寫的虛函數的異常說明至少要和對應的基類虛函數的異常說明相同,甚至更加嚴格,更特殊。
``````
#include <iostream>
using namespace std;
void test(){
cout << "before throw." <<endl;
throw -1;
cout << "after throw." <<endl;
}
int main(){
try {
test();
} catch(int a){
cout << "esception:" <<a <<endl;
}
}
``````
``````
#include <iostream>
using namespace std;
class Test{
public:
Test(){
cout << "Test Init" <<endl;
}
~Test(){
cout << "Test Destroy" <<endl;
}
};
int main(){
try{
Test t;
cout << "before throw." << endl;
throw -1;
cout << "after throw." << endl;
} catch(int a){
cout << "exception:" << a << endl;
}
}
``````
* 標準異常類
* ‘exception’派生
|:-|
|'logic_error'|邏輯錯誤,在程序運行前可以檢測出來|
|'runtime_error'|運行時錯誤,僅在程序運行中檢測到|
* 邏輯異常‘logic_error’派生
|異常類|作用|
|:-|
|'range_error'|違反後置條件|
|'bad_alloc'|存儲分配錯誤|
> 嘗試捕獲邏輯異常和運行時異常
* 自定義異常類
* 編碼流程
1.繼承異常類'exception'
2.實現接口‘what()’
* 代碼結構
``````
class 異常類:public exception {
public:
const char* what()const throw(){
return 信息字符串;
}
};
``````
構造函數、析構函數的異常處理。
* 構造函數可以拋出異常,此時不會調用析構函數,所以如果拋出了異常前,申請了資源,需要自己釋放。
* C++標準指明析構函數不能,也不應該拋出異常。
1.如果析構函數拋出異常,則異常點之後的程序不會執行,如果析構函數在異常點之後執行了某些必要的動作比如釋放某些資源,則這些動作不會執行,會造成諸如資源泄露的問題。
2.通常異常發生時,C++的機制會調用已經構造對象的析構函數來釋放資源,此時若析構函數本身也拋出異常,則前一個異常尚未處理,又有新的異常,會造成程序崩潰的問題。
``````
g++ -fno-exceptions
``````
在不同的編碼規範中,對是否使用異常存在爭議。