22異常處理

異常處理

1.處理錯誤的傳統機制

傳統的C語言在函數執行過程中遇到語法錯誤或者邏輯錯誤的時候,通過函數返回值表明所遇到的錯誤類型。

2.思想

在C++裏面,通過異常,跨越函數來通知整個程序錯誤發生並對錯誤進行相應的處理。

  • C++的異常處理機制使得異常的引發和異常的處理不必在同一個函數中,這樣底層的函數可以着重解決具體問題,而不必過多的考慮異常的處理。上層調用者可以再適當的位置設計對不同類型異常的處理。

  • 異常是專門針對抽象編程中的一系列錯誤處理的,C++中不能借助函數機制,因爲棧結構的本質是先進後出,依次訪問,無法進行跳躍,但錯誤處理的特徵卻是遇到錯誤信息就想要轉到若干級之上進行重新嘗試,如圖

  • 異常超脫於函數機制,決定了其對函數的跨越式回跳。

總的來說,異常是爲了在程序出錯的時候能夠捕獲並判斷錯誤類型,且對錯誤做出合理的處理。增強了程序的健壯性。

3.基本語法

  1. 若有異常則通過throw操作創建一個異常對象並拋擲。
  2. 將可能拋出異常的程序段嵌在try塊之中。控制通過正常的順序執行到達try語句,然後執行try塊內的保護段。
  3. 如果在保護段執行期間沒有引起異常,那麼跟在try塊後的catch子句就不執行。程序從try塊後跟隨的最後一個catch子句後面的語句繼續執行下去。
  4. catch子句按其在try塊後出現的順序被檢查。匹配的catch子句將捕獲並處理異常(或繼續拋擲異常)。
  5. 如果匹配的處理器未找到,則運行函數terminate將被自動調用,其缺省功能是調用abort終止程序。
  6. 處理不了的異常,可以在catch的最後一個分支,使用throw語法,向上扔。

案例1:被零整除案例

int divide(int x, int y )
{
    if (y ==0)
    {
        throw x;
    }
    return x/y;
}

void main()
{
    try
    {
        cout << "8/2 = " << divide(8, 2) << endl;
        cout << "10/0 =" << divide(10, 0) << endl;
    }
    catch (int e)
    {
        cout << "e" << " is divided by zero!" << endl;
    }
    catch(...)
    {
        cout << "未知異常" << endl;
    }

    cout << "ok" << endl;
    system("pause");
    return ;
}

案例2,類對象作爲異常變量被拋出

class A{};
void f(){
  if(...) throw A();
}
void g(){
  try{
    f();
  }catch(A){
    cout<<“exception A\n”;
  }
}
int main(){
  g();
}
  • throw A將穿透函數fgmain,抵達系統的最後一道防線——激發terminate函數.該函數調用引起運行終止的abort函數.

  • 最後一道防線的函數可以由程序員設置.從而規定其終止前的行爲.

  • 修改系統默認行爲:

    • 可以通過 set_terminate函數修改捕捉異常的默認處理器,從而使得發生處理不了的異常時,被自定義函數處理。
    • void myTerminate(){cout<<“HereIsMyTerminate\n”;}
    • set_terminate(myTerminate);
    • set_terminate函數在頭文件exception中聲明,參數爲函數指針void(*)().

案例3
構造函數沒有返回類型,無法通過返回值來報告運行狀態,所以只通過一種非函數機制的途徑,即異常機制,來解決構造函數的出錯問題。

異常機制與函數機制互不干涉,但捕捉的方式是基於類型匹配。捕捉相當於函數返回類型的匹配,而不是函數參數的匹配,所以捕捉不用考慮一個拋擲中的多種數據類型匹配問題,因爲函數返回值只會有一個。
比如:

class A{};
class B{};

int main()
{
    try
    {
        int     j = 0;    
        double  d = 2.3;    
        char    str[20] = "Hello";
        cout<<"Please input a exception number: ";
        int a; 
        cin>>a;
        switch(a)
        {
        case  1: 
            throw d;      
        case  2: 
            throw j;      
        case  3: 
            throw str;
        case  4: 
            throw A();      
        case  5: 
            throw B();
        default: 
            cout<<"No throws here.\n";    
        }
    }
    catch(int)
    {
        cout<<"int exception.\n";
    }
    catch(double)
    {
        cout<<"double exception.\n";
    }
    catch(char*)
    {
        cout<<"char* exception.\n";
    }
    catch(A)
    {
        cout<<"class A exception.\n";
    }
    catch(B)
    {
        cout<<"class B exception.\n";
    }
    cout<<"That's ok.\n";
    system("pause");
}

catch代碼塊必須出現在try後,並且在try塊後可以出現多個catch代碼塊,以捕捉各種不同類型的拋擲。


異常機制是基於這樣的原理:程序運行實質上是數據實體在做一些操作,因此發生異常現象的地方,一定是某個實體出了差錯,該實體所對應的數據類型便作爲拋擲和捕捉的依據。

  • 異常捕捉嚴格按照類型匹配
    異常捕捉的類型匹配之苛刻程度可以和模板的類型匹配媲美,它不允許相容類型的隱式轉換,比如,拋擲char類型用int型就捕捉不到.例如下列代碼不會輸出“int exception.”,從而也不會輸出“That’s ok.” 因爲出現異常後提示退出
int main(){
  try{
    throw ‘H’;
  }catch(int){
    cout<<"int exception.\n";
  }
  cout<<"That's ok.\n";
}

4.棧解旋

異常被拋出後,從進入try塊起,到異常被拋擲前,這期間在棧上的構造的所有對象,都會被自動析構。析構的順序與構造的順序相反。這一過程稱爲棧的解旋(unwinding)。


class MyException {};

class Test
{
public:
    Test(int a=0, int b=0)
    {
        this->a = a;
        this->b = b;
        cout << "Test 構造函數執行" << "a:" << a << " b: " << b << endl;
    }
    void printT()
    {
        cout << "a:" << a << " b: " << b << endl;
    }
    ~Test()
    {
        cout << "Test 析構函數執行" << "a:" << a << " b: " << b << endl;
    }
private:
    int a;
    int b;
};

void myFunc() throw (MyException)
{
    Test t1;
    Test t2;

    cout << "定義了兩個棧變量,異常拋出後測試棧變量的如何被析構" << endl;

    throw MyException();
}

void main()
{
    //異常被拋出後,從進入try塊起,到異常被拋擲前,這期間在棧上的構造的所有對象,
    //都會被自動析構。析構的順序與構造的順序相反。
    //這一過程稱爲棧的解旋(unwinding)
    try  
    {
        myFunc();
    }
    //catch(MyException &e) //這裏不能訪問異常對象
    catch(MyException ) //這裏不能訪問異常對象
    {
        cout << "接收到MyException類型異常" << endl;
    }
    catch(...)
    {
        cout << "未知類型異常" << endl;
    }

    system("pause");
    return ;
}

5.接口聲明

  • 爲了加強程序的可讀性,可以在函數聲明中列出可能拋出的所有異常類型,例如:
    void func() throw (A, B, C , D);//這個函數func()能夠且只能拋出類型A B C D及其子類型的異常。
  • 如果在函數聲明中沒有包含異常接口聲明,則此函數可以拋擲任何類型的異常,例如:
    void func();
  • 一個不拋擲任何類型異常的函數可以聲明爲:
    void func() throw();
  • 如果一個函數拋出了它的異常接口聲明所不允許拋出的異常,unexpected函數會被調用,該函數默認行爲調用terminate函數中止程序。

6.異常類型和變量的生命週期

  • throw的異常是有類型的,可以是,數字、字符串、類對象。
  • throw的異常是有類型的,catch嚴格按照類型進行匹配。

    注意 異常對象的內存模型 。


傳統處理錯誤
//文件的二進制copy
int filecopy01(char *filename2, char *filename1 )
{
    FILE *fp1= NULL,  *fp2 = NULL;

    fp1 = fopen(filename1, "rb");
    if (fp1 == NULL)
    {
        return 1;
    }

    fp2 = fopen(filename2, "wb");
    if (fp1 == NULL)
    {
        return 2;
    }

    char buf[256];
    int  readlen, writelen;
    while ( (readlen = fread(buf, 1, 256, fp1)) > 0 ) //如果讀到數據,則大於0 
    {
        writelen = fwrite(buf, 1, readlen, fp2);
        if (readlen != readlen)
        {
            return 3;
        }
    }

    fclose(fp1);
    fclose(fp2);
    return 0;
}


