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脫離了作用域,自動調用析構函數,會保證資源被釋放


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM