溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

C++語言(13)——C++異常處理

發(fā)布時(shí)間:2020-06-24 19:52:42 來源:網(wǎng)絡(luò) 閱讀:6661 作者:三九感冒靈 欄目:編程語言

1、C語言異常處理

1.1、異常的概念

異常:程序在運(yùn)行過程中可能產(chǎn)生異常(是程序運(yùn)行時(shí)可預(yù)料的執(zhí)行分支),如:運(yùn)行時(shí)除0的情況,需要打開的外部文件不存在的情況,數(shù)組訪問越界的情況...
Bug:bug是程序中的錯(cuò)誤,是不可被預(yù)期運(yùn)行方式,如:野指針、堆內(nèi)存結(jié)束后未釋放、選擇排序無法處理長度為0的數(shù)組...

1.2、C語言的異常處理

1.2.1經(jīng)典方法:

if(判斷是否產(chǎn)生異常)
{
//正常代碼邏輯
}
else
{
//異常代碼邏輯
}

#include <iostream>
#include <string>

using namespace std;

double divide(double a, double b, int* valid)
{
    const double delta = 0.000000000000001;  //一般不要拿浮點(diǎn)數(shù)和0直接做比較
    double ret = 0;

    if( !((-delta < b) && (b < delta)) )
    {
        ret = a / b;

        *valid = 1;
    }
    else
    {
        *valid = 0;
    }

    return ret;
}

int main(int argc, char *argv[])
{   
    int valid = 0;
    double r = divide(1, 0, &valid);

    if( valid )
    {
        cout << "r = " << r << endl;
    }
    else
    {
        cout << "Divided by zero..." << endl;
    }

    return 0;
}

1.2.2.setjmp()和longjmp()

int setjmp(jmp_buf env)
將當(dāng)前上下文保存在jmp_buf結(jié)構(gòu)體中
void longjmp(jmp_buf env, int val)
從jmp_buf結(jié)構(gòu)體中恢復(fù)setjmp()保存的上下文
最終從setjmp函數(shù)調(diào)用點(diǎn)返回,返回值為val

#include <iostream>
#include <string>
#include <csetjmp>

using namespace std;
/**缺陷:setjmp()和longjmp()的引入必然涉及到全局變量,暴力跳轉(zhuǎn)導(dǎo)致代碼可讀性降低***/

static jmp_buf env;     //定義全局變量

double divide(double a, double b)
{
    const double delta = 0.000000000000001;     //一般不要拿浮點(diǎn)數(shù)和0直接做比較
    double ret = 0;

    if( !((-delta < b) && (b < delta)) )
    {
        ret = a / b;
    }
    else
    {
        longjmp(env, 1);
    }

    return ret;
}

int main(int argc, char *argv[])
{   
    if( setjmp(env) == 0 )
    {
        double r = divide(1, 1);

        cout << "r = " << r << endl;
    }
    else
    {
        cout << "Divided by zero..." << endl;
    }

    return 0;
}

缺陷:setjmp()和longjmp()的引入必然涉及到全局變量,暴力跳轉(zhuǎn)導(dǎo)致代碼可讀性降低

2、C++中的異常處理(上)

2.1 try、catch、throw

C++內(nèi)置了異常處理的語法元素try、catch、throw
--try語句處理正常的代碼邏輯
--catch語句處理異常的情況
--C++通過throw語句拋出異常信息
try語句中的異常由對應(yīng)的catch語句處理
函數(shù)在運(yùn)行時(shí)拋出(throw)一個(gè)異常到函數(shù)調(diào)用的地方(try語句內(nèi)部),try語句就會(huì)將異常交給對應(yīng)的catch語句去處理

#include <iostream>
#include <string>

using namespace std;

/**函數(shù)在運(yùn)行時(shí)拋出(throw)一個(gè)異常到函數(shù)調(diào)用的地方(try語句內(nèi)部),try語句就會(huì)將異常交給對應(yīng)的catch語句去處理**/
double divide(double a, double b)
{
    const double delta = 0.000000000000001;
    double ret = 0;

    if( !((-delta < b) && (b < delta)) )
    {
        ret = a / b;
    }
    else
    {
        throw 0;
    }

    return ret;
}

