一.异常的概念及使用
1.异常的概念
异常就是:
程序运行过程中发生了"不正常情况",程序通过一种特殊机制,把错误信息传递出去,再由其他地方统一处理。
例如:
- 除数为 0
- 文件打开失败
- 内存申请失败
- 数组越界
- 网络连接失败
这些都属于异常情况。
没有异常时(传统方式)
比如:

调用:

这里的问题:
问题1:返回值可能冲突
比如:

结果也是:-1
到底是正常结果还是错误?
无法区分。
问题2:错误信息不够
返回:-1
无法知道:
- 哪个函数错了
- 为什么错
- 错误位置
- 怎么修复
只能查错误码。
C语言没有异常机制。
通常这样:


errno
系统错误编号。
例如:

2.异常的抛出和捕获

例如:

输出:捕获异常:10
throw执行后,后面的代码不会执行
例如:

输出:1
异常
当前调用链决定哪个catch处理异常
意思:
程序从当前函数开始向外层找。
例如:

输出:100
这叫异常传播
离throw最近且类型匹配的catch处理
例子:

输出:内部

如果内部类型不匹配:

继续向外传播
最终:外部
3.栈展开
正常函数调用:

异常发生:

程序不再正常返回。
当异常抛出后:
先检查当前函数有没有try
先看当前函数内部能不能处理。
例如:

输出:当前函数处理
当前函数没有catch
退出当前函数继续查找

调用:

发生:throw
查找:

输出:处理异常
这个过程就叫:
栈展开
沿调用栈不断退出。
退出函数时对象会析构 ,调用链创建对象都会销毁

输出:析构
捕获

异常退出也会释放资源。
类型不匹配继续向外找

如果:

则全部匹配
到main还没找到会调用标准库终止程序

例子:

输出可能:

找到catch以后程序继续执行

输出:处理
继续执行
继续的是:整个try-catch后面,不是回到 throw
4.查找匹配的处理代码
1.最常见情况:类型完全匹配
throw 什么类型,就用什么类型接。
如果不一致,继续向外传播。
2.有多个catch,选最近且匹配的
3.允许非const → const(权限缩小)

输出:成功
但是反方向不行:
因为权限放大了
4.数组→指针转换
数组允许退化成指针

输出:hello

发生数组退化
5.函数→函数指针转换

6.派生类异常可以用基类捕获

抛:


能捕获。
完整例子:

输出:除0
工程里:

统一:

处理所有异常。
这是标准写法。
7.catch(...) 捕获所有异常
一般main最后加catch(...)

匹配任意类型

输出:未知异常
但:

只能知道发生异常,不知道具体是什么异常
5.异常重新抛出
当前函数可能只能处理一部分异常,剩下的交给更外层统一处理。

语法:

这里没有对象。
表示:把当前正在处理的那个异常继续向外扔。
例子:

输出:内部处理
外部处理
为什么必须写 throw; 而不是 throw e;

表示:新抛出一个 e 的副本。
可能发生:
- 再拷贝一次
- 类型退化(对象切片)

结果:

叫:对象切片
正确:

表示:继续抛原来的异常对象
不会复制。
不会降级
重新抛出时会重新找catch


输出:最终处理
重新抛出时栈展开仍然发生


资源照样释放
6. 异常规范
一个函数会不会抛异常,能不能提前告诉调用者?
异常规范就是干这个的。
它本质上是一种:函数的异常承诺
告诉别人:
- 我不会抛异常
- 我可能抛哪些异常
- 调用我需不需要准备 catch
假设有函数:

调用者看到:
不知道
会不会抛异常?
抛什么异常?
要不要try?
于是有异常规范,提前说明;
例如:

表示:
保证不抛异常
C++98 的异常规范
语法:
函数后面:

1.throw()
表示:不会抛异常

表示:
这个函数保证不抛异常
如果违反:


程序结束
2.throw(type1,type2...)
表示:允许抛这些类型。

表示:
允许:

为什么淘汰?
因为太麻烦。维护困难
C++11 用 noexcept 简化

表示:保证不抛异常

调用者知道安全,不会异常
编译器不会严格检查 noexcept

可能编译通过
但运行:

结果:

程序结束
noexcept 还能作为运算符

作用:
检查表达式是否可能异常。
返回:


输出:1
说明:
不会抛。
析构函数默认就是 noexcept

默认等价:

所以:

通常:

二.标准库的异常
标准库没有把异常设计成杂乱的 throw int、throw "error",而是设计了一套继承体系,统一用基类 std::exception 管理。
1.标准库异常继承结构

其中:
exception(根基类)
所有标准异常父类。
定义类似:

关键:

支持多态
2.what() 是干什么的
返回错误描述。
例如:

输出:数据库连接失败

发生动态绑定
实际上调用:

不是:

3.几个常见标准异常
bad_alloc(内存申请失败)

输出类似:

out_of_range(越界)

输出:

invalid_argument(参数错误)
输出:

