天天看點

C++異常處理入門,C++ try catch入門

作者:C語言小新

開發程式是一項“燒腦”的工作,程式員不但要經過長期的知識學習和思維訓練,還要做到一絲不苟,注意每一個細節和邊界。即使這樣,也不能防止程式出錯。

專家指出,長期作息不規律 + 用腦過度的危害很大,可能會誘發神經衰弱、失眠等疾病。我就是受害者之一,曾被失眠困擾了好幾年,不但入睡困難,還容易早醒。程式員要注意勞逸結合,多去健身房,多跑步,多打球,多陪女朋友旅遊等,千萬不要熬夜,以為深夜寫代碼效率高,這樣會透支年輕的身體。

程式的錯誤大緻可以分為三種,分别是文法錯誤、邏輯錯誤和運作時錯誤:

1) 文法錯誤在編譯和連結階段就能發現,隻有 100% 符合文法規則的代碼才能生成可執行程式。文法錯誤是最容易發現、最容易定位、最容易排除的錯誤,程式員最不需要擔心的就是這種錯誤。

2) 邏輯錯誤是說我們編寫的代碼思路有問題,不能夠達到最終的目标,這種錯誤可以通過調試來解決。

3) 運作時錯誤是指程式在運作期間發生的錯誤,例如除數為 0、記憶體配置設定失敗、數組越界、檔案不存在等。C++ 異常(Exception)機制就是為解決運作時錯誤而引入的。

運作時錯誤如果放任不管,系統就會執行預設的操作,終止程式運作,也就是我們常說的程式崩潰(Crash)。C++ 提供了異常(Exception)機制,讓我們能夠捕獲運作時錯誤,給程式一次“起死回生”的機會,或者至少告訴使用者發生了什麼再終止程式。

【例1】一個發生運作時錯誤的程式:

#include <iostream>
#include <string>
using namespace std;
int main(){
    string str = "http://c.biancheng.net";
    char ch1 = str[100];  //下标越界,ch1為垃圾值
    cout<<ch1<<endl;
    char ch2 = str.at(100);  //下标越界,抛出異常
    cout<<ch2<<endl;
    return 0;
}           

運作代碼,在控制台輸出 ch1 的值後程式崩潰。下面我們來分析一下原因。

at() 是 string 類的一個成員函數,它會根據下标來傳回字元串的一個字元。與[ ]不同,at() 會檢查下标是否越界,如果越界就抛出一個異常;而[ ]不做檢查,不管下标是多少都會照常通路。

所謂抛出異常,就是報告一個運作時錯誤,程式員可以根據錯誤資訊來進一步處理。

上面的代碼中,下标 100 顯然超出了字元串 str 的長度。由于第 6 行代碼不會檢查下标越界,雖然有邏輯錯誤,但是程式能夠正常運作。而第 8 行代碼則不同,at() 函數檢測到下标越界會抛出一個異常,這個異常可以由程式員處理,但是我們在代碼中并沒有處理,是以系統隻能執行預設的操作,也即終止程式執行。

捕獲異常

我們可以借助 C++ 異常機制來捕獲上面的異常,避免程式崩潰。捕獲異常的文法為:

try{

// 可能抛出異常的語句

}catch(exceptionType variable){

// 處理異常的語句

}

try和catch都是 C++ 中的關鍵字,後跟語句塊,不能省略{ }。try 中包含可能會抛出異常的語句,一旦有異常抛出就會被後面的 catch 捕獲。從 try 的意思可以看出,它隻是“檢測”語句塊有沒有異常,如果沒有發生異常,它就“檢測”不到。catch 是“抓住”的意思,用來捕獲并處理 try 檢測到的異常;如果 try 語句塊沒有檢測到異常(沒有異常抛出),那麼就不會執行 catch 中的語句。

這就好比,catch 告訴 try:你去檢測一下程式有沒有錯誤,有錯誤的話就告訴我,我來處理,沒有的話就不要理我!

catch 關鍵字後面的exceptionType variable指明了目前 catch 可以處理的異常類型,以及具體的出錯資訊。我們稍後再對異常類型展開講解,當務之急是示範一下 try-catch 的用法,先讓讀者有一個整體上的認識。

【例2】修改上面的代碼,加入捕獲異常的語句:

#include <iostream>
#include <string>
#include <exception>
using namespace std;
int main(){
    string str = "http://c.biancheng.net";
  
    try{
        char ch1 = str[100];
        cout<<ch1<<endl;
    }catch(exception e){
        cout<<"[1]out of bound!"<<endl;
    }
    try{
        char ch2 = str.at(100);
        cout<<ch2<<endl;
    }catch(exception &e){  //exception類位于<exception>頭檔案中
        cout<<"[2]out of bound!"<<endl;
    }
    return 0;
}           

