C++中的異常處理

C語言異常處理

異常的概念

  • 異常的說明
    • 程序在運(yùn)行過程中可能產(chǎn)生異常
    • 異常(Exception)與Bug的區(qū)別
      • 異常是程序運(yùn)行時(shí)可預(yù)料的執(zhí)行分支
      • Bug是程序中的錯(cuò)誤专挪,是不被預(yù)期的運(yùn)行方式
  • 異常(Exception)和Bug的對(duì)比:
    • 異常
      • 運(yùn)行時(shí)產(chǎn)生除0的情況
      • 需要打開的外部文件不存在
      • 數(shù)組訪問時(shí)越界
    • Bug
      • 使用野指針
      • 堆數(shù)組使用結(jié)束后未釋放
      • 選擇排序無法處理長度為0的數(shù)組

異常處理

C語言經(jīng)典處理方式:if ... else ...

void func()
{
    if(判斷是否產(chǎn)生異常)
    {
        正常情況代碼邏輯真友;
    }
    else
    {
        異常情況代碼邏輯尖奔;
    }
}

這里舉一個(gè)例子:

#include <iostream>
#include <string>

using namespace std;

//除法判斷
double divide(double a, double b, int* valid)
{
    const double delta = 0.000000000000001;
    double ret = 0;
    
    //強(qiáng)制判斷
    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;
}

運(yùn)行結(jié)果為:

Divided by zero...

這種做法也有缺陷:

  • divide函數(shù)有3個(gè)參數(shù),難以理解其用法
  • divide函數(shù)調(diào)用后必須判斷valid代表的結(jié)果
    • 當(dāng)valid為true時(shí)对省,運(yùn)算結(jié)果正常
    • 當(dāng)valid為false時(shí)吭练,運(yùn)算過程出現(xiàn)異常

所以這里再介紹另外一種方法:

  • 通過setjmp( ) 和 longjmp( ) 進(jìn)行優(yōu)化
    • 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

舉一個(gè)例子:

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

using namespace std;

//創(chuàng)建一個(gè)靜態(tài)的結(jié)構(gòu)體
static jmp_buf env;

double divide(double a, double b)
{
    const double delta = 0.000000000000001;
    double ret = 0;
    
    if( !((-delta < b) && (b < delta)) )
    {
        ret = a / b;
    }
    else
    {
        //假如程序執(zhí)行到這里伤极,就恢復(fù)setjmp保存的上下文,然后返回1
        longjmp(env, 1);
    }
    
    return ret;
}

int main(int argc, char *argv[])
{   
    //將當(dāng)前的上下文保存到env結(jié)構(gòu)體中
    if( setjmp(env) == 0 )
    {
        double r = divide(1, 0);
        
        cout << "r = " << r << endl;
    }
    else
    {
        //返回1以后姨伤,與0不相等哨坪,就執(zhí)行到這里
        cout << "Divided by zero..." << endl;
    }
    
    return 0;
}

執(zhí)行結(jié)果為:

Divided by zero...

這種方法的缺陷是:

  • setjmp( )和longjmp( )的引入
    • 必然涉及到使用全局變量
    • 暴力跳轉(zhuǎn)導(dǎo)致代碼可讀性降低
    • 本質(zhì)還是if ... else ... 異常處理方式

最后再介紹一種方法,還是用 if ... else ... 來做乍楚,但是使用宏定義來說明語義:

#include <iostream>
#include <string>

using namespace std;

#define SUCCESS           0 
#define INVALID_POINTER   -1
#define INVALID_LENGTH    -2
#define INVALID_PARAMETER -3

int MemSet(void* dest, unsigned int length, unsigned char v)
{
    if( dest == NULL )
    {
        return INVALID_POINTER;
    }
    
    if( length < 4 )
    {
        return INVALID_LENGTH;
    }
    
    if( (v < 0) || (v > 9) )
    {
        return INVALID_PARAMETER;
    }
    
    unsigned char* p = (unsigned char*)dest;
    
    for(int i=0; i<length; i++)
    {
        p[i] = v;
    }
    
    return SUCCESS;
}

int main(int argc, char *argv[])
{
    int ai[5];
    int ret = MemSet(ai, sizeof(ai), 0);
    
    if( ret == SUCCESS )
    {
    }
    else if( ret == INVALID_POINTER )
    {
    }
    else if( ret == INVALID_LENGTH )
    {
    }
    else if( ret == INVALID_PARAMETER )
    {
    }
    
    return ret;
}

小結(jié)

  • 程序中不可避免的會(huì)發(fā)生異常
  • 異常是在開發(fā)階段就可以預(yù)見的運(yùn)行時(shí)問題
  • C語言中通過經(jīng)典的 if ... else ... 方式處理異常
  • C++中存在更好的異常處理方式

C++的異常處理

異常處理介紹

C++內(nèi)置了異常處理的語法元素 try ... catch ...

  • try語句處理正常代碼邏輯
  • catch語句處理異常情況
  • try語句中的異常由對(duì)應(yīng)的catch語句處理
  • 語法:
try
{
    double r = divide(1, 0);
}
catch(...)
{
    cout << "Divided by zero..." << endl;
}
  • C++通過throw語句拋出異常信息
double divide(double a, double b)
{
    const double delta = 0.0000000000001;
    double ret = 0;
    if(!((-delta < b) && (b < delta)))
    {
        ret = a / b;
    }
    else
    {
        //產(chǎn)生除0異常
        throw 0;
    }

    return ret;
}

異常處理分析

  • 這里對(duì)C++異常處理分析一下:
    • throw拋出的異常必須被catch處理
      • 當(dāng)前函數(shù)能夠處理異常当编,程序繼續(xù)往下執(zhí)行
      • 當(dāng)前函數(shù)無法處理異常,則函數(shù)停止執(zhí)行徒溪,并返回

未被處理的異常會(huì)順著函數(shù)調(diào)用棧向上傳播忿偷,知道被處理為止,否則程序?qū)⑼V箞?zhí)行臊泌。

function1 ==> function2 ==> function3
          <==           <== throw1;

這里舉一個(gè)例子:

#include <iostream>
#include <string>

using namespace std;

double divide(double a, double b)
{
    const double delta = 0.000000000000001;
    double ret = 0;
    
    if( !((-delta < b) && (b < delta)) )
    {
        ret = a / b;
    }
    else
    {
        //用throw拋出異常信息
        throw 0;
    }
    
    return ret;
}

int main(int argc, char *argv[])
{    
    try
    {
        double r = divide(1, 0);
            
        cout << "r = " << r << endl;
    }
    catch(...)
    {
        //try語句中拋出的異常在這里接受并處理
        cout << "Divided by zero..." << endl;
    }
    
    return 0;
}

繼續(xù)學(xué)習(xí) try... catch ...的知識(shí)點(diǎn):

  • 同一個(gè)try 語句可以跟上多個(gè)catch語句

    • catch語句可以定義具體處理的異常類型
    • 不同類型的異常由不同的catch語句負(fù)責(zé)處理
    • try語句中可以拋出任何類型的異常
    • catch( ... ) 用于處理所有類型的異常
    • 任何異常都只能被捕獲(catch)一次
  • 異常處理的匹配規(guī)則:

//異常處理匹配時(shí)牵舱,不進(jìn)行任何的類型轉(zhuǎn)換
|   try
|   {
|       throw 1;
|   }   
|   catch(Type1 t1)
|   {
|   }
|   catch(Type2 t2)
|   {
|   }
|   catch(TypeN tn)
|   {
|   }
v
異常拋出后,自上而下嚴(yán)格匹配每一個(gè)catch語句處理的類型

這里用一個(gè)例子來試驗(yàn)一下匹配規(guī)則:

#include <iostream>
#include <string>

using namespace std;

void Demo1()
{
    try
    {   
        //這里拋出一個(gè)字符
        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()
{
    //這里拋出string類字符串
    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;
}

執(zhí)行結(jié)果如下:

catch(char c)
catch(string ss)

catch再拋出異常

在try ... catch ... 語句中缺虐,catch語句塊中可以拋出異常

try
{
    func();
}
catch(int i)
{
    //將捕獲的異常重新拋出
    throw i;
}
catch(...)
{
    //將捕獲的異常重新拋出
    throw;
}

//catch中拋出的異常需要外層的try ... catch ...捕獲

可是為什么要重新拋出異常呢?因?yàn)椋?/p>

catch中捕獲的異辰阜玻可以被重新解釋后拋出高氮,這樣就可以在工程開發(fā)中使用這樣的方式統(tǒng)一異常類型。
//工程開發(fā)
通過調(diào)用 MyFunc 獲得 func函數(shù)的功能和統(tǒng)一的異常信息
    |
    | 調(diào)用
    V

//私有庫
void MyFunc(int i);
/*異常類型為Exception*/
    |
    | 封裝
    V

//第三方庫
void func(int i);
/*異常類型為int*/

這里舉一個(gè)例子:

#include <iostream>
#include <string>

using namespace std;

void Demo()
{
    
    try
    {
        try
        {
            //這里拋出的異常由內(nèi)層try ... catch ...來捕獲處理
            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)
{
    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;
}

運(yùn)行結(jié)果為:

Inner: catch(...)
Outer: catch(...)
Exception Info: Timeout Exception

自定義異常類型

自定義類類型及匹配:

  • 異常的類型可以是自定義類類型
  • 對(duì)于類類型異常的匹配依舊是自上而下嚴(yán)格匹配
  • 賦值兼容性原則在異常匹配中依然適用
  • 一般而言
    • 匹配子類異常的catch放在上部
    • 匹配父類異常的catch放在下部

工程中的異常類:

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

這里舉一個(gè)例子:

#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:
                //這里直接拋出一個(gè)類
                throw Exception(-1, "Invalid Parameter");
                break;
            case -2:
                //這里直接拋出一個(gè)類
                throw Exception(-2, "Runtime Exception");
                break;
            case -3:
                //這里直接拋出一個(gè)類
                throw Exception(-3, "Timeout Exception");
                break;
        }
    }
}

int main(int argc, char *argv[])
{
    try
    {
        MyFunc(11);
    }
    //接受到的時(shí)候 判斷為引用類型
    catch(const Exception& e)
    {
        cout << "Exception Info: " << endl;
        cout << "   ID: " << e.id() << endl;
        cout << "   Description: " << e.description() << endl;
    }
    //接受到的時(shí)候 判斷為引用類型
    catch(const Base& e)
    {
        cout << "catch(const Base& e)" << endl;
    }
    
    return 0;
}

運(yùn)行結(jié)果為:

Exception Info: 
   ID: -3
   Description: Timeout Exception

C++標(biāo)準(zhǔn)庫的異常類族

在C++標(biāo)準(zhǔn)庫中提供了實(shí)用異常類族

  • 標(biāo)準(zhǔn)庫中的異常都是從 exception 類派生的
  • exception 類有兩個(gè)主要的分支
    • logic_error
      • 常用于程序中的可避免邏輯錯(cuò)誤
    • runtime_error
      • 常用于程序中無法避免的惡性錯(cuò)誤

這里演示一下如何使用:

#include <iostream>
#include <string>
#include "Array.h"
#include "HeapArray.h"

using namespace std;

void TestArray()
{
    Array<int, 5> a;
    
    /*
        這里如果越界會(huì)拋出異常:
        throw out_of_range("T& Array<T, N>::operator[] (int index)");
        throw out_of_range("T Array<T, N>::operator[] (int index) const");
        */
    for(int i=0; i<a.length(); i++)
    {
        a[i] = i;
    }
        
    for(int i=0; i<a.length(); i++)
    {
        cout << a[i] << endl;
    }
}

void TestHeapArray()
{
    HeapArray<double>* pa = HeapArray<double>::NewInstance(5);
    
    if( pa != NULL )
    {
        HeapArray<double>& array = pa->self();
        
        /*
        這里如果越界會(huì)拋出異常:
        throw out_of_range("T& HeapArray<T>::operator [] (int index)");
        throw out_of_range("T HeapArray<T>::operator [] (int index) const");
        */
        for(int i=0; i<array.length(); i++)
        {
            array[i] = i;
        }
            
        for(int i=0; i<array.length(); i++)
        {
            cout << array[i] << endl;
        }
    }
    
    delete pa;
}

int main(int argc, char *argv[])
{
    
    try
    {
        TestArray();
        
        cout << endl;
        
        TestHeapArray();
    }
    catch(...)
    {
        cout << "Exception" << endl;
    }
    
    return 0;
}

小結(jié)

  • C++中直接支持異常處理的概念
  • try ... catch ...是C++中異常處理的專用語句
  • try 語句處理正常代碼邏輯罪裹,catch 語句處理異常情況
  • 同一個(gè) try 語句可以跟上多個(gè) catch 語句
  • 異常處理必須嚴(yán)格匹配,不進(jìn)行任何的類型轉(zhuǎn)換
  • catch語句塊中可以拋出異常
  • 異常的類型可以是自定義類類型
  • 賦值兼容性原則在異常匹配中依然適用
  • 標(biāo)準(zhǔn)庫中的異常都是從exception類派生的
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市状共,隨后出現(xiàn)的幾起案子套耕,更是在濱河造成了極大的恐慌,老刑警劉巖峡继,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件冯袍,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡碾牌,警方通過查閱死者的電腦和手機(jī)康愤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來舶吗,“玉大人征冷,你說我怎么就攤上這事∈那恚” “怎么了检激?”我有些...
    開封第一講書人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長踊赠。 經(jīng)常有香客問我呵扛,道長,這世上最難降的妖魔是什么筐带? 我笑而不...
    開封第一講書人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任今穿,我火速辦了婚禮,結(jié)果婚禮上伦籍,老公的妹妹穿的比我還像新娘蓝晒。我一直安慰自己,他們只是感情好帖鸦,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開白布芝薇。 她就那樣靜靜地躺著,像睡著了一般作儿。 火紅的嫁衣襯著肌膚如雪洛二。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評(píng)論 1 308
  • 那天攻锰,我揣著相機(jī)與錄音晾嘶,去河邊找鬼。 笑死娶吞,一個(gè)胖子當(dāng)著我的面吹牛垒迂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播妒蛇,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼机断,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼楷拳!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起吏奸,我...
    開封第一講書人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤欢揖,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后苦丁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體浸颓,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年旺拉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了产上。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蛾狗,死狀恐怖晋涣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情沉桌,我是刑警寧澤谢鹊,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站留凭,受9級(jí)特大地震影響佃扼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蔼夜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一兼耀、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧求冷,春花似錦瘤运、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至韭山,卻和暖如春郁季,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背钱磅。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來泰國打工巩踏, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人续搀。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像菠净,于是被迫代替她去往敵國和親禁舷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子彪杉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法牵咙,內(nèi)部類的語法派近,繼承相關(guān)的語法,異常的語法洁桌,線程的語...
    子非魚_t_閱讀 31,663評(píng)論 18 399
  • 轉(zhuǎn)載:http://www.cnblogs.com/lulipro/p/7504267.html 一渴丸、異常簡(jiǎn)介 程...
    SinX竟然被占用了閱讀 977評(píng)論 2 2
  • Java異常類型 所有異常類型都是Throwable的子類,Throwable把異常分成兩個(gè)不同分支的子類Erro...
    予別她閱讀 932評(píng)論 0 2
  • 六種異常處理的陋習(xí) 你覺得自己是一個(gè)Java專家嗎另凌?是否肯定自己已經(jīng)全面掌握了Java的異常處理機(jī)制谱轨?在下面這段代...
    Executing閱讀 1,334評(píng)論 0 6
  • 夜色朦朧 一顆顆飽滿的橘 像天使的眼 像嬰兒的臉 那是熟透了的渴望 主人卻炫耀似的 不筑圍墻 任這誘人招展 甚至延...
    喜樂心記閱讀 228評(píng)論 1 2