int main(int argc, char *argv[])
{    
    try
    {
        double r = divide(1, 0);

        cout << "r = " << r << endl;
    }
    catch(...)
    {
        cout << "Divided by zero..." << endl;
    }

    return 0;
}

2.2 C++異常處理分析

--throw拋出的異常必須被catch處理
當(dāng)前函數(shù)能夠處理異常,程序繼續(xù)往下執(zhí)行
當(dāng)前函數(shù)無法處理異常,則函數(shù)停止執(zhí)行,并返回(未被處理的異常會(huì)順著函數(shù)調(diào)用棧向上傳播,直到被處理為止,否則程序?qū)⑼V箞?zhí)行)

2.3自定義具體的異常類型

--不同的異常由不同的catch語句負(fù)責(zé)處理
--catch(...)用于處理所有類型的異常(只能被放在最后面)

#include <iostream>
#include <string>

using namespace std;

/**異常拋出后,至上而下將嚴(yán)格的匹配每一個(gè)catch語句處理的類型,不進(jìn)行任何類型的轉(zhuǎn)換**/
void Demo1()
{
    try
    {   
        throw 'c';
    }
    catch(char c)
    {
        cout << "catch(char c)" << endl;
    }
    catch(short c)
    {
        cout << "catch(short c)" << endl;
    }
    catch(double c)
    {
        cout << "catch(double c)" << endl;
    }
    catch(...)
    {
        cout << "catch(...)" << endl;
    }
}

void Demo2()
{
    throw string("D.T.Software");
}

int main(int argc, char *argv[])
{    
    Demo1();

    try
    {
        Demo2();
    }
    catch(char* s)
    {
        cout << "catch(char *s)" << endl;
    }
    catch(const char* cs)
    {
        cout << "catch(const char *cs)" << endl;
    }
    catch(string ss)
    {
        cout << "catch(string ss)" << endl;
    }

    return 0;
}

注意:任何異常都只能被捕獲(catch)一次,異常拋出后,捕獲時(shí)至上而下將嚴(yán)格的匹配每一個(gè)catch語句處理的類型,不進(jìn)行任何類型的轉(zhuǎn)換

3、C++中的異常處理(下)

3.1、catch重新解釋

catch中捕獲的異??梢员恢匦陆忉寬伋?,catch拋出的異常需要外層的try...catch...捕獲
為什么要重新拋出異常?
實(shí)際工程中我們可以對第三方庫中拋出的異常進(jìn)行捕獲、重新解釋(統(tǒng)一異常類型,方便代碼問題定位),然后再拋出

#include <iostream>
#include <string>

using namespace std;

void Demo()
{
    try
    {
        try
        {
            throw 'c';
        }
        catch(int i)
        {
            cout << "Inner: catch(int i)" << endl;
            throw i;
        }
        catch(...)
        {
            cout << "Inner: catch(...)" << endl;
            throw;
        }
    }
    catch(...)
    {
        cout << "Outer: catch(...)" << endl;
    }
}

/*
    假設(shè): 當(dāng)前的函數(shù)是第三方庫中的函數(shù),因此,我們無法修改源代碼

    函數(shù)名: void func(int i)
    拋出異常的類型: int
                        -1 ==》 參數(shù)異常
                        -2 ==》 運(yùn)行異常
                        -3 ==》 超時(shí)異常
*/
void func(int i)
{
    if( i < 0 )
    {
        throw -1;
    }

    if( i > 100 )
    {
        throw -2;
    }

    if( i == 11 )
    {
        throw -3;
    }

    cout << "Run func..." << endl;
}

void MyFunc(int i)  //調(diào)用第三方庫函數(shù),捕獲并重新解釋異常,然后拋出
{
    try
    {
        func(i);
    }
    catch(int i)
    {
        switch(i)
        {
            case -1:
                throw "Invalid Parameter";      //捕獲異常并重新解釋并拋出
                break;
            case -2:
                throw "Runtime Exception";
                break;
            case -3:
                throw "Timeout Exception";
                break;
        }
    }
}

int main(int argc, char *argv[])
{
    Demo();

    try
    {
        MyFunc(11);
    }
    catch(const char* cs)
    {
        cout << "Exception Info: " << cs << endl;
    }

    return 0;
}

注意:
(1)異常的類型可以是自定義類類型,對于類類型異常的匹配依舊是至上而下、嚴(yán)格匹配
(2)賦值兼容原則在異常匹配中依然適用,一般而言
--匹配子類異常的catch放在上部
--匹配父類異常的catch放在下部

#include <iostream>
#include <string>

using namespace std;

class Base
{
};
//異常的類型可以是自定義類類型
class Exception : public Base
{
    int m_id;
    string m_desc;
public:
    Exception(int id, string desc)
    {
        m_id = id;
        m_desc = desc;
    }

    int id() const
    {
        return m_id;
    }

    string description() const
    {
        return m_desc;
    }
};

/*
    假設(shè): 當(dāng)前的函數(shù)式第三方庫中的函數(shù),因此,我們無法修改源代碼

    函數(shù)名: void func(int i)
    拋出異常的類型: int
                        -1 ==》 參數(shù)異常
                        -2 ==》 運(yùn)行異常
                        -3 ==》 超時(shí)異常
*/
void func(int i)
{
    if( i < 0 )
    {
        throw -1;
    }

    if( i > 100 )
    {
        throw -2;
    }

    if( i == 11 )
    {
        throw -3;
    }

    cout << "Run func..." << endl;
}

void MyFunc(int i)
{
    try
    {
        func(i);
    }
    catch(int i)
    {
        switch(i)
        {
            case -1:
                throw Exception(-1, "Invalid Parameter");
                break;
            case -2:
                throw Exception(-2, "Runtime Exception");
                break;
            case -3:
                throw Exception(-3, "Timeout Exception");
                break;
        }
    }
}

int main(int argc, char *argv[])
{
    try
    {
        MyFunc(11);
    }

    //在定義catch語句塊時(shí)推薦使用引用作為參數(shù)(防止拷貝構(gòu)造)
    // 賦值兼容原則在異常匹配中依然適用,一般而言
    catch(const Exception& e)   // 匹配子類異常的catch放在上部
    {
        cout << "Exception Info: " << endl;
        cout << "   ID: " << e.id() << endl;
        cout << "   Description: " << e.description() << endl;
    }
    catch(const Base& e)        // 匹配父類異常的catch放在下部
    {
        cout << "catch(const Base& e)" << endl;
    }

    return 0;
}

3.2工程建議:

在工程中會(huì)定義一系列的異常類,每個(gè)類代表工程中可能出現(xiàn)的一種異常類型
代碼復(fù)用時(shí)可能需要重新解釋不同的異常類
在定義catch語句塊時(shí)推薦使用引用作為參數(shù)(防止拷貝構(gòu)造)

3.3、標(biāo)準(zhǔn)庫異常類族

(1)C++標(biāo)準(zhǔn)庫中提供了實(shí)用異常類族,都是從exception類派生的,主要有兩個(gè)分支
--logic_error(常用于程序中可避免的邏輯錯(cuò)誤)
--runtime_error(常用于程序中無法避免的惡性錯(cuò)誤)
標(biāo)準(zhǔn)庫中的異常:
C++語言(13)——C++異常處理

4.異常處理深度解析

問題1:main函數(shù)拋出異常

main函數(shù)中跑出異常會(huì)發(fā)生什么?如果異常不處理會(huì)傳到哪里?

#include <iostream>

using namespace std;

class Test 
{
public:
    Test() 
    {
        cout << "Test()"; 
        cout << endl;
    }

    ~Test() 
    {
        cout << "~Test()"; 
        cout << endl;
    }
};

int main()
{
    static Test t;

    throw 1;

    return 0;
}

