編程代碼
新聞詳情

C++異常處理(try catch throw)完全攻略

發布時(shí)間:2021-01-13 14:21:49 最後更新:2021-01-13 14:46:24 浏覽次數:3058

程序運行時(shí)常會碰到(dào)一(yī / yì /yí)些異常情況,例如:

•   做除法的(de)時(shí)候除數爲(wéi / wèi) 0;

•   用戶輸入年齡時(shí)輸入了(le/liǎo)一(yī / yì /yí)個(gè)負數;

•   用 new 運算符動态分配空間時(shí),空間不(bù)夠導緻無法分配;

•   訪問數組元素時(shí),下标越界;打開文件讀取時(shí),文件不(bù)存在(zài)。


這(zhè)些異常情況,如果不(bù)能發現并加以(yǐ)處理,很可能會導緻程序崩潰。

所謂“處理”,可以(yǐ)是(shì)給出(chū)錯誤提示信息,然後讓程序沿一(yī / yì /yí)條不(bù)會出(chū)錯的(de)路徑繼續執行;也(yě)可能是(shì)不(bù)得不(bù)結束程序,但在(zài)結束前做一(yī / yì /yí)些必要(yào / yāo)的(de)工作,如将内存中的(de)數據寫入文件、關閉打開的(de)文件、釋放動态分配的(de)内存空間等。

一(yī / yì /yí)發現異常情況就(jiù)立即處理未必妥當,因爲(wéi / wèi)在(zài)一(yī / yì /yí)個(gè)函數執行過程中發生的(de)異常,在(zài)有的(de)情況下由該函數的(de)調用者決定如何處理更加合适。尤其像庫函數這(zhè)類提供給程序員調用,用以(yǐ)完成與具體應用無關的(de)通用功能的(de)函數,執行過程中貿然對異常進行處理,未必符合調用它的(de)程序的(de)需要(yào / yāo)。

此外,将異常分散在(zài)各處進行處理不(bù)利于(yú)代碼的(de)維護,尤其是(shì)對于(yú)在(zài)不(bù)同地(dì / de)方發生的(de)同一(yī / yì /yí)種異常,都要(yào / yāo)編寫相同的(de)處理代碼也(yě)是(shì)一(yī / yì /yí)種不(bù)必要(yào / yāo)的(de)重複和(hé / huò)冗餘。如果能在(zài)發生各種異常時(shí)讓程序都執行到(dào)同一(yī / yì /yí)個(gè)地(dì / de)方,這(zhè)個(gè)地(dì / de)方能夠對異常進行集中處理,則程序就(jiù)會更容易編寫、維護。

鑒于(yú)上(shàng)述原因,C++ 引入了(le/liǎo)異常處理機制。其基本思想是(shì):函數 A 在(zài)執行過程中發現異常時(shí)可以(yǐ)不(bù)加處理,而(ér)隻是(shì)“拋出(chū)一(yī / yì /yí)個(gè)異常”給 A 的(de)調用者,假定爲(wéi / wèi)函數 B。

拋出(chū)異常而(ér)不(bù)加處理會導緻函數 A 立即中止,在(zài)這(zhè)種情況下,函數 B 可以(yǐ)選擇捕獲 A 拋出(chū)的(de)異常進行處理,也(yě)可以(yǐ)選擇置之(zhī)不(bù)理。如果置之(zhī)不(bù)理,這(zhè)個(gè)異常就(jiù)會被拋給 B 的(de)調用者,以(yǐ)此類推。

如果一(yī / yì /yí)層層的(de)函數都不(bù)處理異常,異常最終會被拋給最外層的(de)main 函數。main 函數應該處理異常。如果main函數也(yě)不(bù)處理異常,那麽程序就(jiù)會立即異常地(dì / de)中止。

C++異常處理基本語法

C++通過 throw 語句和(hé / huò) try...catch 語句實現對異常的(de)處理。throw 語句的(de)語法如下:

throw  表達式;

該語句拋出(chū)一(yī / yì /yí)個(gè)異常。異常是(shì)一(yī / yì /yí)個(gè)表達式,其值的(de)類型可以(yǐ)是(shì)基本類型,也(yě)可以(yǐ)是(shì)類。

try...catch 語句的(de)語法如下:

try {
    語句組
}
catch(異常類型) {
    異常處理代碼
}
...
catch(異常類型) {
    異常處理代碼
}

catch可以(yǐ)有多個(gè),但至少要(yào / yāo)有一(yī / yì /yí)個(gè)。

不(bù)妨把 try 和(hé / huò)其後{}中的(de)内容稱作“try塊”,把 catch 和(hé / huò)其後{}中的(de)内容稱作“catch塊”。

try...catch 語句的(de)執行過程是(shì):

•   執行 try 塊中的(de)語句,如果執行的(de)過程中沒有異常拋出(chū),那麽執行完後就(jiù)執行最後一(yī / yì /yí)個(gè) catch 塊後面的(de)語句,所有 catch 塊中的(de)語句都不(bù)會被執行;

•   如果 try 塊執行的(de)過程中拋出(chū)了(le/liǎo)異常,那麽拋出(chū)異常後立即跳轉到(dào)第一(yī / yì /yí)個(gè)“異常類型”和(hé / huò)拋出(chū)的(de)異常類型匹配的(de) catch 塊中執行(稱作異常被該 catch 塊“捕獲”),執行完後再跳轉到(dào)最後一(yī / yì /yí)個(gè) catch 塊後面繼續執行。


例如下面的(de)程序:

#include 
using namespace std;
int main()
{
    double m ,n;
    cin >> m >> n;
    try {
        cout << "before dividing." << endl;
        if( n == 0)
            throw -1; //抛出(chū)int類型異常
        else
            cout << m / n << endl;
        cout << "after dividing." << endl;
    }
    catch(double d) {
        cout << "catch(double) " << d <<  endl;
    }
    catch(int e) {
        cout << "catch(int) " << e << endl;
    }
    cout << "finished" << endl;
    return 0;
}

程序的(de)運行結果如下:
9 6↙
before dividing.
1.5
after dividing.
finished

說(shuō)明當 n 不(bù)爲(wéi / wèi) 0 時(shí),try 塊中不(bù)會拋出(chū)異常。因此程序在(zài) try 塊正常執行完後,越過所有的(de) catch 塊繼續執行,catch 塊一(yī / yì /yí)個(gè)也(yě)不(bù)會執行。

程序的(de)運行結果也(yě)可能如下:
9 0↙
before dividing.
catch\(int) -1
finished

當 n 爲(wéi / wèi) 0 時(shí),try 塊中會拋出(chū)一(yī / yì /yí)個(gè)整型異常。拋出(chū)異常後,try 塊立即停止執行。該整型異常會被類型匹配的(de)第一(yī / yì /yí)個(gè) catch 塊捕獲,即進入catch(inte)塊執行,該 catch 塊執行完畢後,程序繼續往後執行,直到(dào)正常結束。

如果拋出(chū)的(de)異常沒有被 catch 塊捕獲,例如,将catch(int e),改爲(wéi / wèi)catch(chare),當輸入的(de) n 爲(wéi / wèi) 0 時(shí),拋出(chū)的(de)整型異常就(jiù)沒有catch 塊能捕獲,這(zhè)個(gè)異常也(yě)就(jiù)得不(bù)到(dào)處理,那麽程序就(jiù)會立即中止,try...catch 後面的(de)内容都不(bù)會被執行。

能夠捕獲任何異常的(de) catch 語句

如果希望不(bù)論拋出(chū)哪種類型的(de)異常都能捕獲,可以(yǐ)編寫如下 catch 塊:

catch(...) {
    ...
}

這(zhè)樣的(de)catch 塊能夠捕獲任何還沒有被捕獲的(de)異常。例如下面的(de)程序:

#include 
using namespace std;
int main()
{
    double m, n;
    cin >> m >> n;
    try {
        cout << "before dividing." << endl;
        if (n == 0)
            throw - 1;  //抛出(chū)整型異常
        else if (m == 0)
            throw - 1.0;  //拋出(chū) double 型異常
        else
            cout << m / n << endl;
        cout << "after dividing." << endl;
    }
    catch (double d) {
        cout << "catch (double)" << d << endl;
    }
    catch (...) {
        cout << "catch (...)" << endl;
    }
    cout << "finished" << endl;
    return 0;
}

