C++異常處理try、catch 沒有finally

程序的錯誤大體能夠分爲三種,分別是語法錯誤、邏輯錯誤和運行時錯誤:
1) 語法錯誤在編譯和連接階段就能發現,只有 100% 符合語法規則的代碼才能生成可執行程序。語法錯誤是最容易發現、最容易定位、最容易排除的錯誤,程序員最不須要擔憂的就是這種錯誤。
2) 邏輯錯誤是說咱們編寫的代碼思路有問題,不可以達到最終的目標,這種錯誤能夠經過調試來解決。
3) 運行時錯誤是指程序在運行期間發生的錯誤,例如除數爲 0、內存分配失敗、數組越界、文件不存在等。C++ 異常(Exception)機制就是爲解決運行時錯誤而引入的。
 
運行時錯誤若是聽任無論,系統就會執行默認的操做,終止程序運行,也就是咱們常說的程序崩潰(Crash)。
C++ 提供了異常(Exception)機制,讓咱們可以捕獲運行時錯誤,給程序一次「起死回生」的機會,或者至少告訴用戶發生了什麼再終止程序。
捕獲異常
咱們能夠藉助 C++ 異常機制來捕獲上面的異常,避免程序崩潰。捕獲異常的語法爲:
try{
    // 可能拋出異常的語句
}catch(exceptionType variable){
    // 處理異常的語句
}
這就比如,catch 告訴 try:你去檢測一下程序有沒有錯誤,有錯誤的話就告訴我,我來處理,沒有就pass!
#include<iostream>
#include<exception>
 
using namespace std;
 
int main(int argc, char *argv[])
{
    string str = " https://home.cnblogs.com/u/gschain/";
    try {
        char ch = str[100];
        cout<<ch<<endl;
    } catch (exception &e) {
        cout<<"ch out of bound"<<endl;
    }
 
    try {
        char ch1 = str.at(100);
        cout<<ch1<<endl;
    } catch (exception &e) {
        cout<<"ch1 out of bound"<<endl;
    }
    return 0;
}
運行結果:
 
ch1 out of bound
 
能夠看出,第一個 try 沒有捕獲到異常,輸出了一個沒有意義的字符(垃圾值)。由於[ ]不會檢查下標越界,不會拋出異常,因此即便有錯誤,try 也檢測不到。
換句話說,發生異常時必須將異常明確地拋出,try 才能檢測到;若是不拋出來,即便有異常 try 也檢測不到。所謂拋出異常,就是明確地告訴程序發生了什麼錯誤。
第二個 try 檢測到了異常,並交給 catch 處理,執行 catch 中的語句。須要說明的是,異常一旦拋出,會馬上被 try 檢測到,而且不會再執行異常點(異常發生位置)後面的語句。本例中拋出異常的位置是第 17 行的 at() 函數,它後面的 cout 語句就不會再被執行,因此看不到它的輸出。
說得直接一點,檢測到異常後程序的執行流會發生跳轉,從異常點跳轉到 catch 所在的位置,位於異常點以後的、而且在當前 try 塊內的語句就都不會再執行了;即便 catch 語句成功地處理了錯誤,程序的執行流也不會再回退到異常點,因此這些語句永遠都沒有執行的機會了。本例中,第 18 行代碼就是被跳過的代碼。
執行完 catch 塊所包含的代碼後,程序會繼續執行 catch 塊後面的代碼,就恢復了正常的執行流。
 
拋出(Throw)--> 檢測(Try) --> 捕獲(Catch)
try{
    throw "Unknown Exception";  //拋出異常
    cout<<"This statement will not be executed."<<endl;
}catch(const char* &e){
    cout<<e<<endl;
}
 
多級 catch
當異常發生時,程序會按照從上到下的順序,將異常類型和 catch 所能接收的類型逐個匹配。一旦找到類型匹配的 catch 就中止檢索,並將異常交給當前的 catch 處理(其餘的 catch 不會被執行)。若是最終也沒有找到匹配的 catch,就只能交給系統處理,終止程序的運行。
#include<iostream>
#include<exception>
using namespace std;
class Base{ };
class Derived: public Base{ };
 
int main(int argc, char *argv[])
{
    try{
        throw Derived();  //拋出本身的異常類型,其實是建立一個Derived類型的匿名對象
        cout<<"This statement will not be executed."<<endl;
    }catch(int){
        cout<<"Exception type: int"<<endl;
    }catch(char *){
        cout<<"Exception type: cahr *"<<endl;
    }catch(Base){  //匹配成功(向上轉型)
        cout<<"Exception type: Base"<<endl;
    }catch(Derived){
        cout<<"Exception type: Derived"<<endl;
    }
 
    return 0;
}
輸出結果:
Exception type: Base
 
catch 在匹配過程當中的類型轉換
C/C++ 中存在多種多樣的類型轉換,以普通函數(非模板函數)爲例,發生函數調用時,若是實參和形參的類型不是嚴格匹配,那麼會將實參的類型進行適當的轉換,以適應形參的類型,這些轉換包括:
算數轉換:例如 int 轉換爲 float,char 轉換爲 int,double 轉換爲 int 等。
向上轉型:也就是派生類向基類的轉換,請查看《C++向上轉型(將派生類賦值給基類)》瞭解詳情。
const 轉換:也即將非 const 類型轉換爲 const 類型,例如將 char * 轉換爲 const char *。
數組或函數指針轉換:若是函數形參不是引用類型,那麼數組名會轉換爲數組指針,函數名也會轉換爲函數指針。
用戶自定的類型轉換。
 
#include <iostream>
using namespace std;
int main(){
    int nums[] = {1, 2, 3};
    try{
        throw nums;
        cout<<"This statement will not be executed."<<endl;
    }catch(const int *){
        cout<<"Exception type: const int *"<<endl;
    }
 
    return 0;
}
運行結果:
Exception type: const int *
nums 原本的類型是int [3],可是 catch 中沒有嚴格匹配的類型,因此先轉換爲int *,再轉換爲const int *。
 
throw用做異常規範
throw 關鍵字除了能夠用在函數體中拋出異常,還能夠用在函數頭和函數體之間,指明當前函數可以拋出的異常類型,這稱爲異常規範(Exception specification),有些教程也稱爲異常指示符或異常列表。請看下面的例子:
double func (char param) throw (int);
這條語句聲明瞭一個名爲 func 的函數,它的返回值類型爲 double,有一個 char類型的參數,而且只能拋出int類型的異常。若是拋出其餘類型的異常,try將沒法捕獲,只能終止程序。
 
若是函數會拋出多種類型的異常,那麼能夠用逗號隔開:
double func (char param) throw (int, char, exception);
 
若是函數不會拋出任何異常,那麼( )中什麼也不寫:
double func (char param) throw ();
 
如此,func() 函數就不能拋出任何類型的異常了,即便拋出了,try 也檢測不到。
int disp() throw(int, int)
{
    int i = 1;
    if (i == 1) throw 1, 3;
    return i;
}
 
C++語言自己或者標準庫拋出的異常都是 exception 的子類,稱爲標準異常(Standard Exception)。你能夠經過下面的語句來捕獲全部的標準異常:
try{
    //可能拋出異常的語句
}catch(exception &e){
    //處理異常的語句
}
 
爲什麼C++不提供「finally」結構
由於C++提供了另外一種 機制,徹底能夠取代finally,並且這種機制幾乎總要比finally工做得更好:就是——「分配資源即初始化」。
在C++中一般使用RAII,即Resource Aquisition Is Initialization.
就是將資源封裝成一個類,將資源的初始化封裝在構造函數裏,釋放封裝在析構函數裏。要在局部使用資源的時候,就實例化一個local object。
在拋出異常的時候,因爲local object脫離了做用域,自動調用析構函數,會保證資源被釋放
相關文章
相關標籤/搜索