• 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中会捕捉这个异常,并且将异常值输出。

    1. void C()
    2. {
    3. throw 1;
    4. }
    5. void B()
    6. {
    7. C();
    8. }
    9. void A()
    10. {
    11. try {
    12. B();
    13. } catch (int i)
    14. {
    15. cout << "catch exception " << i << endl;
    16. }
    17. }

    main函数

    1. int main(int argc, char **argv)
    2. {
    3. A();
    4. return 0;
    5. }

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

    修改测试

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

    1. void C(int i)
    2. {
    3. int a = 1;
    4. double b = 1.23;
    5. float c = 4.56;
    6. if (i == 0) {
    7. cout << "In C, it is OK" << endl;
    8. } else if (i == 1) {
    9. throw a;
    10. } else if (i == 2) {
    11. throw b;
    12. } else if (i == 3) {
    13. throw c;
    14. }
    15. }

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

    1. void B(int i)
    2. {
    3. cout << "call C ..." << endl;
    4. C(i);
    5. cout << "After call C" << endl;
    6. }
    7. void A(int i)
    8. {
    9. try {
    10. B(i);
    11. } catch (int j) {
    12. cout << "catch exception " << j << endl;
    13. }
    14. }

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

    1. int main(int argc, char **argv)
    2. {
    3. int i;
    4. if (argc != 2) {
    5. cout << "Usage: " << endl;
    6. cout << argv[0] << " <0|1|2|3>" << endl;
    7. return -1;
    8. }
    9. i = strtoul(argv[1], NULL, 0);
    10. A(i);
    11. return 0;
    12. }

    编译测试,当执行 ./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:

    1. class MyException {
    2. public:
    3. void what(void)
    4. {
    5. cout << "MyException::what(void)" << endl;
    6. }
    7. };

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

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

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

    增加派生类异常

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

    1. class MySubException : public MyException {
    2. public:
    3. void what(void)
    4. {
    5. cout << "MySubException::what(void)" << endl;
    6. }
    7. };

    修改代码,当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分支未提到的异常”。

  • 相关阅读:
    【uni-app从入门到实战】打包
    几行代码,让黑白老照片重获新生!
    【04】穿越功耗墙,我们该从哪些方面提升“性能”?
    ADworld reverse wp easyre-153
    关于LWIP的一点记录(二)
    node 第十二天 npm补充 详解package-lock.json在团队协作中的作用
    RabbitMQ - 死信、TTL原理、延迟队列安装和配置
    【Redis学习笔记】第二章【2.4】Redis数据类型--set
    读取服务器文件,并进行移动-ChannelSftp
    go语言中Map的使用
  • 原文地址:https://blog.csdn.net/qq_33141353/article/details/126333087