關于面向對象設計的重要特性--多態性到上一節純虛函數和抽象類就講完了。這一講是本C++編程入門教程的最后一講--異常處理。

       我們開發的軟件一般按照正常的流程操作時運行不會出問題,但是用戶不一定會根據軟件工程師的想法來操作軟件,而且往往隨機性很大,另外,軟件的運行環境也會改變,例如硬盤空間不足、文件被移走,這些都可能會導致軟件出現異常,甚至崩潰。所以我們進行軟件開發時要充分考慮異常的捕捉和處理。

       一.異常處理的基本思想

       進行異常處理的目標是,使軟件具有容錯能力,在出現運行環境或者異常操作等問題時,程序能夠繼續往下運行,必要時彈出提示信息。

       軟件開發中往往每個函數都有自己的分工,很多出現錯誤的函數都不會處理錯誤,而是產生一個異常讓調用者捕捉和處理。如果調用者也不能處理此異常,則異常就會被繼續向上級調用者傳遞,這個傳遞過程會一直持續到異常能被處理為止。如果程序最終沒能處理這個異常,那么它就會被傳遞給C++的庫函數terminate,然后terminate會調用abort函數終止程序。

        二.C++異常處理的語法

        異常處理機制是靠try、throw和catch語句實現的。

        throw語句的形式為:

        throw 表達式

        try塊的語法形式為:

        try
        {
                  復合語句
        }
        catch(異常類型的聲明)
        {
                  復合語句
        }
        catch(異常類型的聲明)
        {
                  復合語句
        }
        ...

       先說說throw語句,當某段程序有了不能處理的異常時,就可以用“throw 表達式”的方式傳遞這個異常給調用者。這里throw后的表達式在語法上與return后的表達式類似。

       再來看try塊的try子句,子句后括號里的復合語句就是被監測的程序段。如果某段程序或者調用的某個函數可能會產生異常,就把它放到try后。當try子句后的程序段發生異常時,程序段中throw就會拋出這個異常。

       最后來看try的catch子句,catch子句后括號里的異常類型的聲明,在語法上與函數的形參類似,可以是某個類型(包括類)的值也可以是引用,它指明了此catch子句用來處理何種類型的異常。當try子句中的異常拋出后,每個catch子句會被依次檢查,哪個catch子句的異常類型的聲明與拋出異常的類型一致,就由哪個catch子句來處理此異常。catch后異常類型的聲明部分可以是一個省略號,形式如:catch(...),這種形式的catch子句可以處理任何類型的異常,它只能放到try塊所有其他catch語句之后。

       可見,如果try監測的某段程序多個地方需要拋出異常,那么throw后應該跟不同類型的表達式來區分,而不應該只通過不同的值區分。

雞啄米:C++編程入門系列之五十(異常處理)

       講了異常處理的語法形式,據此再來說說異常處理的執行過程:

       1.程序正常執行到try塊的try子句,然后執行try子句后的復合語句,也就是被監測的程序段。

       2.如果try子句后的程序段正常執行了,沒有發生任何異常,那么此try塊的所有catch子句將不被執行,程序直接跳轉到整個try塊(包括try子句和所有catch子句)后繼續執行。

       3.如果try子句后的程序段或者此程序段中的任何調用函數發生了異常,并通過throw拋出了這個異常,則此try塊的所有catch子句會按其出現的順序被檢查。若沒有找到匹配的處理程序則繼續檢查外層的try塊。如果一直找不到則此過程會繼續到最外層的try塊被檢查。這里有兩種結果:a.找到了匹配的處理程序,則相應catch子句捕捉異常,將異常對象拷貝給catch的參數,如果此參數是引用則它指向異常對象。catch的參數被這樣初始化以后,此catch子句對應的try子句后的程序段中,從開頭到異常拋出位置之間構造的所有對象進行析構,析構順序與構造順序相反。然后catch處理程序被執行,最后程序跳轉到try塊之后的語句執行。b.始終沒有找到匹配的處理程序,則運行C++庫函數terminate,而terminate函數調用abort函數終止程序。

       雞啄米給大家舉個異常處理的例子,大家知道我們只能對非負實數求平方根,若是負數就應該處理此異常。例程如下:

       #include <iostream>
       #include "math.h"
       using namespace std;
       double GetSqrt(double x);       // 求平方根的函數的原型聲明
       int main()
       {
                try
                {
                           // 由于求平方根運算有可能出現對負數運算的異常,所以放到try塊中
                          cout << "9.0的平方根是 " << GetSqrt(9.0) << endl;
                          cout << "-1.0的平方根是 " << GetSqrt(-1.0) << endl;
                          cout << "16.0的平方根是 " << GetSqrt(16.0) << endl;
                }
               catch(double y)    // 捕捉double型異常
               {
                          cout << "發生對負數" << y << "求平方根的異常。" << endl;
               }
               cout << "程序繼續運行完畢。" << endl;
               return 0;
       }
       double GetSqrt(double x)
       {
               if (x < 0)
                         throw x;     // 如果x為負數,則拋出一個double型異常
               return sqrt(x);
       }

       程序運行結果為:

       9.0的平方根是 3
       發生對負數-1求平方根的異常。
       程序繼續運行完畢。

       根據結果可以看出,程序在運行cout << "-1.0的平方根是 " << GetSqrt(-1.0) << endl;時GetSqrt函數拋出異常,異常被main函數中catch子句捕捉,輸出信息后,程序跳轉到main函數最后一句輸出"程序繼續運行完畢。"。而try子句后的cout << "16.0的平方根是 " << GetSqrt(16.0) << endl;沒有被執行。這是因為異常拋出后會按照catch子句出現的順序依次檢查,當找到匹配的catch處理程序時后面的所有catch子句就被忽略。根據這個原理,若catch(...)放到前面則其后的所有catch子句就不會被檢查,因此它只能放到try塊的最后。

       其實很多情況下catch子句的處理程序并不需要訪問異常對象,只需要聲明異常的類型就夠了,例如上面程序中的catch子句就可以改成

        catch(double)    // 捕捉double型異常
        {
             cout << "發生對負數求平方根的異常。" << endl;
        }

       當然如果需要訪問異常對象就要給出參數名,就像上面程序中的catch(double y)。

       三.異常接口聲明

       我們可以在函數的聲明中給出它可能會拋出的所有異常類型。例如:

        void func()  throw(X, Y);

       上面的語句表明函數func能夠拋出也只能拋出X、Y及它們子類型的異常。

       若函數的聲明中沒有給出任何異常接口聲明,則此函數可能拋出任何類型的異常。例如:

        void func();

       如果函數不拋出任何類型的異常,則可以這樣聲明:

       void func()  throw();

       這一講的內容比較多,希望大家好好掌握,多多練習。這是本教程最后一講,雞啄米希望大家學完以后能夠實踐和應用起來,最終成功加入到C++開發隊伍中來。大家有問題可以經?;仉u啄米博客來交流討論。

 

除非特別注明,雞啄米文章均為原創
轉載請標明本文地址:http://www.9385095.live/software/127.html
2012年2月8日
作者:雞啄米 分類:軟件開發 瀏覽: 評論:21