3.6 C++高级编程_异常

引入异常

假设有A,B,C三个函数,其中A调用了B,B调用了C(A > B > C)。

如果C执行过程中出现异常,那么首先要将异常情况返回给B,然后由B再返回给A。

在C++中,可以使用异常来处理这种情况。

可以简单概括为:函数A捕捉函数C发出的异常

分析一下这句话:

  1. 谁捕捉异常?—— A
  2. 谁抛出异常?—— C
  3. 捕捉到异常后怎么处理?—— 由A决定

修改代码,在函数C中抛出一个异常,异常值为1;对应的,在函数A中会捕捉这个异常,并且将异常值输出。

void C()
{
    throw 1;
}

void B()
{
    C();
}

void A()
{
    try {
        B();
    } catch (int i)
    {
        cout << "catch exception " << i << endl;
    }
}

main函数

int main(int argc, char **argv)
{
    A();
    return 0;
}

编译测试,可以看到函数A成功捕捉到了函数C抛出的异常。

修改测试

修改C函数,增加一个传参,在代码中根据传参的值选择程序正常执行还是抛出异常

void C(int i)
{
    int a = 1;
    double b = 1.23;
    float c = 4.56;    

    if (i == 0) {
        cout << "In C, it is OK" << endl;
    } else if (i == 1) {
        throw a;
    } else if (i == 2) {
        throw b;
    } else if (i == 3) {
        throw c;
    }
}

C的传参是由A传给B,B传给C的,也就是说来源是A。

void B(int i)
{
    cout << "call C ..." << endl;
    C(i);
    cout << "After call C" << endl;
}

void A(int i)
{
    try {
        B(i);
    } catch (int j) {
        cout << "catch exception " << j << endl;
    }
}

修改main函数,将传入的argv[1]从中字符转为整型,然后传给函数A,也就是说C的传参来源是命令行

int main(int argc, char **argv)
{
    int i;

    if (argc != 2) {
        cout << "Usage: " << endl;
        cout << argv[0] << " <0|1|2|3>" << endl;
        return -1;
    }

    i = strtoul(argv[1], NULL, 0);

    A(i);

    return 0;
}

编译测试,当执行 ./exception2 0 时,程序正常执行,没有发生异常。

当执行 ./exception2 1 时,捕捉到了异常,并且输出了异常值1。

当执行  ./exception2 2 时,程序崩溃了。

这是因为,传入2时C抛出了一个double型的异常值,但是A捕捉的异常是int型,此时程序崩溃了。

 

 

完善测试程序

我们需要完善一下测试程序,当C函数抛出不同类型的异常值时,程序不应该崩溃。

修改A函数,增加对double型和float型异常值的捕捉和处理。

此时再执行 ./exception3 2 和 ./exception3 3 就没有问题了。

但是,如果异常的类型非常的多,难道每个类型都要加一个专门的处理吗?是否可以写一段代码,直接处理剩下的所有种类的异常?

答:可以的。

修改A函数,将对float型异常值的处理,改为对剩下的所有种类的异常的处理。

编译测试,此时对float型异常值的处理流程,就是省略号中定义的处理了。

对 double 和 int 则没有影响。

抛出类的实例化对象

在之前的程序中,throw出的是int,double,float,那么是否可以扔出类的实例化对象呢?

答:可以的。

增加一个类,叫做MyException:

class MyException {
public:
    void what(void)
    {
        cout << "MyException::what(void)" << endl;
    }
};

然后在C函数中,传参为4时抛出这个类异常。

在A函数中,捕捉到抛出了MyException类异常时,调用成员函数what。

 编译测试,程序成功抛出和捕捉到了异常。

增加派生类异常

修改代码,增加一个 MyException类的派生类 MySubException。

class MySubException : public MyException {
public:
    void what(void)
    {
        cout << "MySubException::what(void)" << endl;
    }
};

修改代码,当i=5时,抛出的类异常是MySubException。

此时如果不修改A函数,即不添加MySubException类异常的检测时,此时会触发哪个处理程序。

可以看到,此时会触发处理基类异常的异常处理程序,也就是说基类的检测,同样可以检测出抛出的派生类异常。

在A函数中,添加对MySubException类异常的处理。

添加在对MyException类异常的处理之后。

此时编译会有警告,意思是抛出的派生类MySubException类异常,会被之前处理基类MyException类异常的程序处理,而不是处理MySubException异常的代码来处理,因为catch MySubException在catch MyException之后。

执行发现,触发的处理程序也是MyException类的处理程序。

将对MySubException类异常的处理,转移到对MyException类异常的处理之前。

此时编译执行,可以成功触发对MySubException类异常的处理。

使用多态

修改代码,去掉对派生类MySubException异常的处理,只保留对基类MyException的处理。

此时,如果想要实现,抛出基类MyException类异常,调用的是基类的what函数;抛出派生类MySubException类异常,调用派生类的what函数。

那么,就要使用多态

将MyException类的成员函数what,声明为 virtual 即可。

编译测试,结果符合预期。

增加可能抛出异常的说明

前面的代码,在C函数中抛出异常,在函数A中捕获异常并处理,那么,A怎么知道有哪些异常需要处理呢?或者说,它怎么知道函数C可能抛出哪些异常呢?

修改代码,在函数C中增加说明可能抛出的异常的说明,声明函数C可能抛出int型和double型的异常。

编译测试,此时只有抛出int型和double型的异常时,程序可以成功执行,抛出其他异常,程序都会崩溃。

也就是说,即时在A函数中有针对float型,MyException类,MySubException类异常的处理,但是由于C函数没有声明可能抛出这些异常,此时如果C函数抛出这些异常,程序也会崩溃。

未定义异常的处理和终止函数

未定义异常的处理函数

当程序检测到未定义异常时,如果我们没有定义一个未定义异常的处理函数,那么程序会调用一个默认的未定义异常的处理函数。

可以通过set_unexpected重新定义一个未定义异常的处理函数。

修改代码,增加一个未定义异常的处理函数my_unexpected_func。

编译测试,可以看到当检测到未定义异常时,调用了我们自定义的未定义异常的处理函数my_unexpected_func。

未定义异常终止函数

在上面的未定义异常的处理函数函数中,除了输出我们添加的my_unexpected_func(void)语句外,还有一条“terminate called after throwing an instance of 'MyException'”。

这个是由默认的未定义异常终止函数输出的,类似的,我们同样可以自定义一个未定义异常终止函数。

修改代码,使用set_terminate自定义一个未定义异常终止函数my_terminate_func。

编译测试,可以看到,在调用了我们自定义的未定义异常的处理函数之后,系统又调用了自定义的未定义异常的终止函数

小结

对于意料之外的异常,系统会执行两个函数

  1. “unexpected”函数(可以自己提供);
  2. “terminate”函数(可以自己提供);

其中,“unexpected”函数用来处理声明之外的异常;“terminate”函数用来处理“catch分支未提到的异常”。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值