第 16章 异常处理
16.1 异常处理概述
16.2 异常处理的基本思想
16.3 C++异常处理的实现
16.4 标准 C++库中的异常类
16.5 多路捕获
16.6 含有异常的程序设计
在编写程序时,应该考虑确定程序可能出
现的错误,然后加入处理错误的代码。也就是
说,在环境条件出现异常情况下,不会轻易出
现死机和灾难性的后果,而应有正确合理的表
现。这就是异常处理。 C++提供了异常处理机
制,它使得程序出现错误时,力争做到允许用
户排除环境错误,继续运行程序。
16.1 异常处理概述
程序可能按编程者的意愿终止, 也可能因为程序
中发生了错误而终止 。 例如, 程序执行时遇到除数为 0
或下标越界, 这时将产生系统中断, 从而导致正在执
行的程序提前终止 。
程序的错误有两种, 一种是编译错误, 即语法错
误 。 如果使用了错误的语法, 函数, 结构和类, 程序
就无法被生成运行代码 。 另一种是在运行时发生的错
误, 它分为不可预料的逻辑错误和可以预料的运行异
常 。
为处理可预料的错误, 常用的典型方法是让被调
用函数返回某一个特别的值 ( 或将某个按引用调用传
递的参数设置为一个特别的值 ), 而外层的调用程序
则检查这个错误标志, 从而确定是否产生了某一类型
的错误 。 另一种典型方法是当错误发生时跳出当前的
函数体, 控制转向某个专门的错误处理程序, 从而中
断正常的控制流 。 这两种方法都是权宜之计, 不能形
成强有力的结构化异常处理模式 。
异常处理机制是用于管理程序运行期间错误的一
种结构化方法。所谓结构化是指程序的控制不会由于
产生异常而随意跳转。异常处理机制将程序中的正常
处理代码与异常处理代码显式区别开来,提高了程序
的可读性。
16.2 异常处理的基本思想
对于中小型程序,一旦发生异常,一般是将程序
立即中断执行,从而无条件释放系统所有资源。而对
于比较大的程序来说,如果出现异常,应该允许恢复
和继续执行。恢复的过程就是把产生异常所造成的恶
劣影响去掉,中间一般要涉及一系列的函数调用链的
退栈,对象的析构,资源的释放等。继续运行就是异
常处理之后,在紧接着异常处理的代码区域中继续运
行。
16.3 C++异常处理的实现
C++语言异常处理机制的基本思想是将异常的检测
与处理分离 。 当在一个函数体中检测到异常条件存在,
但无法确定相应的处理方法时, 将引发一个异常, 并
由函数的直接或间接调用检测并处理这个异常 。 这一
基本思想用 3个保留字实现,throw,try和 catch。 其作
用是:
( 1) try:标识程序中异常语句块的开始 。
( 2) throw:用来创建用户自定义类型的异常错
误 。
( 3) catch:标识异常错误处理模块的开始。
在一般情况下,被调用函数直接检测到异常条件
的存在并使用 throw引发一个异常(注意,C++语言的
异常是由程序员控制引发的,而不是由计算机硬件或
程序运行环境控制的);在上层调用函数中使用 try检
测函数调用是否引发异常,检测到的各种异常由 catch
捕获并作相应处理。
16.3.1 异常处理的语法
在 C++程序中,任何需要检测异常的语句(包括函
数调用)都必须在 try语句块中执行,异常必须由紧跟
着 try语句后面的 catch语句来捕获并处理。因而,try与
catch总是结合使用。 throw,try和 catch语句的一般语法
如下:
throw <表达式 >;
try
{
//try语句块
}
catch( 类型 1 参数 1)
{
//针对类型 1的异常处理
}
catch ( 类型 2 参数 2)
{
//针对类型 2的异常处理
}