void main()
{
    int ret;
    ret = filecopy01("c:/1.txt","c:/2.txt");
    if (ret !=0 )
    {
        switch(ret)
        {
        case 1:
            printf("打開源文件時出錯!\n");
            break;
        case 2:
            printf("打開目標文件時出錯!\n");
            break;
        case 3:
            printf("拷貝文件時出錯!\n");
            break;
        default:
            printf("發生未知錯誤!\n");
            break;
        }
    }
}
**throw int類型異常**
/文件的二進制copy
void filecopy02(char *filename2, char *filename1 )
{
    FILE *fp1= NULL,  *fp2 = NULL;

    fp1 = fopen(filename1, "rb");
    if (fp1 == NULL)
    {
        //return 1;
        throw 1;
    }

    fp2 = fopen(filename2, "wb");
    if (fp1 == NULL)
    {
        //return 2;
        throw 2;
    }

    char buf[256];
    int  readlen, writelen;
    while ( (readlen = fread(buf, 1, 256, fp1)) > 0 ) //如果讀到數據,則大於0 
    {
        writelen = fwrite(buf, 1, readlen, fp2);
        if (readlen != readlen)
        {
            //return 3;
            throw 3;
        }
    }

    fclose(fp1);
    fclose(fp2);
    return ;
}
**throw字符類型異常**
//文件的二進制copy
void filecopy03(char *filename2, char *filename1 )
{
    FILE *fp1= NULL,  *fp2 = NULL;

    fp1 = fopen(filename1, "rb");
    if (fp1 == NULL)
    {
        throw "打開源文件時出錯";
    }

    fp2 = fopen(filename2, "wb");
    if (fp1 == NULL)
    {
        throw "打開目標文件時出錯";
    }

    char buf[256];
    int  readlen, writelen;
    while ( (readlen = fread(buf, 1, 256, fp1)) > 0 ) //如果讀到數據,則大於0 
    {
        writelen = fwrite(buf, 1, readlen, fp2);
        if (readlen != readlen)
        {
            throw "拷貝文件過程中失敗";
        }
    }

    fclose(fp1);
    fclose(fp2);
    return ;
}
**throw類對象類型異常**
//throw int類型變量
//throw 字符串類型
//throw 類類型
class BadSrcFile 
{
public:
    BadSrcFile()
    {
        cout << "BadSrcFile 構造 do "<<endl;
    }
    ~BadSrcFile()
    {
        cout << "BadSrcFile 析構 do "<<endl;
    }
    BadSrcFile(BadSrcFile & obj)
    {
        cout << "拷貝構造  do "<<endl;
    }
    void toString()
    {
        cout << "aaaa" << endl;
    }

};
class BadDestFile {};
class BadCpyFile {};;

void filecopy04(char *filename2, char *filename1 )
{
    FILE *fp1= NULL,  *fp2 = NULL;

    fp1 = fopen(filename1, "rb");
    if (fp1 == NULL)
    {
        //throw new BadSrcFile();
        throw  BadSrcFile();
    }

    fp2 = fopen(filename2, "wb");
    if (fp1 == NULL)
    {
        throw BadDestFile();
    }

    char buf[256];
    int  readlen, writelen;
    while ( (readlen = fread(buf, 1, 256, fp1)) > 0 ) //如果讀到數據,則大於0 
    {
        writelen = fwrite(buf, 1, readlen, fp2);
        if (readlen != readlen)
        {
            throw BadCpyFile();
        }
    }

    fclose(fp1);
    fclose(fp2);
    return ;
}
**測試**
//結論://C++編譯器通過throw 來產生對象,C++編譯器再執行對應的catch分支,相當於一個函數調用,把實參傳遞給形參。
void main()
{
    try
    {
         //filecopy02("c:/1.txt","c:/2.txt");
        // filecopy03("c:/1.txt","c:/2.txt");
         filecopy04("c:/1.txt","c:/2.txt");
    }
    catch (int e)
    {
        printf("發生異常:%d \n", e);
    }
    catch (const char * e)
    {
        printf("發生異常:%s \n", e);
    }
    catch ( BadSrcFile *e)
    {
         e->toString();
        printf("發生異常:打開源文件時出錯!\n");
    }
    catch ( BadSrcFile &e)
    {
        e.toString();
        printf("發生異常:打開源文件時出錯!\n");
    }
    catch ( BadDestFile e)
    {
        printf("發生異常:打開目標文件時出錯!\n");
    }
    catch ( BadCpyFile e)
    {
        printf("發生異常:copy時出錯!\n");
    }
    catch(...) //抓漏網之魚
    {
        printf("發生了未知異常! 抓漏網之魚\n");
    }
    //class BadSrcFile {};
    //class BadDestFile {};
    //class BadCpyFile {};; 
}
**綜合案例**

#include <iostream>
using namespace std;


//傳統的錯誤處理機制
//throw int類型異常
void my_strcpy1(char *to, char *from)
{
    if (from == NULL)
    {
        throw 1;
    }
    if (to == NULL)
    {
        throw 2;
    }

    //copy是的 場景檢查
    if (*from == 'a')
    {
        throw 3; //copy時出錯
    }
    while (*from != '\0')
    {
        *to = *from;
        to ++;
        from ++;
    }
    *to = '\0';
}

//傳統的錯誤處理機制
//throw char*類型異常
void my_strcpy2(char *to, char *from)
{
    if (from == NULL)
    {
        throw "源buf出錯";
    }
    if (to == NULL)
    {
        throw "目的buf出錯";
    }

    //copy是的 場景檢查
    if (*from == 'a')
    {
        throw "copy過程出錯"; //copy時出錯
    }
    while (*from != '\0')
    {
        *to = *from;
        to ++;
        from ++;
    }
    *to = '\0';
}


class BadSrcType {};
class BadDestType {};
class BadProcessType
{
public:
    BadProcessType()
    {
        cout << "BadProcessType構造函數do \n";
    }


    BadProcessType(const BadProcessType &obj)
    {
        cout << "BadProcessType copy構造函數do \n";
    }

    ~BadProcessType()
    {
        cout << "BadProcessType析構函數do \n";
    }

};


//傳統的錯誤處理機制
//throw 類對象 類型異常
void my_strcpy3(char *to, char *from)
{
    if (from == NULL)
    {
        throw BadSrcType();
    }
    if (to == NULL)
    {
        throw BadDestType();
    }

    //copy是的 場景檢查
    if (*from == 'a')
    {
        printf("開始 BadProcessType類型異常 \n");
        throw BadProcessType(); //會不會產生一個匿名對象?
    }

    if (*from == 'b')
    {
        throw &(BadProcessType()); //會不會產生一個匿名對象?
    }

    if (*from == 'c')
    {
        throw new BadProcessType; //會不會產生一個匿名對象?
    }
    while (*from != '\0')
    {
        *to = *from;
        to ++;
        from ++;
    }
    *to = '\0';
}

void main()
{
    int ret = 0;
    char buf1[] = "cbbcdefg";
    char buf2[1024] = {0};

    try
    {
        //my_strcpy1(buf2, buf1);
        //my_strcpy2(buf2, buf1);
        my_strcpy3(buf2, buf1);
    }
    catch (int e) //e可以寫 也可以不寫
    {
        cout << e << " int類型異常" << endl;
    }
    catch(char *e)
    {
        cout << e << " char* 類型異常" << endl;
    }

    //---
    catch(BadSrcType e)
    {
        cout << " BadSrcType 類型異常" << endl;
    }
    catch(BadDestType e)
    {
        cout << " BadDestType 類型異常" << endl;
    }
    //結論1: 如果 接受異常的時候 使用一個異常變量,則copy構造異常變量.  
    /*
    catch( BadProcessType e) //是把匿名對象copy給e 還是e還是那個匿名對象
    {
        cout << " BadProcessType 類型異常" << endl;
    }
    */
    //結論2: 使用引用的話 會使用throw時候的那個對象
    //catch( BadProcessType &e) //是把匿名對象copy給e 還是e還是那個匿名對象
    //{
    //  cout << " BadProcessType 類型異常" << endl;
    //}

    //結論3: 指針可以和引用/元素寫在一塊 但是引用/元素不能寫在一塊
    catch( BadProcessType *e) //是把匿名對象copy給e 還是e還是那個匿名對象
    {
        cout << " BadProcessType 類型異常" << endl;
        delete e;
    }

    //結論4: 類對象時, 使用引用比較合適 

    // --
    catch (...)
    {
        cout << "未知 類型異常" << endl;
    }

    cout<<"hello..."<<endl;
    system("pause");
    return ;
}


//傳統的錯誤處理機制
int my_strcpy(char *to, char *from)
{
    if (from == NULL)
    {
        return 1;
    }
    if (to == NULL)
    {
        return 2;
    }

    //copy是的 場景檢查
    if (*from == 'a')
    {
        return 3; //copy時出錯
    }
    while (*from != '\0')
    {
        *to = *from;
        to ++;
        from ++;
    }
    *to = '\0';

    return 0;
}


void main41()
{
    int ret = 0;
    char buf1[] = "zbcdefg";
    char buf2[1024] = {0};

    ret = my_strcpy(buf2, buf1);
    if (ret != 0)
    {
        switch(ret)
        {
        case 1:
            printf("源buf出錯!\n");
            break;
        case 2:
            printf("目的buf出錯!\n");
            break;
        case 3:
            printf("copy過程出錯!\n");
            break;
        default:
            printf("未知錯誤!\n");
            break;
        }
    }
    printf("buf2:%s \n", buf2);

    cout<<"hello..."<<endl;
    system("pause");
    return ;
}

結論1: 如果接受異常的時候使用一個異常變量,則copy構造異常變量. 調用拷貝構造函數。