實(shí)驗(yàn)結(jié)果證明,異常不被處理會(huì)導(dǎo)致程序會(huì)異常結(jié)束,并打印異常語句。
那么異常語句是哪里打印的?
如果異常無法被處理,terminate()函數(shù)會(huì)被自動(dòng)調(diào)用,該函數(shù)用于結(jié)束異常,同時(shí)在terminate()函數(shù)中會(huì)調(diào)用庫函數(shù)abort()函數(shù)終止程序(abort函數(shù)使得程序執(zhí)行異常并立即退出)。
C++語法支持自定義terminate()函數(shù)的實(shí)現(xiàn):
(1)定義一個(gè)無返回值無參數(shù)的函數(shù)(函數(shù)類型為void(*)()類型)
a)不能拋出異常
b)必須以某種方式結(jié)束當(dāng)前程序(abort/exit/…)
(2)調(diào)用set_terminate注冊自定義的terminate()函數(shù)
a)返回值為默認(rèn)的terminate函數(shù)入口地址。

#include <iostream>
#include <cstdlib>
#include <exception>

using namespace std;

void my_terminate()
{
    cout << "void my_terminate()" << endl;
    exit(1);
}

class Test 
{
public:
    Test() 
    {
        cout << "Test()"; 
        cout << endl;
    }

    ~Test() 
    {
        cout << "~Test()"; 
        cout << endl;
    }
};

int main()
{
    set_terminate(my_terminate);

    static Test t;

    throw 1;

    return 0;
}

問題2:析構(gòu)函拋出異常

析構(gòu)函數(shù)中拋出異常會(huì)怎么樣?

#include <iostream>
#include <cstdlib>
#include <exception>

using namespace std;

void my_terminate()
{
    cout << "void my_terminate()" << endl;
    // exit(1);
    abort();    // C++ 標(biāo)準(zhǔn)庫中terminate()函數(shù)調(diào)用的為abort直接結(jié)束程序,不會(huì)再去調(diào)用析構(gòu)函數(shù),防止析構(gòu)函數(shù)中還有異常扔出
}

class Test 
{
public:
    Test() 
    {
        cout << "Test()"; 
        cout << endl;
    }

    ~Test() 
    {
        cout << "~Test()"; 
        cout << endl;

        throw 2;    // terminate函數(shù)是整個(gè)程序釋放資源的最后機(jī)會(huì)
                    // 析構(gòu)函數(shù)中不能拋出異常,會(huì)導(dǎo)致terminate函數(shù)被多次調(diào)用,造成資源重復(fù)釋放

    }
};

int main()
{
    set_terminate(my_terminate);

    static Test t;

    throw 1;

    return 0;
}

實(shí)驗(yàn)結(jié)果證明在Linux這樣比較穩(wěn)定的環(huán)境中析構(gòu)函數(shù)中拋出異常會(huì)調(diào)用terminate()函數(shù)。貌似沒有什么問題,但對于某些嵌入式系統(tǒng),可能導(dǎo)致系統(tǒng)的不穩(wěn)定。
結(jié)論:
terminate函數(shù)是整個(gè)程序釋放資源的最后機(jī)會(huì)。
析構(gòu)函數(shù)中不能拋出異常,會(huì)導(dǎo)致terminate函數(shù)被多次調(diào)用,造成資源重復(fù)釋放。
C++ 標(biāo)準(zhǔn)庫中terminate()函數(shù)調(diào)用的為abort函數(shù),直接結(jié)束程序,不會(huì)再去調(diào)用析構(gòu)函數(shù),防止析構(gòu)函數(shù)中還有異常扔出

5.函數(shù)異常規(guī)格說明

5.1如何判斷一個(gè)函數(shù)是否 會(huì)拋出異常,會(huì)拋出那些異常?

C++語法提供了用于申明函數(shù)所拋出的異常,異常做為函數(shù)聲明的 修飾符寫函數(shù)聲明的后面。
// 可能拋出任何異常
void fun(void) ;
// 只能拋出int型異常
void fun(void) throw(int);
// 不能拋出異常
void fun(void) throw();

#include <iostream>
using namespace std;

void func() throw(int)
{
    cout << "func()";
    cout << endl;

    throw 'c';
}