catch ( 类型 n 参数 n)
{
//针对类型 n的异常处理
}
( 1) 控制通过正常的顺序执行到达 try语句, 然后
执行 try块内的保护段 。
( 2) 如果在保护段执行期间没有引起异常, 那么
跟在 try块后的 catch子句就不执行, 程序从异常被抛掷
的 try块后跟随的最后一个 catch子句后面的语句继续执
行下去 。
( 3) 如果在保护段执行期间或在保护段调用的任
何函数中有异常被抛掷, 则从通过 throw运算数创建的
对象中创建一个异常对象 。 编译器从能够处理抛掷类
型的异常的更高执行上下文中寻找一个 catch子句 ( 或
一个能处理任何类型异常的 catch处理程序 ) 。 catch处
理程序按其在 try块后出现的顺序被检查 。 如果没有找
到合适的处理程序, 则继续检查下一个动态封闭的 try
块 。 此处理继续下去直到最外层的封闭 try块被检查完 。
异常处理的执行过程如下:
( 4) 如果匹配的处理器未找到, 则运行函数
terminate将被自动调用, 而函数 terminate的默认功能是
调用 abort终止程序 。
( 5)如果找到了一个匹配的 catch处理程序,且它
通过值进行捕获,则其形参通过拷贝异常对象进行初
始化。如果它通过引用进行捕获,则参量初始化为指
向异常对象。在形参被初始化之后,开始“循环展开
栈”的过程,这包括对那些在与 catch处理器相对应的
try块开始和异常丢弃地点之间创建的(但尚未析构的)
所有自动对象的析构。析构以与构造相反的顺序进行。
然后执行 catch处理程序,接下来程序跳转到跟随在最
后处理程序之后的语句。
注意,catch处理程序的出现顺序很重要,因为在
一个 try块中,异常处理程序是按照它出现的顺序被检
查的。
16.3.2 异常处理的规则
( 1) try分程序必须出现在前, catch紧跟出现在后 。
catch之后的圆括号中必须含有数据类型, 捕获是利用
数据类型匹配实现的 。
( 2)如果程序内有多个异常错误处理模块,则当
异常错误发生时,系统自动查找与该异常错误类型相
匹配的 catch模块,查找次序为 catch出现的次序。
( 3)如果异常错误类型为 C++的类,并且该类有
其基类,则应该将派生类的错误处理程序放在前面,
基类的错误处理程序放在后面。
( 4)如果一个异常错误发生后,系统找不到一个
与该错误类型相匹配的异常错误处理模块,则调用预
定义的运行时刻终止函数,默认情况下是 abort。
16.4 标准 C++库中的异常类
标准 C++库中包含 9个异常类, 它们可以分为运行
时异常和逻辑异常:
length_error //运行时长度异常
domain_error //运行时域异常
out_of_range_error //运行时越界异常
invalid_argument //运行时参数异常
range_error //逻辑异常, 范围异常
overflow_error //逻辑异常, 溢出 ( 上 ) 异常
overflow_error //逻辑异常,溢出(下)异常
标准 C++库中的这些异常类并没有全部被显式使
用,因为 C++标准库中很少发生异常,但是这些标准
C++库中的异常类可以为编程人员,特别式自己类库
的开发者提供一些经验。
16.5 多路捕获
很多程序可能有若干不同种类的运行错误,它们
可以使用异常处理机制,每种错误可与一个类,一种
数据类型或一个值相关。这样,在程序中就会出现多
路捕获。
例 16-5
16.6 含有异常的程序设计
16.6.1 何时避免异常
异常并不能处理所发生的所有问题 。 实际上若对
异常过分的考虑, 将会遇到许多麻烦 。 下面的段落指
出异常不能被保证的情况 。
1,异步事件
2,普通错误情况
3,流控制
4,不强迫使用异常
5,新异常, 老代码
16.6.2 异常的典型使用
1,随时使用异常规格说明
2,起始于标准异常
3,套装用户自己的异常
4,使用异常层次
5,多重继承
6,用, 引用, 而非, 值, 去捕获
7,在构造函数中抛出异常
8,不要在析构函数中导致异常
9,避免无保护的指针