C++学习笔记:(十)异常
目錄
10.異常處理
10.1異常處理概述
10.2拋出異常
10.3異常捕獲
10.4構造函數、析構函數與異常處理
10.5異常匹配
10.6標準異常及層次結構層次結構。
10.異常處理
C++具有強大的擴展能力,同時也大大增加了產生錯誤的可能性。在編程時,不能忽略異常處理。處理異常的方法多種多樣。錯誤處理代碼分布在整個系統代碼中,在任何可能出錯的地方都進行異常處理,閱讀代碼時可以直接看到異常處理的情況,但是引起的代碼膨脹將不可避免地使程序閱讀困難。
來看這樣的一個問題,兩個數的調和平均數的定義是:這兩個數字倒數的平均值的倒數,因此表達式為:2.0*x*y/(x+y)。如果y是x的負值,則上述公式將出現除數為零的情況。對于這種問題,處理辦法之一是,如果其中一個參數是另一個參數的負值,則調用abort()函數。abort()函數的原型位于頭文件cstdlib(或stdlib.h)中,其典型實現是向標準錯誤流(即cerr使用的錯誤流)發送消息abnormal program termination(程序異常終止),然后終止程序。它還返回一個值,告訴操作系統(如果程序是由另一個程序調用的,則告訴父進程),處理失敗。
abort()函數實例:
#include <iostream> #include <cstdlib> using namespace std; double hmean(double a, double b);int main() {double x, y, z;cout << "Enter two numbers:";while(cin >> x >> y){z = hmean(x, y);cout << "Harmonic mean of" << x << " and " << y << " is " << z <<endl;cout << "Enter next set of numbers <q to quit>:"; }cout << "End!\n";return 0; }double hmean(double a, double b) {if(a == -b){cout << "untenable arguments to hmean()\n";abort();}return 2.0*a*b/(a+b); }注意,在hmean()中調用abort()函數將直接終止程序,而不是先返回到main()。一般而言,顯示的程序異常中斷消息隨編譯器而異。為了避免異常終止,程序應該在調用hmean()函數之前檢查x和y的值。
一種比異常終止更靈活的方法是,使用函數返回值來指出問題。例如,ostream類的get(void)成員通常返回下一個輸入字符的ASCII碼,但到達文件尾時,將返回特殊值EOF。對hmean()來說,這種方法不管用。任何數值都是有效的返回值,因此不存在可用于指出問題的特殊值。在這種情況下,可使用指針參數或引用參數來將值返回給調用程序,并使用函數的返回值來指出成功還是失敗。istream族重載>>運算符使用了這種技術的變體。通過告知調用程序是成功還是失敗,使得程序可以采取除異常終止程序之外的其他措施。
程序實例:
#include <iostream> #include <cfloat> using namespace std;bool hmean(double a, double b, double *ans);int main() {double x, y, z;cout << "Enter two numbers:";while (cin >> x >> y){if(hmean(x, y, &z)){cout << "Harmonic mean of " << x << " and " << y << " is " << z <<endl;}else cout << "One value should not be the negative " << "of the other - try again.\n";cout << "Enter next set of numbers <q to quit>: ";}cout << "End!\n";return 0; }bool hmean(double a, double b, double *ans) {if(a == -b){*ans = DBL_MAX;return false;}else{*ans = 2.0*a*b/(a+b);return true;} }上例將hmean()的返回值重定義為bool,讓返回值告訴我們是成功還是失敗,第三個參數是用于提供答案(對內置類型的參數,用指針可以明顯看出是哪個參數用于提供答案)。
?
10.1異常處理概述
C++的異常處理是一種允許兩個獨立開發的程序組件在程序執行期間遇到程序異常時,相互通信的機制。具有以下特點:
(1)異常處理程序的編寫不再繁瑣。在錯誤有可能出現處寫一些代碼,并在后面的單獨節中加入異常處理程序。如果程序中多次調用一個函數,在程序中加入一個函數異常處理程序即可。
(2)異常發生不會被忽略。如果被調用函數需要發送一條異常處理信息給調用函數,它可向調用函數發送一描述異常處理信息的對象。如果調用函數沒有捕捉和處理該錯誤信號,在后續時刻該調用函數將繼續發送描述異常信息的對象,直到異常信息被捕捉和處理為止。
異常處理被用來處理同步錯誤,如除數為0、數組下標越界、運算溢出和無效函數參數等。異常處理通常用于發現錯誤的部分與處理錯誤的部分不在同一范圍時。與用戶進行交互式對話的程序不能用異常處理來處理輸入錯誤。異常處理特別適合用于程序無法恢復但又需要提供有序清理,使得程序可以正常結束的情況。異常處理不僅提供了程序的容錯性,還提供了各種捕獲異常的方法,如根據類型捕獲異常,或者指定捕獲任何類型的異常。
?
10.2拋出異常
如果程序發生異常情況,而在當前的上下文環境中獲取異常處理的足夠信息,可以創建一個包含出錯信息的對象并將此對象拋出當前的上下文環境,將出錯信息發送到更大的上下文環境中,稱為異常拋出。
拋出異常語法如下:
throw ourerror("some error happened");其中ourerror是一個普通的自定義類。如有異常拋出,可以使用任意類型變量作為參數。一般情況下,為清晰地區分異常信息,創建一個新類用于異常拋出。當異常發生時,通過throw調用構造函數創建一個自定義的對象ourerr,此對象正是throw函數的返回值,通常這個對象不是函數設計的正常返回值類型;且異常拋出的返回點不同于正常函數調用返回點。例如,在函數f()中拋出異常:
void f() {throw int(5); }可以根據要求拋出不同類型的異常對象。為能清晰地區分不同類型異常,可根據錯誤類型設計不同類型的對象。
?
10.3異常捕獲
10.3.1 異常處理語法
如果函數內拋出一個異常(或在調用函數時拋出一個異常),則在異常拋出時系統會自動退出所在函數的執行。如不想在異常拋出時退出函數,可在函數內創建一個特殊塊用于測試各種錯誤。測試塊作為普通作用域,由關鍵字try引導,異常拋出后,由catch引導的異常處理模塊應能接受任何類型的異常。在try之后,根據異常的不同情況,相應的處理方法由關鍵字catch引導。語法如下:
try{//可能發生錯誤的代碼 }catch(type1 t1){//第一種類型異常處理 }catch(type2 t2){//第二種類型異常處理 } ......//其他類型異常處理異常處理部分必須直接放在測試塊之后。每一個catch語句相當于以特殊類型為參數的函數(如類型type1、type2等)。如果異常拋出給出的異常類型足以判斷如何進行異常處理,則異常處理器catch中的參數可以省略。
對try部分拋出的異常,系統將從前到后逐個與catch后所給出的異常類型相匹配。如果匹配成功,則進入相應的處理部分執行異常處理程序。
異常處理的執行流程如下:
(1)程序進入到try塊,執行try塊內的代碼。
(2)如果在try塊內沒有發生異常,則直接轉到所有catch塊后的第一條語句執行下去。
(3)如果發生異常,則根據throw拋出的異常對象類型來匹配一個catch語句(此catch能處理此種類型的異常,即catch后的參數類型與throw拋出異常對象類型一致)。如果找到類型匹配的catch語句,進行捕獲,其參數被初始化為指向異常對象,執行相應catch內的語句模塊;如果找不到匹配類型的catch語句,系統函數terminate被調用,終止程序。
需要注意的是,通常在try塊中,可能有已經分配但尚未釋放的資源,如果可能,catch處理程序應該釋放這些資源。例如,catch處理程序刪除通過new分配的空間,關閉拋出異常的try塊中打開的文件。對于catch塊來說,處理錯誤之后可以讓程序繼續運行,也可以終止程序。
#include <iostream> using namespace std;class Divdebyzero {const char* message;public:Divdebyzero():message("divided by zero"){}const char* what(){return message;} };double testdiv(int num1, int num2) {if(num2 == 0){throw Divdebyzero();}return (double)num1/num2; }int main() {int num1,num2;double res;cout << "please input two integers:";while(cin >> num1 >> num2){try{res = testdiv(num1, num2);cout << "the res is :" << res <<endl;}catch(Divdebyzero ex){cout << "error " << ex.what() <<"\n";break;}cout << "\nplease input two intergers:";}return 0; }?
10.3.2異常接口聲明
C++提供了異常接口聲明語法,利用它可以清晰的告訴使用者異常拋出的類型。異常接口聲明再次使用了關鍵字throw,語法如下:
void f() throw(A,B,C,D); ?//此函數只能拋出A,B,C,D及其子類型異常。傳統函數:void f();意味著能拋出任何一種異常。void f() throw();表明函數不會有異常拋出。
?
10.3.3捕獲所有異常
C++中可以聲明:
catch(......){}來捕獲所有類型的異常。在一些情況下,可能無法處理有關異常信息,可以通過不加參數的throw來重新拋出異常,使得異常進入更高層次的上下文環境。語法如下:
catch(.....){cout << "一個異常被拋出!";throw; }throw只能出現在catch子句的復合語句中,且被拋出的異常就是原來的異常對象。
?
10.3.4未捕獲異常的處理
如果任意層的異常處理器都沒有捕獲到異常(沒有指定相應的catch塊),稱為”未捕獲異?!?。系統的特殊函數terminate()將自動調用,該函數通過調用abort()函數來終止程序的執行。
#include <iostream> using namespace std;class Bummer{}; class Killer{}; void foo() {int error = 1;if(error){cout << "throwing Killer" <<endl;throw Killer();} }int main() {try{cout << "calling foo()" <<endl;foo();}catch(Bummer){cout << "catching Bummer" <<endl;}cout << "finished" <<endl;return 0; }在主函數調用函數foo()是拋出異常類型Killer,而主函數中的異常處理塊catch只能處理Bummer類型的異常,所以發生了未捕獲異常的情況,通過調用系統的特殊函數terminate()終止程序。
?
10.4構造函數、析構函數與異常處理
異常處理部分的常見錯誤在于異常拋出時,對象沒有被正確清除。雖然C++的異常處理器可以保證當離開一個作用域時,該作用域中所有結構完整的對象的析構函數都能被調用,以清除這些對象,但是當對象的構造函數不完整時其析構函數將不被調用。
構造函數中發生異常后,異常處理遵從以下規則:
(1)如果對象有成員函數,且如果在外層對象構造完成之前有異常拋出,則在發生異常之前,執行構造成員對象的析構函數。
(2)如果異常發生時,對象數組被部分構造,則只調用已構造的數組元素的析構函數。
(3)異??梢蕴^通常釋放資源的代碼,從而造成資源泄漏。解決辦法是,請求資源時初始化一個局部對象,發生異常時,調用析構函數并釋放資源。
(4)要捕捉析構函數中的異常,可以將調用析構函數的函數放入try塊,并提供相應類型的catch處理程序塊。拋出對象的析構函數在異常處理程序執行完畢后執行。
?
10.5異常匹配
從基類可以派生各種異常類,當一個異常拋出時,異常處理器會根據異常處理順序找到”最近”的異常類型進行處理。如果catch捕獲了一個指向基類型異常對象的指針或引用,那么它也可以捕獲該基類所派生的異常對象的指針或引用。相關錯誤的多態處理是允許的。
#include <iostream> using namespace std;class Basicerr{}; class Childerr1:public Basicerr{}; class Childerr2:public Basicerr{};class Test { public:void f(){throw Childerr2();} };int main() {Test t;try{t.f();}catch(Basicerr){cout << "catching Basicerr" <<endl;}catch(Childerr1){cout << "catching Childerr1" <<endl;}catch(Childerr2){cout << "catching Childerr2" <<endl;}return 0; }對于這里的異常處理機制,第一個處理器總是匹配一個Basicerr對象或從Basicerr派生的子類對象,所以第一個異常處理捕獲第二個或第三個異常處理的所有異常,而第二個和第三個異常處理器永遠不被調用。因此在捕獲異常中,常把捕獲基類類型的異常處理器放在最末端。
?
10.6標準異常及層次結構層次結構。
C++標準提供了標準庫異常及層次結構。標準異常以基類exception開頭(在頭文件<exception>中定義),該基類提供了函數what(),每個派生類中重定義發出相應的錯誤信息。
由基類exception直接派生的類runtime_error和logic_error(均定義在頭文件<stdexcept>中),分別報告程序的邏輯錯誤和運行時錯誤信息。注意:異常處理不能用于處理異步情況,如磁盤I/O完成、網絡消息到達、鼠標單擊等,這些情況最好用其他方法處理,如終端處理。
總結
以上是生活随笔為你收集整理的C++学习笔记:(十)异常的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 美团网上笔试题
- 下一篇: 内存四区 malloc/free与ne