程序的(de)運行結果如下:
9 0↙
before dividing.
catch (...)
finished

當 n 爲(wéi / wèi) 0 時(shí),拋出(chū)的(de)整型異常被catchy(...)捕獲。

程序的(de)運行結果也(yě)可能如下:
0 6↙
before dividing.
catch (double) -1
finished

當 m 爲(wéi / wèi) 0 時(shí),拋出(chū)一(yī / yì /yí)個(gè) double類型的(de)異常。雖然catch (double)和(hé / huò)catch(...)都能匹配該異常,但是(shì)catch(double)是(shì)第一(yī / yì /yí)個(gè)能匹配的(de) catch 塊,因此會執行它,而(ér)不(bù)會執行catch(...)塊。

由于(yú)catch(...)能匹配任何類型的(de)異常,它後面的(de) catch 塊實際上(shàng)就(jiù)不(bù)起作用,因此不(bù)要(yào / yāo)将它寫在(zài)其他(tā) catch 塊前面。

異常的(de)再拋出(chū)

如果一(yī / yì /yí)個(gè)函數在(zài)執行過程中拋出(chū)的(de)異常在(zài)本函數内就(jiù)被 catch 塊捕獲并處理,那麽該異常就(jiù)不(bù)會拋給這(zhè)個(gè)函數的(de)調用者(也(yě)稱爲(wéi / wèi)“上(shàng)一(yī / yì /yí)層的(de)函數”);如果異常在(zài)本函數中沒有被處理,則它就(jiù)會被拋給上(shàng)一(yī / yì /yí)層的(de)函數。例如下面的(de)程序:

#include 
#include 
using namespace std;
class CException
{
public:
    string msg;
    CException(string s) : msg(s) {}
};
double Devide(double x, double y)
{
    if (y == 0)
        throw CException("devided by zero");
    cout << "in Devide" << endl;
    return x / y;
}
int CountTax(int salary)
{
    try {
        if (salary < 0)
            throw - 1;
        cout << "counting tax" << endl;
    }
    catch (int) {
        cout << "salary < 0" << endl;
    }
    cout << "tax counted" << endl;
    return salary * 0.15;
}
int main()
{
    double f = 1.2;
    try {
        CountTax(-1);
        f = Devide(3, 0);
        cout << "end of try block" << endl;
    }
    catch (CException e) {
        cout << e.msg << endl;
    }
    cout << "f = " << f << endl;
    cout << "finished" << endl;
    return 0;
}

程序的(de)輸出(chū)結果如下:
salary < 0
tax counted
devided by zero
f=1.2
finished

CountTa 函數拋出(chū)異常後自行處理,這(zhè)個(gè)異常就(jiù)不(bù)會繼續被拋給調用者,即 main 函數。因此在(zài) main 函數的(de) try 塊中,CountTax 之(zhī)後的(de)語句還能正常執行,即會執行f =Devide(3, 0);。

第 35 行,Devide 函數拋出(chū)了(le/liǎo)異常卻不(bù)處理,該異常就(jiù)會被拋給 Devide 函數的(de)調用者,即 main 函數。拋出(chū)此異常後,Devide 函數立即結束,第 14 行不(bù)會被執行,函數也(yě)不(bù)會返回一(yī / yì /yí)個(gè)值,這(zhè)從第 35 行 f 的(de)值不(bù)會被修改可以(yǐ)看出(chū)。

Devide 函數中拋出(chū)的(de)異常被 main 函數中類型匹配的(de) catch 塊捕獲。第 38 行中的(de)e 對象是(shì)用複制構造函數初始化的(de)。

如果拋出(chū)的(de)異常是(shì)派生類的(de)對象,而(ér) catch 塊的(de)異常類型是(shì)基類,那麽這(zhè)兩者也(yě)能夠匹配,因爲(wéi / wèi)派生類對象也(yě)是(shì)基類對象。

