异常处理

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 ;
}

变量,则copy构造异常变量. 调用拷贝构造函数。


引用的话 会直接使用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");
}