int main()
{
    try 
    {
        func();
    } 
    catch(int) 
    {
        cout << "catch(int)";
        cout << endl;
    } 
    catch(char) 
    {
        cout << "catch(char)";
        cout << endl;
    }

    return 0;
}

異常規(guī)格說明的意義:
-提示函數(shù)調(diào)用這必須做好異常處理的準(zhǔn)備
-提示函數(shù)的維護(hù)者不要拋出其他異常
-異常規(guī)格 說明是函數(shù)接口的一部分。

5.2如果拋出的異常不在申明列表里會(huì)發(fā)生什么?

函數(shù)拋出的異常不在規(guī)格說明中,全局函數(shù)unexpected()會(huì)被調(diào)用
默認(rèn)的unexpected()函數(shù)會(huì)調(diào)用全局的terminate()函數(shù)
可以自定義unexpected()函數(shù)
--函數(shù)類型void(*)(void)
--能夠再次拋出異常
a)在次拋出的異常符合觸發(fā)函數(shù)的異常規(guī)格函數(shù)時(shí),程序恢復(fù)執(zhí)行
b)否則,調(diào)用terminate()函數(shù)結(jié)束程序
--調(diào)用set_unexpected()函數(shù)設(shè)置自定義的異常函數(shù),返回值為默認(rèn)的unexpected函數(shù)入口地址。
注意不是所有C++編譯器都支持這個(gè)標(biāo)準(zhǔn)行為,其中vs就不支持(會(huì)直接處理拋出的異常,盡管其不在異常規(guī)格申明中)。

#include <iostream>
#include <cstdlib>
#include <exception>

/**
函數(shù)拋出的異常不在規(guī)格說明中,全局函數(shù)unexpected()會(huì)被調(diào)用
    默認(rèn)的unexpected()函數(shù)會(huì)調(diào)用全局的terminate()函數(shù)
    可以自定義unexpected()函數(shù)
        --函數(shù)類型void(*)(void)
        --能夠再次拋出異常
            a)在次拋出的異常符合觸發(fā)函數(shù)的異常規(guī)格函數(shù)時(shí),程序恢復(fù)執(zhí)行
            b)否則,調(diào)用terminate()函數(shù)結(jié)束程序
        --調(diào)用set_unexpected()函數(shù)設(shè)置自定義的異常函數(shù),返回值為默認(rèn)的unexpected函數(shù)入口地址。
    注意不是所有C++編譯器都支持這個(gè)標(biāo)準(zhǔn)行為,其中vs就不支持(會(huì)直接處理拋出的異常,盡管其不在異常規(guī)格申明中)。
**/

using namespace std;

void my_unexpected()
{
    cout << "void my_unexpected()" << endl;
    // exit(1);
    throw 1;
}

void func() throw(int)
{
    cout << "func()";
    cout << endl;

    throw 'c';
}

int main()
{
    set_unexpected(my_unexpected);

    try 
    {
        func();
    } 
    catch(int) 
    {
        cout << "catch(int)";
        cout << endl;
    } 
    catch(char) 
    {
        cout << "catch(char)";
        cout << endl;
    }

    return 0;
}

6.try ...catch的其他寫法

1、try…catch用于分隔正常邏輯的代碼和異常處理代碼,可以直接將函數(shù)實(shí)現(xiàn)分隔為兩部分。
2、函數(shù)聲明和定義時(shí)可以直接指定拋出的異常類型,異常聲明成為函數(shù)的一部分可以提高代碼的可讀性。

#include <iostream>
#include <string>

using namespace std;

int func(int i, int j) throw(int, char)
{
    if( (0 < j) && (j < 10) )
    {
        return (i + j);
    }
    else
    {
        throw '0';
    }
}

void test(int i) try
{
    cout << "func(i, i) = " << func(i, i) << endl;
}
catch(int i)
{
    cout << "Exception: " << i << endl;
}
catch(...)
{
    cout << "Exception..." << endl;
}

int main(int argc, char *argv[])
{
    test(5);

    test(10);

    return 0;
}

顯然這種寫法可讀性降低。
本文參考唐老師課程,特此鳴謝。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI