【C++】面向對象之C++異常-007

第七章 C++異常


7.1 異沉裕基本概念


Bjarne Stroustrup說:提供異常的基本目的就是為了處理上面的問題∫嘉粒基本思想是:讓一個函數在發(fā)現(xiàn)了自己無法處理的錯誤時拋出(throw)一個異常谆扎,然后它的(直接或者間接)調用者能夠處理這個問題镰吆。也就是《C++ primer》中說的:將問題檢測和問題處理相分離。

一種思想:在所有支持異常處理的編程語言中(例如java)洪囤,要認識到的一個思想:在異常處理過程中徒坡,由問題檢測代碼可以拋出一個對象給問題處理代碼,通過這個對象的類型和內容瘤缩,實際上完成了兩個部分的通信喇完,通信的內容是“出現(xiàn)了什么錯誤”。當然剥啤,各種語言對異常的具體實現(xiàn)有著或多或少的區(qū)別锦溪,但是這個通信的思想是不變的不脯。

一句話:異常處理就是處理程序中的錯誤。所謂錯誤是指在程序運行的過程中發(fā)生的一些異常事件(如:除0溢出刻诊,數組下標越界防楷,所要讀取的文件不存在,空指針,內存不足等等)则涯。

回顧一下:我們以前編寫程序是如何處理異常复局?

在C語言的世界中,對錯誤的處理總是圍繞著兩種方法:一是使用整型的返回值標識錯誤粟判;二是使用errno宏(可以簡單的理解為一個全局整型變量)去記錄錯誤亿昏。當然C++中仍然是可以用這兩種方法的。

這兩種方法最大的缺陷就是會出現(xiàn)不一致問題档礁。例如有些函數返回1表示成功角钩,返回0表示出錯;而有些函數返回0表示成功呻澜,返回非0表示出錯递礼。

還有一個缺點就是函數的返回值只有一個,你通過函數的返回值表示錯誤代碼易迹,那么函數就不能返回其他的值宰衙。當然,你也可以通過指針或者C++的引用來返回另外的值睹欲,但是這樣可能會令你的程序略微晦澀難懂供炼。

c++異常機制相比C語言異常處理的優(yōu)勢?

  • 函數的返回值可以忽略,但異常不可忽略窘疮。如果程序出現(xiàn)異常袋哼,但是沒有被捕獲,程序就會終止闸衫,這多少會促使程序員開發(fā)出來的程序更健壯一點涛贯。而如果使用C語言的error宏或者函數返回值,調用者都有可能忘記檢查蔚出,從而沒有對錯誤進行處理弟翘,結果造成程序莫名其面的終止或出現(xiàn)錯誤的結果。
  • 整型返回值沒有任何語義信息骄酗。而異常卻包含語義信息稀余,有時你從類名就能夠體現(xiàn)出來。
  • 整型返回值缺乏相關的上下文信息趋翻。異常作為一個類睛琳,可以擁有自己的成員,這些成員就可以傳遞足夠的信息。
  • 異常處理可以在調用跳級师骗。這是一個代碼編寫時的問題:假設在有多個函數的調用棧中出現(xiàn)了某個錯誤历等,使用整型返回碼要求你在每一級函數中都要進行處理。而使用異常處理的棧展開機制辟癌,只需要在一處進行處理就可以了寒屯,不需要每級函數都處理。
//如果判斷返回值愿待,那么返回值是錯誤碼還是結果浩螺?
//如果不判斷返回值,那么b==0時候仍侥,程序結果已經不正確
//A寫的代碼
int A_MyDivide(int a,int b){
    if (b == 0){
        return -1;
    }

    return a / b;
}

//B寫的代碼
int B_MyDivide(int a,int b){

    int ba = a + 100;
    int bb = b;

    int ret = A_MyDivide(ba, bb);  //由于B沒有處理異常要出,導致B結果運算錯誤

    return ret;
}

//C寫的代碼
int C_MyDivide(){

    int a = 10;
    int b = 0;

    int ret = B_MyDivide(a, b); //更嚴重的是,由于B沒有繼續(xù)拋出異常农渊,導致C的代碼沒有辦法捕獲異常
    if (ret == -1){
        return -1;
    }
    else{
        return ret;
    }
}

//所以,我們希望:
//1.異常應該捕獲患蹂,如果你捕獲,可以砸紊,那么異常必須繼續(xù)拋給上層函數,你不處理传于,不代表你的上層不處理
//2.這個例子,異常沒有捕獲的結果就是運行結果錯的一塌糊涂醉顽,結果未知沼溜,未知的結果程序沒有必要執(zhí)行下去

7.2 異常語法


7.2.1 異常基本語法

int A_MyDivide(int a, int b){
    if (b == 0){
        throw 0;
    }

    return a / b;
}

//B寫的代碼 B寫代碼比較粗心游添,忘記處理異常
int B_MyDivide(int a, int b){

    int ba = a;
    int bb = b;

    int ret = A_MyDivide(ba, bb) + 100;  //由于B沒有處理異常系草,導致B結果運算錯誤

    return ret;
}

//C寫的代碼
int C_MyDivide(){

    int a = 10;
    int b = 0;

    int ret = 0;

//沒有處理異常,程序直接中斷執(zhí)行
#if 1 
    ret = B_MyDivide(a, b);

//處理異常
#else 
    try{
        ret = B_MyDivide(a, b); //更嚴重的是唆涝,由于B沒有繼續(xù)拋出異常找都,導致C的代碼沒有辦法捕獲異常
    }
    catch (int e){
        cout << "C_MyDivide Call B_MyDivide 除數為:" << e << endl;
    }
#endif
    
    return ret;
}

int main(){

    C_MyDivide();

    system("pause");
    return EXIT_SUCCESS;
}

總結:

  • 若有異常則通過throw操作創(chuàng)建一個異常對象并拋出。
  • 將可能拋出異常的程序段放到try塊之中廊酣。
  • 如果在try段執(zhí)行期間沒有引起異常能耻,那么跟在try后面的catch字句就不會執(zhí)行。
  • catch子句會根據出現(xiàn)的先后順序被檢查亡驰,匹配的catch語句捕獲并處理異常(或繼續(xù)拋出異常)
  • 如果匹配的處理未找到晓猛,則運行函數terminate將自動被調用,其缺省功能調用abort終止程序凡辱。
  • 處理不了的異常鞍帝,可以在catch的最后一個分支,使用throw煞茫,向上拋。

c++異常處理使得異常的引發(fā)和異常的處理不必在一個函數中,這樣底層的函數可以著重解決具體問題续徽,而不必過多的考慮異常的處理蚓曼。上層調用者可以在適當的位置設計對不同類型異常的處理。

7.2.2 異常嚴格類型匹配

異常機制和函數機制互不干涉,但是捕捉方式是通過嚴格類型匹配钦扭。

void TestFunction(){
    
    cout << "開始拋出異常..." << endl;
    //throw 10; //拋出int類型異常
    //throw 'a'; //拋出char類型異常
    //throw "abcd"; //拋出char*類型異常
    string ex = "string exception!";
    throw ex;

}

int main(){

    try{
        TestFunction();
    }
    catch (int){
        cout << "拋出Int類型異常!" << endl;
    }
    catch (char){
        cout << "拋出Char類型異常!" << endl;
    }
    catch (char*){
        cout << "拋出Char*類型異常!" << endl;
    }
    catch (string){
        cout << "拋出string類型異常!" << endl;
    }
    //捕獲所有異常
    catch (...){
        cout << "拋出其他類型異常!" << endl;
    }


    system("pause");
    return EXIT_SUCCESS;
}

7.2.3 棧解旋(unwinding)

異常被拋出后纫版,從進入try塊起,到異常被拋擲前客情,這期間在棧上構造的所有對象其弊,都會被自動析構。析構的順序與構造的順序相反膀斋,這一過程稱為棧的解旋(unwinding).

class Person{
public:
    Person(string name){
        mName = name;
        cout << mName << "對象被創(chuàng)建!" << endl;
    }
    ~Person(){
        cout << mName << "對象被析構!" << endl;
    }
public:
    string mName;
};

void TestFunction(){
    
    Person p1("aaa");
    Person p2("bbb");
    Person p3("ccc");

    //拋出異常
    throw 10;
}

int main(){

    try{
        TestFunction();
    }
    catch (...){
        cout << "異常被捕獲!" << endl;
    }

    system("pause");
    return EXIT_SUCCESS;
}

7.2.4 異常接口聲明

  • 為了加強程序的可讀性梭伐,可以在函數聲明中列出可能拋出異常的所有類型,例如:void func() throw(A,B,C);這個函數func能夠且只能拋出類型A,B,C及其子類型的異常仰担。
  • 如果在函數聲明中沒有包含異常接口聲明糊识,則此函數可以拋任何類型的異常,例如:void func()
  • 一個不拋任何類型異常的函數可聲明為:void func() throw()
  • 如果一個函數拋出了它的異常接口聲明所不允許拋出的異常,unexcepted函數會被調用摔蓝,該函數默認行為調用terminate函數中斷程序赂苗。
//可拋出所有類型異常
void TestFunction01(){
    throw 10;
}

//只能拋出int char char*類型異常
void TestFunction02() throw(int,char,char*){
    string exception = "error!";
    throw exception;
}

//不能拋出任何類型異常
void TestFunction03() throw(){
    throw 10;
}

int main(){

    try{
        //TestFunction01();
        //TestFunction02();
        //TestFunction03();
    }
    catch (...){
        cout << "捕獲異常!" << endl;
    }
    
    system("pause");
    return EXIT_SUCCESS;
}

請分別在qt vs linux下做測試! Qt and Linux 正確!

7.2.5 異常變量生命周期

  • throw的異常是有類型的,可以是數字贮尉、字符串拌滋、類對象。
  • throw的異常是有類型的猜谚,catch需嚴格匹配異常類型败砂。
class MyException
{
public:
    MyException(){
        cout << "異常變量構造" << endl;
    };
    MyException(const MyException & e)
    {
        cout << "拷貝構造" << endl;
    }
    ~MyException()
    {
        cout << "異常變量析構" << endl;
    }
};
void DoWork()
{
    throw new MyException(); //test1 2都用 throw MyExecption();
}

void test01()
{
    try
    {
        DoWork();
    }
    catch (MyException e)
    {
        cout << "捕獲 異常" << endl;
    }
}
void test02()
{
    try
    {
        DoWork();
    }
    catch (MyException &e)
    {
        cout << "捕獲 異常" << endl;
    }
}

void test03()
{
    try
    {
        DoWork();
    }
    catch (MyException *e)
    {
        cout << "捕獲 異常" << endl;
        delete e;
    }
}

7.2.6 異常的多態(tài)使用

//異常基類
class BaseException{
public:
    virtual void printError(){};
};

//空指針異常
class NullPointerException : public BaseException{
public:
    virtual void printError(){
        cout << "空指針異常!" << endl;
    }
};
//越界異常
class OutOfRangeException : public BaseException{
public:
    virtual void printError(){
        cout << "越界異常!" << endl;
    }
};

void doWork(){

    throw NullPointerException();
}

void test()
{
    try{
        doWork();
    }
    catch (BaseException& ex){
        ex.printError();
    }
}

7.3 C++標準異常庫


7.3.1 標準庫介紹

標準庫中也提供了很多的異常類龄毡,它們是通過類繼承組織起來的吠卷。異常類繼承層級結構圖如下:


在這里插入圖片描述

每個類所在的頭文件在圖下方標識出來。

標準異常類的成員:

  • ① 在上述繼承體系中沦零,每個類都有提供了構造函數祭隔、復制構造函數、和賦值操作符重載路操。
  • logic_error類及其子類疾渴、runtime_error類及其子類,它們的構造函數是接受一個string類型的形式參數屯仗,用于異常信息的描述
  • ③ 所有的異常類都有一個what()方法搞坝,返回const char* 類型(C風格字符串)的值,描述異常信息魁袜。

標準異常類的具體描述:

異常名稱 描述
exception 所有標準異常類的父類
bad_alloc 當operator new and operator new[]桩撮,請求分配內存失敗時
bad_exception 這是個特殊的異常敦第,如果函數的異常拋出列表里聲明了bad_exception異常,當函數內部拋出了異常拋出列表中沒有的異常店量,這是調用的unexpected函數中若拋出異常芜果,不論什么類型,都會被替換為bad_exception類型
bad_typeid 使用typeid操作符融师,操作一個NULL指針右钾,而該指針是帶有虛函數的類,這時拋出bad_typeid異常
bad_cast 使用dynamic_cast轉換引用失敗的時候
ios_base::failure io操作過程出現(xiàn)錯誤
logic_error 邏輯錯誤旱爆,可以在運行前檢測的錯誤
runtime_error 運行時錯誤舀射,僅在運行時才可以檢測的錯誤

logic_error的子類:

異常名稱 描述
length_error 試圖生成一個超出該類型最大長度的對象時,例如vector的resize操作
domain_error 參數的值域錯誤怀伦,主要用在數學函數中脆烟。例如使用一個負值調用只能操作非負數的函數
out_of_range 超出有效范圍
invalid_argument 參數不合適。在標準庫中空镜,當利用string對象構造bitset時浩淘,而string中的字符不是’0’或’1’的時候,拋出該異常

runtime_error的子類:

異常名稱 描述
range_error 計算結果超出了有意義的值域范圍
overflow_error 算術計算上溢
underflow_error 算術計算下溢
invalid_argument 參數不合適吴攒。在標準庫中张抄,當利用string對象構造bitset時,而string中的字符不是’0’或’1’的時候洼怔,拋出該異常
#include<stdexcept>
class Person{
public:
    Person(int age){
        if (age < 0 || age > 150){
            throw out_of_range("年齡應該在0-150歲之間!");
        }
    }
public:
    int mAge;
};

int main(){

    try{
        Person p(151);
    }
    catch (out_of_range& ex){
        cout << ex.what() << endl;
    }
    
    system("pause");
    return EXIT_SUCCESS;
}

7.3.2 編寫自己的異常類

  1. 編寫自己的異常類署惯?
    ① 標準庫中的異常是有限的;
    ② 在自己的異常類中镣隶,可以添加自己的信息极谊。(標準庫中的異常類值允許設置一個用來描述異常的字符串)。

  2. 如何編寫自己的異常類安岂?
    ① 建議自己的異常類要繼承標準異常類轻猖。因為C++中可以拋出任何類型的異常,所以我們的異常類可以不繼承自標準異常域那,但是這樣可能會導致程序混亂咙边,尤其是當我們多人協(xié)同開發(fā)時。
    ② 當繼承標準異常類時次员,應該重載父類的what函數和虛析構函數败许。
    ③ 因為棧展開的過程中,要復制異常類型淑蔚,那么要根據你在類中添加的成員考慮是否提供自己的復制構造函數市殷。

//自定義異常類
class MyOutOfRange:public exception
{
public:
    MyOutOfRange(const string  errorInfo)
    {
        this->m_Error = errorInfo;
    }

    MyOutOfRange(const char * errorInfo)
    {
        this->m_Error = string( errorInfo);
    }

    virtual  ~MyOutOfRange()
    {
    
    }
    virtual const char *  what() const
    {
        return this->m_Error.c_str() ;
    }

    string m_Error;

};

class Person
{
public:
    Person(int age)
    {
        if (age <= 0 || age > 150)
        {
            //拋出異常 越界
            //cout << "越界" << endl;
            //throw  out_of_range("年齡必須在0~150之間");

            //throw length_error("長度異常");
            throw MyOutOfRange(("我的異常 年齡必須在0~150之間"));
        }
        else
        {
            this->m_Age = age;
        }
        
    }

    int m_Age;
};


void test01()
{
    try
    {
        Person p(151);
    }
    catch ( out_of_range & e )
    {
        cout << e.what() << endl;
    }
    catch (length_error & e)
    {
        cout << e.what() << endl;
    }
    catch (MyOutOfRange e)
    {
        cout << e.what() << endl;
    }
}
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市刹衫,隨后出現(xiàn)的幾起案子醋寝,更是在濱河造成了極大的恐慌搞挣,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件甥桂,死亡現(xiàn)場離奇詭異柿究,居然都是意外死亡,警方通過查閱死者的電腦和手機黄选,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來婶肩,“玉大人办陷,你說我怎么就攤上這事÷杉撸” “怎么了民镜?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長险毁。 經常有香客問我制圈,道長,這世上最難降的妖魔是什么畔况? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任鲸鹦,我火速辦了婚禮,結果婚禮上跷跪,老公的妹妹穿的比我還像新娘馋嗜。我一直安慰自己,他們只是感情好吵瞻,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布葛菇。 她就那樣靜靜地躺著,像睡著了一般橡羞。 火紅的嫁衣襯著肌膚如雪眯停。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天卿泽,我揣著相機與錄音莺债,去河邊找鬼。 笑死又厉,一個胖子當著我的面吹牛九府,可吹牛的內容都是我干的。 我是一名探鬼主播覆致,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼侄旬,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了煌妈?” 一聲冷哼從身側響起儡羔,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤宣羊,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后汰蜘,有當地人在樹林里發(fā)現(xiàn)了一具尸體仇冯,經...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年族操,在試婚紗的時候發(fā)現(xiàn)自己被綠了苛坚。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡色难,死狀恐怖泼舱,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情枷莉,我是刑警寧澤娇昙,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站笤妙,受9級特大地震影響冒掌,放射性物質發(fā)生泄漏。R本人自食惡果不足惜蹲盘,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一股毫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧辜限,春花似錦皇拣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至毫深,卻和暖如春吩坝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背哑蔫。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工钉寝, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人闸迷。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓嵌纲,卻偏偏與公主長得像,于是被迫代替她去往敵國和親腥沽。 傳聞我的和親對象是個殘疾皇子逮走,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內容