結論2: 使用引用的話 會直接使用throw時候的那個對象。


結論3: 指針可以和引用/元素寫在一塊但是引用/元素不能寫在一塊。


結論4: 異常變量是類對象時, 使用引用比較合適

7.異常和繼承

  • 由於異常變量可以是一個類對象,所以可以實現一個異常類
  • 異常類可以派生出子類,也可以繼承自其他類
  • 父類引用或者指針指向子類對象可以實現多態,從而實現異常類框架,實現代碼複用。

案例:設計一個數組類 MyArray

  • 重載[]操作,
  • 數組初始化時,對數組的個數進行有效檢查
    • index<0 拋出異常eNegative
    • index = 0 拋出異常 eZero
    • index>1000拋出異常eTooBig
    • index<10 拋出異常eTooSmall
    • eSize類是以上類的父類,實現有參數構造、並定義virtual void printErr()輸出錯誤。

#include <iostream>
using namespace std;

class MyArray
{
public:
    MyArray(int len);
    ~MyArray();
public:
    int & operator[](int index);
    int getLen();

    class eSize
    {
    public:
        eSize(int size)
        {
            m_size = size;
        }
        virtual void printErr()
        {
            cout << "size:" << m_size << " ";
        }
    protected:
        int m_size;
    };
    class eNegative : public eSize
    {
    public:
        eNegative(int size) : eSize(size)
        {
            ;
        }
        virtual void printErr()
        {
            cout << "eNegative 類型 size:" << m_size << " ";
        }
    };
    class eZero : public eSize
    {
    public:
        eZero(int size) : eSize(size)
        {
            ;
        }
        virtual void printErr()
        {
            cout << "eZero 類型 size:" << m_size << " ";
        }
    };
    class eTooBig : public eSize
    {
    public:
        eTooBig(int size) : eSize(size)
        {
            ;
        }
        virtual void printErr()
        {
            cout << "eTooBig 類型 size:" << m_size << " ";
        }
    };
    class eTooSmall : public eSize
    {
    public:
        eTooSmall(int size) : eSize(size)
        {
            ;
        }
        virtual void printErr()
        {
            cout << "eTooSmall 類型 size:" << m_size << " ";
        }
    };

private:
    int *m_space;
    int m_len;
};


MyArray::MyArray(int len)
{
    if (len  < 0)
    {
        throw eNegative(len);
    }
    else if (len == 0)
    {
        throw eZero(len);
    }
    else if (len > 1000)
    {
        throw eTooBig(len);
    }
    else if (len < 3)
    {
        throw eTooSmall(len);
    }
    m_len = len;
    m_space = new int[len];
}

MyArray::~MyArray()
{
    if (m_space != NULL)
    {
        delete [] m_space;
        m_space = NULL;
        m_len = 0;
    }
}

int & MyArray::operator[](int index)
{
    return m_space[index];
}

int MyArray::getLen()
{
    return m_len;
}

void main()
{

    try
    {
        MyArray a(-5);
        for (int i=0; i<a.getLen(); i++)
        {
            a[i] = i+1;
            printf("%d ", a[i]);
        }
    }
    catch(MyArray::eSize &e)
    {
        //cout <<  "len的大小: " << e.eSize();
        e.printErr();
    }
    catch (...)
    {
    }


    cout<<"hello..."<<endl;
    system("pause");
    return ;
}

// 不推薦
void main51()
{

    try
    {
        MyArray a(-5);
        for (int i=0; i<a.getLen(); i++)
        {
            a[i] = i+1;
            printf("%d ", a[i]);
        }
    }
    catch(MyArray::eNegative e)
    {
        cout << "eNegative 類型異常" << endl;
    }
    catch(MyArray::eZero e)
    {
        cout << "eZero 類型異常" << endl;
    }
    catch(MyArray::eTooBig e)
    {
        cout << "eTooBig 類型異常" << endl;
    }
    catch(MyArray::eTooSmall e)
    {
        cout << "eTooSmall 類型異常" << endl;
    }

    catch (...)
    {
    }


    cout<<"hello..."<<endl;
    system("pause");
    return ;
}

8.標準異常庫

// out_of_range
#include "iostream"
using namespace std;
#include <stdexcept>  

class Teacher
{
public:
    Teacher(int age)  //構造函數, 通過異常機制 處理錯誤
    {
        if (age > 100)
        {
            throw out_of_range("年齡太大");
        }
        this->age = age;
    }
protected:
private:
    int age;
};

void mainxx()
{
    try
    {
        Teacher t1(102);
    }
    catch (out_of_range e)
    {

        cout << e.what() << endl;
    }

    exception e;
    system("pause");
}   
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章