運作結果:

(

[2]out of bound!

可以看出,第一個 try 沒有捕獲到異常,輸出了一個沒有意義的字元(垃圾值)。因為[ ]不會檢查下标越界,不會抛出異常,是以即使有錯誤,try 也檢測不到。換句話說,發生異常時必須将異常明确地抛出,try 才能檢測到;如果不抛出來,即使有異常 try 也檢測不到。所謂抛出異常,就是明确地告訴程式發生了什麼錯誤。

第二個 try 檢測到了異常,并交給 catch 處理,執行 catch 中的語句。需要說明的是,異常一旦抛出,會立刻被 try 檢測到,并且不會再執行異常點(異常發生位置)後面的語句。本例中抛出異常的位置是第 17 行的 at() 函數,它後面的 cout 語句就不會再被執行,是以看不到它的輸出。

說得直接一點,檢測到異常後程式的執行流會發生跳轉,從異常點跳轉到 catch 所在的位置,位于異常點之後的、并且在目前 try 塊内的語句就都不會再執行了;即使 catch 語句成功地處理了錯誤,程式的執行流也不會再回退到異常點,是以這些語句永遠都沒有執行的機會了。本例中,第 18 行代碼就是被跳過的代碼。

執行完 catch 塊所包含的代碼後,程式會繼續執行 catch 塊後面的代碼,就恢複了正常的執行流。

為了示範「不明确地抛出異常就檢測不到異常」,大家不妨将第 10 行代碼改為char ch1 = str[100000000];,通路第 100 個字元可能不會發生異常,但是通路第 1 億個字元肯定會發生異常了,這個異常就是記憶體通路錯誤。運作更改後的程式,會發現第 10 行代碼産生了異常,導緻程式崩潰了,這說明 try-catch 并沒有捕獲到這個異常。

關于「如何抛出異常」,我們将在下節講解,這裡重點是讓大家明白異常的處理流程:

抛出(Throw)--> 檢測(Try) --> 捕獲(Catch)

發生異常的位置

異常可以發生在目前的 try 塊中,也可以發生在 try 塊所調用的某個函數中,或者是所調用的函數又調用了另外的一個函數,這個另外的函數中發生了異常。這些異常,都可以被 try 檢測到。

1) 下面的例子示範了 try 塊中直接發生的異常:

#include <iostream>
#include <string>
#include <exception>
using namespace std;
int main(){
    try{
        throw "Unknown Exception";  //抛出異常
        cout<<"This statement will not be executed."<<endl;
    }catch(const char* &e){
        cout<<e<<endl;
    }
    return 0;
}           

運作結果:

Unknown Exception

throw關鍵字用來抛出一個異常,這個異常會被 try 檢測到,進而被 catch 捕獲。關于 throw 的用法,我們将在下節深入講解,這裡大家隻需要知道,在 try 塊中直接抛出的異常會被 try 檢測到。

2) 下面的例子示範了 try 塊中調用的某個函數中發生了異常:

#include <iostream>
#include <string>
#include <exception>
using namespace std;
void func(){
    throw "Unknown Exception";  //抛出異常
    cout<<"[1]This statement will not be executed."<<endl;
}
int main(){
    try{
        func();
        cout<<"[2]This statement will not be executed."<<endl;
    }catch(const char* &e){
        cout<<e<<endl;
    }
    return 0;
}           

運作結果:

Unknown Exception

func() 在 try 塊中被調用,它抛出的異常會被 try 檢測到,進而被 catch 捕獲。從運作結果可以看出,func() 中的 cout 和 try 中的 cout 都沒有被執行。

3) try 塊中調用了某個函數,該函數又調用了另外的一個函數,這個另外的函數抛出了異常:

#include <iostream>
#include <string>
#include <exception>
using namespace std;
void func_inner(){
    throw "Unknown Exception";  //抛出異常
    cout<<"[1]This statement will not be executed."<<endl;
}
void func_outer(){
    func_inner();
    cout<<"[2]This statement will not be executed."<<endl;
}
int main(){
    try{
        func_outer();
        cout<<"[3]This statement will not be executed."<<endl;
    }catch(const char* &e){
        cout<<e<<endl;
    }
    return 0;
}           

運作結果:

Unknown Exception

發生異常後,程式的執行流會沿着函數的調用鍊往前回退,直到遇見 try 才停止。在這個回退過程中,調用鍊中剩下的代碼(所有函數中未被執行的代碼)都會被跳過,沒有執行的機會了。

繼續閱讀