雖然函數也(yě)可以(yǐ)通過返回值或者傳引用的(de)參數通知調用者發生了(le/liǎo)異常,但采用這(zhè)種方式的(de)話,每次調用函數時(shí)都要(yào / yāo)判斷是(shì)否發生了(le/liǎo)異常,這(zhè)在(zài)函數被多處調用時(shí)比較麻煩。有了(le/liǎo)異常處理機制,可以(yǐ)将多處函數調用都寫在(zài)一(yī / yì /yí)個(gè) try 塊中,任何一(yī / yì /yí)處調用發生異常都會被匹配的(de) catch 塊捕獲并處理,也(yě)就(jiù)不(bù)需要(yào / yāo)每次調用後都判斷是(shì)否發生了(le/liǎo)異常。

有時(shí),雖然在(zài)函數中對異常進行了(le/liǎo)處理,但是(shì)還是(shì)希望能夠通知調用者,以(yǐ)便讓調用者知道(dào)發生了(le/liǎo)異常,從而(ér)可以(yǐ)作進一(yī / yì /yí)步的(de)處理。在(zài) catch 塊中拋出(chū)異常可以(yǐ)滿足這(zhè)種需要(yào / yāo)。例如:

#include 
#include 
using namespace std;
int CountTax(int salary)
{
    try {
        if( salary < 0 )
            throw string("zero salary");
        cout << "counting tax" << endl;

    }
    catch (string s ) {
        cout << "CountTax error : " << s << endl;
        throw; //繼續抛出(chū)捕獲的(de)異常
    }
    cout << "tax counted" << endl;
    return salary * 0.15;
}
int main()
{
    double f = 1.2;
    try {
        CountTax(-1);
        cout << "end of try block" << endl;
    }
    catch(string s) {
        cout << s << endl;
    }
    cout << "finished" << endl;
    return 0;
}

程序的(de)輸出(chū)結果如下:
CountTax error:zero salary
zero salary
finished

第 14 行的(de)throw;沒有指明拋出(chū)什麽樣的(de)異常,因此拋出(chū)的(de)就(jiù)是(shì)catch 塊捕獲到(dào)的(de)異常,即 string("zero salary")。這(zhè)個(gè)異常會被 main 函數中的(de) catch 塊捕獲。

函數的(de)異常聲明列表

爲(wéi / wèi)了(le/liǎo)增強程序的(de)可讀性和(hé / huò)可維護性,使程序員在(zài)使用一(yī / yì /yí)個(gè)函數時(shí)就(jiù)能看出(chū)這(zhè)個(gè)函數可能會拋出(chū)哪些異常,C++ 允許在(zài)函數聲明和(hé / huò)定義時(shí),加上(shàng)它所能拋出(chū)的(de)異常的(de)列表,具體寫法如下:

void func() throw (int, double, A, B, C);

void func() throw (int, double, A, B, C){...}

上(shàng)面的(de)寫法表明 func 可能拋出(chū) int 型、double型以(yǐ)及 A、B、C 三種類型的(de)異常。異常聲明列表可以(yǐ)在(zài)函數聲明時(shí)寫,也(yě)可以(yǐ)在(zài)函數定義時(shí)寫。如果兩處都寫,則兩處應一(yī / yì /yí)緻。

如果異常聲明列表如下編寫:

void func() throw ();

則說(shuō)明func 函數不(bù)會拋出(chū)任何異常。

一(yī / yì /yí)個(gè)函數如果不(bù)交待能拋出(chū)哪些類型的(de)異常,就(jiù)可以(yǐ)拋出(chū)任何類型的(de)異常。

函數如果拋出(chū)了(le/liǎo)其異常聲明列表中沒有的(de)異常,在(zài)編譯時(shí)不(bù)會引發錯誤,但在(zài)運行時(shí), Dev C++ 編譯出(chū)來(lái)的(de)程序會出(chū)錯;用 Visual Studio 2010 編譯出(chū)來(lái)的(de)程序則不(bù)會出(chū)錯,異常聲明列表不(bù)起實際作用。

C++标準異常類

C++标準庫中有一(yī / yì /yí)些類代表異常,這(zhè)些類都是(shì)從 exception 類派生而(ér)來(lái)的(de)。常用的(de)幾個(gè)異常類如圖 1 所示。

圖1:常用的(de)異常類


bad_typeid、bad_cast、bad_alloc、ios_base::failure、out_of_range 都是(shì) exception 類的(de)派生類。C++ 程序在(zài)碰到(dào)某些異常時(shí),即使程序中沒有寫 throw 語句,也(yě)會自動拋出(chū)上(shàng)述異常類的(de)對象。這(zhè)些異常類還都有名爲(wéi / wèi) what 的(de)成員函數,返回字符串形式的(de)異常描述信息。使用這(zhè)些異常類需要(yào / yāo)包含頭文件stdexcept。

下面分别介紹以(yǐ)上(shàng)幾個(gè)異常類。本節程序的(de)輸出(chū)以(yǐ) Visual Studio 2010爲(wéi / wèi)準,Dev C++ 編譯的(de)程序輸出(chū)有所不(bù)同。

1) bad_typeid

使用typeid 運算符時(shí),如果其操作數是(shì)一(yī / yì /yí)個(gè)多态類的(de)指針,而(ér)該指針的(de)值爲(wéi / wèi) NULL,則會拋出(chū)此異常。

2) bad_cast

在(zài)用dynamic_cast 進行從多态基類對象(或引用)到(dào)派生類的(de)引用的(de)強制類型轉換時(shí),如果轉換是(shì)不(bù)安全的(de),則會拋出(chū)此異常。程序示例如下:

#include 
#include 
using namespace std;
class Base
{
    virtual void func() {}
};
class Derived : public Base
{
public:
    void Print() {}
};
void PrintObj(Base & b)
{
    try {
        Derived & rd = dynamic_cast (b);
        //此轉換若不(bù)安全,會拋出(chū) bad_cast 異常
        rd.Print();
    }
    catch (bad_cast & e) {
        cerr << e.what() << endl;
    }
}
int main()
{
    Base b;
    PrintObj(b);
    return 0;
}

程序的(de)輸出(chū)結果如下:
Bad dynamic_cast!

在(zài) PrintObj 函數中,通過 dynamic_cast 檢測 b 是(shì)否引用的(de)是(shì)一(yī / yì /yí)個(gè) Derived 對象,如果是(shì),就(jiù)調用其 Print 成員函數;如果不(bù)是(shì),就(jiù)拋出(chū)異常,不(bù)會調用 Derived::Print。

3) bad_alloc

在(zài)用 new運算符進行動态内存分配時(shí),如果沒有足夠的(de)内存,則會引發此異常。程序示例如下:

#include 
#include 
using namespace std;
int main()
{
    try {
        char * p = new char[0x7fffffff];  //無法分配這(zhè)麽多空間,會抛出(chū)異常
    }
    catch (bad_alloc & e)  {
        cerr << e.what() << endl;
    }
    return 0;
}

程序的(de)輸出(chū)結果如下:
bad allocation
ios_base::failure

在(zài)默認狀态下,輸入輸出(chū)流對象不(bù)會拋出(chū)此異常。如果用流對象的(de) exceptions 成員函數設置了(le/liǎo)一(yī / yì /yí)些标志位,則在(zài)出(chū)現打開文件出(chū)錯、讀到(dào)輸入流的(de)文件尾等情況時(shí)會拋出(chū)此異常。此處不(bù)再贅述。

4) out_of_range

用vector 或 string 的(de) at 成員函數根據下标訪問元素時(shí),如果下标越界,則會拋出(chū)此異常。例如:

#include 
#include 
#include 
#include 
using namespace std;
int main()
{
    vector v(10);
    try {
        v.at(100) = 100;  //拋出(chū) out_of_range 異常
    }
    catch (out_of_range & e) {
        cerr << e.what() << endl;
    }
    string s = "hello";
    try {
        char c = s.at(100);  //拋出(chū) out_of_range 異常
    }
    catch (out_of_range & e) {
        cerr << e.what() << endl;
    }
    return 0;
}

程序的(de)輸出(chū)結果如下:
invalid vector subscript
invalid string position

如果将v.at(100)換成v[100],将s.at(100)換成s[100],程序就(jiù)不(bù)會引發異常(但可能導緻程序崩潰)。因爲(wéi / wèi) at 成員函數會檢測下标越界并拋出(chū)異常,而(ér) operator[] 則不(bù)會。operator [] 相比 at 的(de)好處就(jiù)是(shì)不(bù)用判斷下标是(shì)否越界,因此執行速度更快。

在(zài)線客服 雙翌客服
客服電話
  • 0755-23712116
  • 13310869691