一、什么是異常處理
一句話:異常處理就是處理程序中的錯誤。
程序運行時常會碰到一些異常情況,例如:
1、做除法的時候除數為 0;
2、用戶輸入年齡時輸入了一個負數;
3、用 new 運算符動態分配空間時,空間不夠導致無法分配;
4、訪問數組元素時,下標越界;打開文件讀取時,文件不存在。
這些異常情況,如果不能發現並加以處理,很可能會導致程序崩潰。
二、異常處理機制
1、當發生異常,程序無法沿着正常的順序執行下去的時候,立即結束程序可能並不妥當。我們需要給程序提供另外一條可以安全退出的路徑,
在結束前做一些必要的工作,如將內存中的數據寫入文件、關閉打開的文件、釋放動態分配的內存空間等。
2、當發生異常的時候,程序馬上處理可能並不妥當(一個異常有多種處理方法,或者自己無法處理異常),需要將這個異常拋出給他的上級(直接調用者),
由上級決定如何處理。或者是自己不處理再轉交給它的上級去處理,一直可以轉交到最外層的main()函數
3、另外,異常的分散處理不利於代碼的維護,尤其是對於在不同地方發生的同一種異常,都要編寫相同的處理代碼也是一種不必要的重復和冗余。
如果能在發生各種異常時讓程序都執行到同一個地方,這個地方能夠對異常進行集中處理,則程序就會更容易編寫、維護。
在引入異常處理機制之前,異常的處理方式有兩種方法
1、使用整型的返回值標識錯誤;
2、使用errno宏(可以簡單的理解為一個全局整型變量)去記錄錯誤。當然C++中仍然是可以用這兩種方法的。
這兩種方法最大的缺陷就是會出現不一致問題。例如有些函數返回1表示成功,返回0表示出錯;而有些函數返回0表示成功,返回非0表示出錯。
還有一個缺點就是函數的返回值只有一個,你通過函數的返回值表示錯誤代碼,那么函數就不能返回其他的值。
鑒於上述原因,C++引入了異常處理機制
異常處理流程
C++ 異常處理涉及到三個關鍵字:try、catch、throw。
1、throw: 當問題出現時,程序會拋出一個異常。這是通過使用 throw 關鍵字來完成的。
2、try: try 塊中的代碼標識將被激活的特定異常。它后面通常跟着一個或多個 catch 塊。
3、catch: 在您想要處理問題的地方,通過異常處理程序捕獲異常。catch 關鍵字用於捕獲異常。
4、finally:關鍵字finally放在catch之后,如果異常沒有被catch捕獲,會使用關鍵字去清理釋放資源
如果有一個塊拋出一個異常,捕獲異常的方法會使用 try 和 catch 關鍵字。try 塊中放置可能拋出異常的代碼(判斷異常的類型),try 塊中的代碼被稱為保護代碼。
catch后面對應每個異常的處理方法。
以除法除0舉例。代碼如下所示:
#include <iostream> using namespace std; double division(int a, int b) { if (b == 0) { throw "Division by zero condition!"; } return (a / b); } int main() { int x = 50; int y = 0; double z = 0; //try\catch的使用和switch\case的使用類似 try { z = division(x, y); cout << z << endl; } catch (const char* msg) { cerr << msg << endl; } //finally{} return 0; }
三、幾個概念
1、棧展開
棧展開指的是:當異常拋出后,匹配catch的過程。
拋出異常時,將暫停當前函數的執行,開始查找匹配的catch子句。沿着函數的嵌套調用鏈向上查找,直到找到一個匹配的catch子句,或者找不到匹配的catch子句。
棧展開的時候,會通過析構函數或者是delete銷毀局部對象(從開始匹配位置到確認匹配這一段中間位置的資源會被釋放)
2、析構函數應該從不拋出異常。
如果析構函數中出現異常,那么就應該在析構函數內部將這個異常進行處理,而不是將異常拋出去。
為什么不應該?拋出異常的就是棧展開的過程,而棧展開會調用析構函數銷毀局部對象,這樣多次調用析構函數會導致程序崩潰(內存泄漏)
3、構造函數可以拋出異常
當構造函數內出現異常,可以選擇將異常拋出,在棧展開的過程調用析構函數釋放已申請的內存,也可以在內部將異常處理,手動調用delete釋放
4、catch捕獲所有異常
語法:在catch語句中,使用三個點(…)。即寫成:catch (…) 這里三個點是“通配符”,類似 可變長形式參數。