第十二章 异常处理
大型和十分复杂的程序往往会产生一些很难查找的甚至是
无法避免的运行时错误。 当发生运行时错误时,不能简单地结
束程序运行,而是退回到任务的起点,指出错误,并由用户决
定下一步工作 。 面向对象的异常处理( exception handling)
机制是 C++语言用以解决这个问题的有力工具。
程序的错误有两种, 一种是编译错误, 即语法错误 。 如果
使用了错误的语法, 函数, 结构和类, 程序就无法被生成运行
代码 。 另一种是在运行时发生的错误, 它分为不可预料的逻辑
错误和可以预料的运行异常, 这里所讲的 异常 ( exception) 是
程序可能检测到的, 运行时不正常的情况, 如存储空间耗尽,
数组越界, 被 0除等等, 可以预见可能发生在什么地方, 但是无
法确知怎样发生和何时发生 。 特别在一个大型的程序 ( 软件 )
中, 程序各部分是由不同的小组编写的, 它们由公共接口连起
来, 错误可能就发生在相互的配合上, 也可能发生在事先根本
想不到的个别的条件组合上 。
第十二章 异常处理
为处理可预料的错误, 常用的典型方法是让被调用函数返
回某一个特别的值 ( 或将某个按引用调用传递的参数设置为一
个特别的值 ), 而外层的调用程序则检查这个错误标志, 从而
确定是否产生了某一类型的错误 。 另一种典型方法是当错误发
生时跳出当前的函数体, 控制转向某个专门的错误处理程序,
从而中断正常的控制流 。 这两种方法都是权宜之计, 不能形成
强有力的结构化异常处理模式 。
异常处理机制是用于管理程序运行期间错误的一种结构化
方法。所谓结构化是指程序的控制不会由于产生异常而随意跳
转。异常处理机制将程序中的正常处理代码与异常处理代码显
式区别开来,提高了程序的可读性。
异常处理在 C++编程中已经普遍采用,成为提高程序健壮
性的重要手段之一。
12.1 异常处理的机制
C++语言异常处理机制的基本思想是将异常的检测与处
理分离。当在一个函数体中检测到异常条件存在,但无法确定
相应的处理方法时,将引发一个异常,并由函数的直接或间接
调用检测并处理这个异常。这一基本思想用 3个保留字实现:
throw,try和 catch。其作用是:
( 1) try:标识程序中异常语句块的开始。
( 2) throw:用来创建用户自定义类型的异常错误。
( 3) catch:标识异常错误处理模块的开始。
在 C++程序中,任何需要检测异常的语句(包括函数调
用)都必须在 try语句块中执行,异常必须由紧跟着 try语句后
面的 catch语句来捕获并处理。因而,try与 catch总是结合使
用。 throw,try和 catch语句的一般语法如下:
12.1 异常处理的机制
throw <表达式 >;
try {
//try语句块
}
catch( 类型 1 参数 1)
{
//针对类型 1的异常处理
}
catch ( 类型 2 参数 2)
{
//针对类型 2的异常处理
}
…
catch ( 类型 n 参数 n)
{
//针对类型 n的异常处理
}
12.1 异常处理的机制
请看下面的程序段给出 try块与 catch子句的关系:
int main()
{ int a[9]={1,2,3,4,5,6,7,8,9},b[9]={0},i;
stack <int> istack(8);
try {
for(i=0; i<9; i++) istack.Push(a[i]);
istack.PrintStack();
}
catch(pushOnFull <int> ) {
cerr<<”栈满” <<endl;
}
for(i=0; i<9; i++) cout<<b[i]<<’\t’; cout<<endl;
}
这里有一个 try块,对应压栈的操作语句;也有一个 catch子句
( catch clause),分别处理压栈时的栈满溢出的异常处理。
12.1 异常处理的机制
template <class T>
void Stack<T>::Push(const T &data) {
if(IsFull()) throw pushOnFull<T>(data);
//注意加了括号,是构造一个无名对象
elements[++top]=data;
}
注意 pushOnFull是 类, C++要求抛出的必须是对象,所以必
须有,()”,即 调用构造函数建立一个对象 。异常并非总是类对
象,throw表达式也可以抛出任何类型的对象,如枚举、整数
等等。但最常用的是类对象。 throw表达式 抛出异常 为异常处
理的 第一步 。在堆栈的压栈和出栈操作中发生错误而抛出的异
常,理所当然地应由调用堆栈的程序来处理。
首先,在 C++中异常往往用类( class)来实现,以栈为例,
异常类声明如下,
class pushOnFull{...}; //栈满异常
12.1 异常处理的机制
程序按下列规则控制:
1,如果没有异常发生, 继续执行 try块中的代码, 与 try块相关
联 的 catch子句被忽略, 程序正常执行, main()返回 0。
2,当第一个 try块在 for循环中抛出异常, 则该 for循环退出,
try 块 也 退 出, 去执行 pushOnFull 异 常 的 catch 子句 。
istack.PrintStack()不再执行, 被忽略 。
3.当某条语句抛出异常时,跟在该语句后面的语句将被跳过。
程序执行权交给处理异常的 catch子句,如果没有 catch子句能
够处理异常,则交给 C++标准库中定义的 terminate()。
由 catch字句捕获并处理异常是 第二步 。注意与 catch语
句分别 匹配 的是在压栈成员函数模板中的 throw语句,一个抛
出 pushOnFull类的无名对象。
12.2 捕获异常
catch子句由三部分组成:关键字 catch、圆括号中的异常声
明以及复合语句中的一组语句。 * 注意这不是函数,所以圆括
号中不是形参,而是一个异常类型声明,可以是类型也可以是
对象。
异常声明中也可以是一个对象声明。以栈为例。当栈满时,
要求在异常对象中 保存不能被压入到栈中的值,这时,
pushOnFull类可定义如下:
template <class T> class pushOnFull {
T val;
public:
pushOnFull(T i) { val = i; }
T value() { return _value; }
};
新的私有数据成员 val 保存那些不能被压入栈中的值。该值即
调用构造函数时的实参。
12.2 捕获异常
这样在 catch子句中, 要取得 val,须调用 pushOnFull中的成
员函数 value():
catch( pushOnFull<T> eObj) {
cerr<<” 栈满, <<eObj.value()<<” 未压入
栈, <<endl;
return 1;
}
在 catch子句的异常声明中声明了对象 eObj,用它来调用
pushOnFull类的对象成员函数 value()。异常对象是在抛出
点被创建,与 catch子句是否显式要求创建一个异常对象无关,
该对象总是存在,在 catch子句中只是为了调用异常处理对象
的成员函数才声明为对象,不用类。
对应在 throw表达式中,构造抛出对象也要有实参:
throw pushOnFull(data);
//data即 Push(const &data)中的参数 data
12.3 异常处理的详细处理流程
异常处理的执行过程如下:
( 1) 控制通过正常的顺序执行到达 try语句, 然后执行 try块内
的保护段 。
( 2) 如果在保护段执行期间没有引起异常, 那么跟在 try块后
的 catch子句就不执行, 程序从异常被抛掷的 try块后跟随的
最后一个 catch子句后面的语句继续执行下去 。
( 3) 如果在保护段执行期间或在保护段调用的任何函数中有
异常被抛掷, 则从通过 throw运算数创建的对象中创建一个
异常对象 。 编译器从能够处理抛掷类型的异常的更高执行上
下文中寻找一个 catch子句 ( 或一个能处理任何类型异常的
catch处理程序 ) 。 catch处理程序按其在 try块后出现的顺
序被检查 。 如果没有找到合适的处理程序, 则继续检查下一
个动态封闭的 try块 。 此处理继续下去直到最外层的封闭 try
块被检查完 。
12.3 异常处理的详细处理流程
( 4) 如果匹配的处理器未找到, 则运行函数 terminate将被自
动调用, 而函数 terminate的默认功能是调用 abort终止程序 。
( 5)如果找到了一个匹配的 catch处理程序,且它通过值进行
捕获,则其形参通过拷贝异常对象进行初始化。如果它通过
引用进行捕获,则参量初始化为指向异常对象。
注意,catch处理程序的出现顺序很重要,因为在一个
try块中,异常处理程序是按照它出现的顺序被检查的。
大型和十分复杂的程序往往会产生一些很难查找的甚至是
无法避免的运行时错误。 当发生运行时错误时,不能简单地结
束程序运行,而是退回到任务的起点,指出错误,并由用户决
定下一步工作 。 面向对象的异常处理( exception handling)
机制是 C++语言用以解决这个问题的有力工具。
程序的错误有两种, 一种是编译错误, 即语法错误 。 如果
使用了错误的语法, 函数, 结构和类, 程序就无法被生成运行
代码 。 另一种是在运行时发生的错误, 它分为不可预料的逻辑
错误和可以预料的运行异常, 这里所讲的 异常 ( exception) 是
程序可能检测到的, 运行时不正常的情况, 如存储空间耗尽,
数组越界, 被 0除等等, 可以预见可能发生在什么地方, 但是无
法确知怎样发生和何时发生 。 特别在一个大型的程序 ( 软件 )
中, 程序各部分是由不同的小组编写的, 它们由公共接口连起
来, 错误可能就发生在相互的配合上, 也可能发生在事先根本
想不到的个别的条件组合上 。
第十二章 异常处理
为处理可预料的错误, 常用的典型方法是让被调用函数返
回某一个特别的值 ( 或将某个按引用调用传递的参数设置为一
个特别的值 ), 而外层的调用程序则检查这个错误标志, 从而
确定是否产生了某一类型的错误 。 另一种典型方法是当错误发
生时跳出当前的函数体, 控制转向某个专门的错误处理程序,
从而中断正常的控制流 。 这两种方法都是权宜之计, 不能形成
强有力的结构化异常处理模式 。
异常处理机制是用于管理程序运行期间错误的一种结构化
方法。所谓结构化是指程序的控制不会由于产生异常而随意跳
转。异常处理机制将程序中的正常处理代码与异常处理代码显
式区别开来,提高了程序的可读性。
异常处理在 C++编程中已经普遍采用,成为提高程序健壮
性的重要手段之一。
12.1 异常处理的机制
C++语言异常处理机制的基本思想是将异常的检测与处
理分离。当在一个函数体中检测到异常条件存在,但无法确定
相应的处理方法时,将引发一个异常,并由函数的直接或间接
调用检测并处理这个异常。这一基本思想用 3个保留字实现:
throw,try和 catch。其作用是:
( 1) try:标识程序中异常语句块的开始。
( 2) throw:用来创建用户自定义类型的异常错误。
( 3) catch:标识异常错误处理模块的开始。
在 C++程序中,任何需要检测异常的语句(包括函数调
用)都必须在 try语句块中执行,异常必须由紧跟着 try语句后
面的 catch语句来捕获并处理。因而,try与 catch总是结合使
用。 throw,try和 catch语句的一般语法如下:
12.1 异常处理的机制
throw <表达式 >;
try {
//try语句块
}
catch( 类型 1 参数 1)
{
//针对类型 1的异常处理
}
catch ( 类型 2 参数 2)
{
//针对类型 2的异常处理
}
…
catch ( 类型 n 参数 n)
{
//针对类型 n的异常处理
}
12.1 异常处理的机制
请看下面的程序段给出 try块与 catch子句的关系:
int main()
{ int a[9]={1,2,3,4,5,6,7,8,9},b[9]={0},i;
stack <int> istack(8);
try {
for(i=0; i<9; i++) istack.Push(a[i]);
istack.PrintStack();
}
catch(pushOnFull <int> ) {
cerr<<”栈满” <<endl;
}
for(i=0; i<9; i++) cout<<b[i]<<’\t’; cout<<endl;
}
这里有一个 try块,对应压栈的操作语句;也有一个 catch子句
( catch clause),分别处理压栈时的栈满溢出的异常处理。
12.1 异常处理的机制
template <class T>
void Stack<T>::Push(const T &data) {
if(IsFull()) throw pushOnFull<T>(data);
//注意加了括号,是构造一个无名对象
elements[++top]=data;
}
注意 pushOnFull是 类, C++要求抛出的必须是对象,所以必
须有,()”,即 调用构造函数建立一个对象 。异常并非总是类对
象,throw表达式也可以抛出任何类型的对象,如枚举、整数
等等。但最常用的是类对象。 throw表达式 抛出异常 为异常处
理的 第一步 。在堆栈的压栈和出栈操作中发生错误而抛出的异
常,理所当然地应由调用堆栈的程序来处理。
首先,在 C++中异常往往用类( class)来实现,以栈为例,
异常类声明如下,
class pushOnFull{...}; //栈满异常
12.1 异常处理的机制
程序按下列规则控制:
1,如果没有异常发生, 继续执行 try块中的代码, 与 try块相关
联 的 catch子句被忽略, 程序正常执行, main()返回 0。
2,当第一个 try块在 for循环中抛出异常, 则该 for循环退出,
try 块 也 退 出, 去执行 pushOnFull 异 常 的 catch 子句 。
istack.PrintStack()不再执行, 被忽略 。
3.当某条语句抛出异常时,跟在该语句后面的语句将被跳过。
程序执行权交给处理异常的 catch子句,如果没有 catch子句能
够处理异常,则交给 C++标准库中定义的 terminate()。
由 catch字句捕获并处理异常是 第二步 。注意与 catch语
句分别 匹配 的是在压栈成员函数模板中的 throw语句,一个抛
出 pushOnFull类的无名对象。
12.2 捕获异常
catch子句由三部分组成:关键字 catch、圆括号中的异常声
明以及复合语句中的一组语句。 * 注意这不是函数,所以圆括
号中不是形参,而是一个异常类型声明,可以是类型也可以是
对象。
异常声明中也可以是一个对象声明。以栈为例。当栈满时,
要求在异常对象中 保存不能被压入到栈中的值,这时,
pushOnFull类可定义如下:
template <class T> class pushOnFull {
T val;
public:
pushOnFull(T i) { val = i; }
T value() { return _value; }
};
新的私有数据成员 val 保存那些不能被压入栈中的值。该值即
调用构造函数时的实参。
12.2 捕获异常
这样在 catch子句中, 要取得 val,须调用 pushOnFull中的成
员函数 value():
catch( pushOnFull<T> eObj) {
cerr<<” 栈满, <<eObj.value()<<” 未压入
栈, <<endl;
return 1;
}
在 catch子句的异常声明中声明了对象 eObj,用它来调用
pushOnFull类的对象成员函数 value()。异常对象是在抛出
点被创建,与 catch子句是否显式要求创建一个异常对象无关,
该对象总是存在,在 catch子句中只是为了调用异常处理对象
的成员函数才声明为对象,不用类。
对应在 throw表达式中,构造抛出对象也要有实参:
throw pushOnFull(data);
//data即 Push(const &data)中的参数 data
12.3 异常处理的详细处理流程
异常处理的执行过程如下:
( 1) 控制通过正常的顺序执行到达 try语句, 然后执行 try块内
的保护段 。
( 2) 如果在保护段执行期间没有引起异常, 那么跟在 try块后
的 catch子句就不执行, 程序从异常被抛掷的 try块后跟随的
最后一个 catch子句后面的语句继续执行下去 。
( 3) 如果在保护段执行期间或在保护段调用的任何函数中有
异常被抛掷, 则从通过 throw运算数创建的对象中创建一个
异常对象 。 编译器从能够处理抛掷类型的异常的更高执行上
下文中寻找一个 catch子句 ( 或一个能处理任何类型异常的
catch处理程序 ) 。 catch处理程序按其在 try块后出现的顺
序被检查 。 如果没有找到合适的处理程序, 则继续检查下一
个动态封闭的 try块 。 此处理继续下去直到最外层的封闭 try
块被检查完 。
12.3 异常处理的详细处理流程
( 4) 如果匹配的处理器未找到, 则运行函数 terminate将被自
动调用, 而函数 terminate的默认功能是调用 abort终止程序 。
( 5)如果找到了一个匹配的 catch处理程序,且它通过值进行
捕获,则其形参通过拷贝异常对象进行初始化。如果它通过
引用进行捕获,则参量初始化为指向异常对象。
注意,catch处理程序的出现顺序很重要,因为在一个
try块中,异常处理程序是按照它出现的顺序被检查的。