从零开始学C++之异常(二):程序错误、异常(语法、抛出、捕获、传播)、栈展开

一、程序错误

编译错误,即语法错误。程序就无法被生成运行代码。 运行时错误

不可预料的逻辑错误 可以预料的运行异常

例如:

动态分配空间时可能不会成功 打开文件可能会失败 除法运算时分母可能为0 整数相乘可能溢出 数组越界……

二、异常

(一)、异常语法

throw  表达式;

try { //try语句块 } catch(类型1  参数1) { //针对类型1的异常处理 } catch (类型2  参数2) { //针对类型2的异常处理 } … catch (类型n  参数n) { //针对类型n的异常处理 }

(二)、异常抛出

可以抛出内置类型异常也可以抛出自定义类型异常 throw抛出一个类对象会调用拷贝构造函数 异常发生之前创建的局部对象被销毁,这一过程称为栈展开

(三)、异常捕获

一个异常处理器一般只捕捉一种类型的异常 异常处理器的参数类型和抛出异常的类型相同 …表示可以捕获任何异常

#include <iostream>
#include <string>
using namespace std;

class MyException
{
public:
    MyException(const char *message)
        : message_(message)
    {
        cout << "MyException ..." << endl;
    }
    MyException(const MyException &other) : message_(other.message_)
    {
        cout << "Copy MyException ..." << endl;
    }
    ~MyException()
    {
        cout << "~MyException" << endl;
    }

    const char *what() const
    {
        return message_.c_str();
    }
private:
    string message_;
};
double Divide(double a, double b)
{
    if (b == 0.0)
    {
        MyException e("division by zero");
        throw e;
        /*throw MyException("division by zero");*/
        //throw 1;
    }
    else
        return a / b;
}
int main(void)
{
    try
    {
        cout << Divide(5.0, 0.0) << endl;
    }
    catch (MyException &e)
    {
        cout << e.what() << endl;
    }
    //catch (int)
    //{
    //  cout<<"int exception ..."<<endl;
    //}
    catch (double)
    {
        cout << "double exception ..." << endl;
    }
    catch (...)
    {
        cout << "catch a exception ..." << endl;
    }
}

程序自定义一个异常类型MyException,从输出可以看出,Divide函数内先构造一个MyException对象e,调用构造函数,因为e是局部对象需要被析构,在析构前先调用拷贝构造函数构造另一个对象,这个对象将被catch 引用,最后这个对象在catch末尾也将被析构。

假设没有构造局部对象,直接throw , 如 throw MyException("division by zero"); 那么将不会调用拷贝构造函数,只存在一个对象,在catch的末尾被析构。

假设throw 1; 而没有对应的catch (int) ,即使存在catch (double) 也捕获不到,不会做类型转换,此时会由catch (...) 捕获到,...表示可以捕获任何异常。

(四)、异常传播

1、try块可以嵌套 2、程序按顺序寻找匹配的异常处理器,抛出的异常将被第一个类型符合的异常处理器捕获 如果内层try块后面没有找到合适的异常处理器,该异常向外传播,到外层try块后面的catch块中寻找 3、没有被捕获的异常将调用terminate函数,terminate函数默认调用abort终止程序的执行 可以使用set_terminate函数指定terminate函数在调用abort之前将调用的函数

void MyTerminate()
{
    cout << "MyTerminate ..." << endl;
}

int main(void)
{
    set_terminate(MyTerminate);
    try
    {
        try
        {
            throw MyException("test exception");
        }
        catch (int)
        {
            cout << "Inner ..." << endl;
            cout << "catch a int exception" << endl;
        }
        //catch (MyException& e)
        //{
        //  cout<<"Inner ..."<<endl;
        //  cout<<e.what()<<endl;
        //  throw e;
        //}
    }
    catch (int)
    {
        cout << "Outer ..." << endl;
        cout << "catch a int exception" << endl;
    }
    catch (MyException &e)
    {
        cout << "Outer ..." << endl;
        cout << e.what() << endl;
    }
}

其中MyException类如上,程序中将内层的catch (MyException& e) 屏蔽了,所以由外层的catch (MyException& e) 捕获,假设将两个都注释掉的话,因为没有找到合适的catch, 那么terminate 函数会被调用,并且由于事先set_terminate 函数设定了abort调用之前被调用的函数MyTerminate,故先输出MyTerminate ...然后程序被终止。

三、栈展开

沿着嵌套调用链接向上查找,直至为异常找到一个catch子句。这个过程称之为栈展开。

为局部对象调用析构函数

析构函数应该从不抛出异常

栈展开期间会执行析构函数,在执行析构函数的时候,已经引发的异常但还没处理,如果这个过程中析构函数又抛出新的异常,将会调用标准库的terminate函数。

异常与构造函数

构造函数中可以抛出异常。如果在构造函数函数中抛出异常,则可能该对象只是部分被构造。即使对象只是被部分构造,也要保证销毁已构造的成员。(如果成员是指针p,因为析构函数不会被调用,故不会执行一般的delete p; 很可能造成内存泄漏)

参考:

C++ primer 第四版 Effective C++ 3rd C++编程规范

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏PHP在线

php总结

php5.3新增魔术方法__invoke在对象实例化之后,像调用变量函数一样调用。 class testClass{ function __invoke(...

2999
来自专栏Java编程

Java异常的深入研究与分析

本文是异常内容的集大成者,力求全面,深入的异常知识研究与分析。本文由金丝燕网独家撰写,参考众多网上资源,经过内容辨别取舍,文字格式校验等步骤编辑而成,以飨读者。...

3220
来自专栏向治洪

Kotlin之基本语法

在今年Google IO大会上Google已经明确kotlin作为为Android第一官方语言的地位。我相信Google的决意,就像当初毫不犹豫的抛弃eclip...

2058
来自专栏IT可乐

Java数据结构和算法(七)——链表

  前面博客我们在讲解数组中,知道数组作为数据存储结构有一定的缺陷。在无序数组中,搜索性能差,在有序数组中,插入效率又很低,而且这两种数组的删除效率都很低,并且...

2357
来自专栏Laoqi's Linux运维专列

正则三剑客-grep

先来普及一下基础知识! ^ 匹配行开始,如:/^sed/匹配所有以sed开头的行。 $ 匹配行结束,如:/sed$/匹配所有以sed结尾的行。 . 匹配一个非换...

2486
来自专栏debugeeker的专栏

《coredump问题原理探究》Linux x86版3.5节栈布局之-fomit-frame-pointer编译选项

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuzhina/article/detai...

452
来自专栏debugeeker的专栏

《coredump问题原理探究》Linux x86版3.6节栈布局之gcc内嵌关键字

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuzhina/article/detai...

491
来自专栏walterlv - 吕毅的博客

Roslyn 语法树中的各种语法节点及每个节点的含义

2018-07-18 12:24

1141
来自专栏java学习

面试题69(关于类的成员变量调用的问题)

面试题69 2018年1月22日 ? 本期题目 (单选题)下列选项中哪一个是正确的? public class Test{ private float ...

2563
来自专栏专注 Java 基础分享

Java 的异常处理机制

902

扫码关注云+社区