2005-4-27 1
C++大学基础教程
第13章 异常处理
北京邮电大学电信工程学院
计算机技术中心
程序设计的要求之一就是程序的健
壮性。希望程序在运行时能够不出
或者少出问题。但是,在程序的实
际运行时,总会有一些因素会导致
程序不能正常运行。异常处理
(Exception Handling)就是要提
出或者是研究一种机制,能够较好
的处理程序不能正常运行的问题。
第十三章 异常处理
13.1 异常和异常处理
13.2 C++异常处理机制
13.3 用类的对象传递异常
13.4 异常处理中的退栈和对象析构
13.1 异常和异常处理
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-5-
13.1.1 异常及其特点
异常(Exceptions)是程序在运行时可能出现
的会导致程序运行终止的错误。
编译系统检查出来的语法错误,导致程序运行
结果不正确的逻辑错误,都不属于异常的范
围。
异常是一个可以正确运行的程序在运行中可能
发生的错误。
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-6-
13.1.1 异常及其特点
常见的异常,如:
? 系统资源不足。如内存不足,不可以动态申
请内存空间;磁盘空间不足,不能打开新的
输出文件,等。
? 用户操作错误导致运算关系不正确。如出现
分母为0,数学运算溢出,数组越界,参数
类型不能转换,等。
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-7-
13.1.1 异常及其特点
异常有以下的一些特点:
? 偶然性。程序运行中,异常并不总是会发生
的。
? 可预见性。异常的存在和出现是可以预见
的。
? 严重性。一旦异常发生,程序可能终止,或
者运行的结果不可预知。
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-8-
13.1.2 异常处理方法及举例
对于程序中的异常,通常有三种处理的
方法:
? 不作处理。很多程序实际上就是不处理异常
的。
? 发布相应的错误信息,然后,终止程序的运
行。在C语言的程序中,往往就是这样处理
的。
? 适当的处理异常,一般应该使程序可以继续
运行。
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-9-
13.1.2 异常处理方法及举例
一般来说,异常处理(Exception Handling)
就是在程序运行时对异常进行检测和控制。
而在C++中,异常处理(EH)就是用C++提供的
try-throw-catch的模式进行异常处理的机
制。
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-10-
例13.1 程序将连续地输入两个实数 ,通过调用函数,返回这
两个数相除的商。并且要注意除数不能为0。
//例13.1 用一般的方法处理除法溢出
#include <iostream.h>
#include <stdlib.h>
double divide(double a, double b)
{
if (b == 0) //检测分母是不是
为0
{
cout << "除数不可以等于0 !"<<endl;
abort(); //调用abort函数终止运行
}
return a/b;
}
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-11-
void main()
{double x,y,z;
cout<<"输入两个实数 x 和 y :";
while (cin >> x >> y)
{ z = divide(x,y);
cout << "x 除以 y 等于 " << z << "\n";
cout << "输入下一组数 <q 表示结束>: ";
}
cout << "Bye!\n";
}
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-12-
如果出现分母为0 的情况,运行将出现以下结果:
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-13-
13.1.2 异常处理方法及举例
这个程序中,对于除数为0的处理有这样
的特点:
? 异常的检测和处理都是在一个程序模块
(divide函数)中进行的;
? 由于函数的返回值是double型的数据,因
此,即使检测到除数为0的情况,也不能通
过返回值来反映这个异常。只能调用函数
abort终止程序的运行。
13.2 C++异常处理机制
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-15-
13.2 C++异常处理机制
C++处理异常有两个基本的做法:
? 异常的检测和处理是在不同的代码段中进行的。一
般的说法是在 “try”部分检测异常, “catch”部分处
理异常。
? 由于异常的检测和处理不是在同一个代码段中进行
的,在检测异常和处理异常的代码段之间需要有一
种传递异常信息的机制,在C++中是通过 “对象 ”来
传递异常的。这种对象可以是一种简单的数据(如
整数),也可以是系统定义或用户自定义的类的对
象。
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-16-
13.2 C++异常处理机制
C++异常处理的语法可以表述如下:
try
{ 受保护语句 ;
throw 异常 ;
其他语句 ;
}
catch(异常类型 )
{异常处理语句 ;
}
检测和抛掷
异常
扑获和处理
异常
try模块
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-17-
13.2 C++异常处理机制
在C++术语中,异常(Exception,注意结尾没
有s)是作为专用名词出现的。就是将异常检
测程序所抛掷的 “带有异常信息的对象 ”称为
“异常 ”。
而将捕获异常的处理程序称为异常处理程序
(Exception Handler)。
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-18-
13.2 C++异常处理机制
在try复合语句中,可以调用其他函数,在所
调用的函数中检测和抛掷异常,而不是在try
复合语句中直接抛掷异常。这个所调用的函
数,仍然是属于这个try模块的,所以这个模
块中的catch部分,仍然可以捕获它所抛掷的
异常并进行处理。
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-19-
例13.2 用C++的异常处理机制,重新处理例13.1。
//例13.2用C++的异常处理机制,处理除法溢出
#include <iostream.h>
#include <stdlib.h>
double divide(double a, double b)
{
if (b == 0)
{
throw "输入错误:除数不可以等于0 !";
}
return a/b;
}
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-20-
void main()
{double x,y,z;
cout<<"输入两个实数 x 和 y :";
while (cin >> x >> y)
{try
{ z = divide(x,y);
}
catch (const char * s) // start of exception handler
{
cout << s << "\n";
cout << "输入一对新的实数: ";
continue;
} // end of handler
cout << "x 除以 y 等于 " << z << "\n";
cout << "输入下一组数 <q 表示结束>: ";
}
cout << "程序结束,再见!\n";
}
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-21-
13.2 C++异常处理机制
程序运行的一种结果是:
输入两个实数 x 和 y :1.2 3.2
x 除以 y 等于 0.375
输入下一组数 <q 表示结束>: 3.4 0
输入错误:除数不可以等于0 !
输入一对新的实数: 2.3 4.5
x 除以 y 等于 0.511111
输入下一组数 <q 表示结束>: q
程序结束,再见!
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-22-
13.2 C++异常处理机制
阅读这个程序,可以注意以下几点:
? 在try的复合语句中,调用了函数divide。因此,
尽管divide函数是在try模块的外面定义的,它仍
然是属于try模块:在try语句块中运行;
? divide函数检测到异常后,抛掷出一个字符串作为
异常对象,异常的类型就是字符串类型;
? catch程序块指定的异常对象类型是char*,可以捕
获字符串异常。捕获异常后的处理方式是通过
continue语句,跳过本次循环,也不输出结果,直
接进入下一次循环,要求用户再输入一对实数。
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-23-
13.2 C++异常处理机制
例 13.2的执行过程可以简要的表示如下:
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-24-
13.2 C++异常处理机制
另外,在编写带有异常处理的程序时,还要注
意:
? try语句块和 catch语句块是一个整体, 两者之
间不能有其他的语句 ;
? 一个 try语句块后面可以有多个 catch语句,但
是,不可以几个 try语句块后面用一个 catch语
句。
13.3 用类的对象传递异常
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-26-
13.3 用类的对象传递异常
throw语句所传递的异常,可以是各种类型
的:整型、实型、字符型、指针,等等。也可
以用类对象来传递异常。
对象就是既有数据属性,也有行为属性。使用
对象来传递异常,就是既可以传递和异常有关
的数据属性,也可以传递和处理异常有关的行
为或者方法。
专门用来传递异常的类称为异常类。异常类可
以是用户自定义的,也可以是系统提供的
exception类。
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-27-
13.3.1用户自定义类的对象传递异常
我们用第十章中的栈类模板来作为例子,类模
板中两个主要的函数push和pop的定义中,都
安排了错误检查的语句,以检查栈空或者栈满
的错误。由于pop函数是有返回值的,在栈空
的条件下,是没有数据可以出栈的。尽管pop
函数可以检测到这种错误,但是,也不可能正
常的返回,于是只好通过exit函数调用结束程
序的执行。
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-28-
13.3.1用户自定义类的对象传递异常
现在,我们用C++异常处理的机制,改写这个
程序。要求改写后的程序不仅有更好的可读
性,而且在栈空不能出栈时,程序也可以继续
运行,使得程序有更好的健壮性。
可以定义两个异常类:一个是 “栈空异常 ”类,
另一个是 “栈满异常 ”类。在try块中,如果检
测到 “栈空异常 ”,就throw一个
“StackEmptyException”类的对象。如果检测
到 “栈满异常 ”,就throw一个
“StackOverflowException”类的对象。
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-29-
13.3.1用户自定义类的对象传递异常
例13.3 通过对象传递异常。用C++异常处理机
制来处理栈操作中的 “栈空异常 ”和 “栈满异
常 ”。定义两个相应的异常类。通过异常类对
象来传递检测到的异常,并且对异常进行处
理。要求在栈空的时候用pop函数出栈失败
时,程序的运行也不终止。
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-30-
//例13.3:带有异常处理的栈
#include <iostream>
using namespace std;
class StackOverflowException //栈满异常类
{public: StackOverflowException() {}
~StackOverflowException() {}
void getMessage()
{ cout << "异常:栈满不能入栈。" << endl; }
};
class StackEmptyException //栈空异常类
{public: StackEmptyException() {}
~StackEmptyException() {}
void getMessage()
{ cout << "异常:栈空不能出栈。" << endl;
}
};
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-31-
template <class T, int i> // 类
模板定义
class MyStack
{ T StackBuffer[i];
int size;
int top;
public:
MyStack( void ) : size( i ) {top = i;};
void push( const T item );
T pop( void );
};
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-32-
template <class T, int i> //push成员函数定
义
void MyStack< T, i >::push( const T item )
{ if( top >0 )
StackBuffer[--top] = item;
else
throw StackOverflowException(); //抛掷对象
异常
return;
}
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-33-
template <class T, int i> //pop成员函数定
义
T MyStack< T, i >::pop( void )
{ if( top < i )
return StackBuffer[top++];
else
throw StackEmptyException();
//抛掷另一个对象异常
}
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-34-
void main() //带有异常处理的类模板测试
程序
{MyStack<int,5> ss;
for(int i=0;i<10;i++)
{try
{if(i%3)cout<<ss.pop()<<endl;
else ss.push(i);
}
catch (StackOverflowException &e)
{ e.getMessage();
}
catch (StackEmptyException &e)
{ e.getMessage();
}
}
cout<<"Bye\n";
}
程序执行的结果是:
0
异常:栈空不能出栈。
3
异常:栈空不能出栈。
6
异常:栈空不能出栈。
Bye
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-35-
13.3.1用户自定义类的对象传递异常
这个例子和例 13.2有一些明显不同的地方:
? 通过对象传递参数。具体来说,是在 throw语句
中直接调用异常类的构造函数,生成一个无名
对象(如: throw
StackEmptyException();),来传递异常的。
? 在 catch语句中规定的异常类型则是异常类对象
的引用。当然,也可以直接用异常类对象作为
异常。
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-36-
13.3.1用户自定义类的对象传递异常
? 通过异常类对象的引用,直接调用异常类的成员函
数 getMessage,来处理异常。
? 在 try语句块后面直接有两个 catch语句来捕获异
常。也就是说,要处理的异常增加时, catch语句
的数目也要增加。
? 运行结果表明, 10次循环都已经完成。没有出现因
为空栈时不能出栈而退出运行的情况。
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-37-
13.3.2 用 exception类对象传递异常
C++提供了一个专门用于传递异常的类: exception
类。可以通过 exception类的对象来传递异常。
class exception
{ public:
exception(); //默认构造函数
exception(char *); //字符串作参数的构造函数
exception(const exception&);
exception& operator= (const exception&);
virtual ~exception(); //虚析构函数
virtual char * what() const;//what()虚函数
private:
char * m_what;
};
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-38-
13.3.2 用 exception类对象传递异常
其中和传递异常最直接有关的函数有两个:
? 带参数的构造函数。参数是字符串,一般就是
检测到异常后要显示的异常信息。
? what()函数。返回值就是构造exception类对象
时所输入的字符串。可以直接用插入运算符
“<<”在显示器上显示。
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-39-
13.3.2 用 exception类对象传递异常
如果捕获到exception类对象后,只要显示
关于异常的信息,则可以直接使用
exception类。如果除了错误信息外,还需
要显示其他信息,或者作其他的操作,则可
以定义一个exception类的派生类,在派生
类中可以定义虚函数what的重载函数,以便
增加新的信息的显示。
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-40-
13.3.2 用 exception类对象传递异常
例13.4 定义一个简单的数组类。在数组类
中重载 “[ ]”运算符,目的是对于数组元素
的下标进行检测。如果发现数组元素下标越
界,就抛掷一个对象来传递异常。并且要求
处理异常时可以显示越界的下标值。
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-41-
13.3.2 用 exception类对象传递异常
我们使用 exception类的对 象来传递对象。但是,
直接使用 exception类对象 还是不能满足例题的要
求。因为不能传递越界的下标值。
为此,可以定义一个ex ception 类的派生类
ArrayOverflow。其中包含一个数据成员k。在构
造ArrayOverflow 类对象时,用越界的下标值初始
化这个数据k。在catch块中捕获到这个对象后,
可以设法显示对象的k值。
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-42-
//例13.4 用exception类参与处理异常
#include <iostream>
#include <exception>
using namespace std;
class ArrayOverflow : public exception
//exception 类 的
派生类
{public:
ArrayOverflow::ArrayOverflow(int i)
: exception( "数组越界异常!\n" ) {k=i;}
const char * what() //重新定义的what()函数
{cout<<"数组下标"<<k<<"越界\n";
return exception::what();
}
private:
int k;
}; // 派 生 类 ArrayOverfow定义结束
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-43-
class MyArray //数组类的定义
{ int *p; //数组首地址
int sz;
//数组大小
public:
MyArray(int s) { p=new int [s]; sz=s; } //构
造函数
~MyArray( ) { delete [ ] p ; }
int size( ) { return sz; }
int& operator[ ] (int i); //重载[]运算符的
原型
};
int& MyArray:: operator[ ] (int i) //重载[]运
算符
{if(i>=0 && i<sz) return p[i];
throw ArrayOverflow( i );
}
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-44-
void f(MyArray &v);
void main()
{MyArray A(10);
f(A);
}
void f( MyArray& v )
{//……
for(int i=0;i<3;i++)
{try { if(i!=1) {v[i]=i; cout<<v[i]<<endl;}
else v[v.size( )+10]=10;
}
catch( ArrayOverflow &r )
{cout<<r.what();
}
} //for循环结束
}
程序运行后输出:
0
数组下标20越界
数组越界异常!
2
13.4 异常处理中的退栈
和对象析构
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-46-
13.4异常处理中的退栈和对象析构
在函数调用时,函数中定义的自动变量
将在堆栈中存放。结束函数调用时,这
些自动变量就会从堆栈中弹出,不再占
用堆栈的空间,这个过程有时被称为 “退
栈 ”(Stack unwinding)。其他的结束
动作还包括调用析构函数,释放函数中
定义的对象。
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-47-
13.4异常处理中的退栈和对象析构
但是,如果函数执行时出现异常,并且
只是采用简单的显示异常信息,然后退
出(exit)程序的做法,则程序的执行
就会突然中断,结束函数调用时必须完
成的退栈和对象释放的操作也不会进
行。这样的结果是很不希望的。
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-48-
float function3(int k) //function3中可能有异常
{if(k==0)
{cout<<"function3中发生异常\n"; //显示异常信息
exit(1);} //退出执行
else return 123/k;
}
void function2(int n)
{ForTest A12;
function3(n); //第三次调用
}
void function1(int m)
{ForTest A11;
function2(m); //第二次调用
}
void main()
{ function1(0); //第一次调用
}
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-49-
13.4异常处理中的退栈和对象析构
程序运行后显示:
function3中发生异常
在function1和fuction2中分别定义了ForTest
类的对象。如果函数可以正常退出,这些对象
将被释放。
但是,程序运行后只显示了异常信息。没有析
构函数被调用的迹象。说明所创建的对象没有
被释放。
如果采用C++的异常处理机制来进行处理。情
况就会完全不同。
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-50-
13.4异常处理中的退栈和对象析构
如果在 function3中用 throw语句来抛掷
异常,就会开始 function3的退栈。
然后,返回到函数 function2开始
function2的退栈,,并且调用 ForTest
类析构函数,释放对象 A12。
接着,返回到函数 function1开始
function1的退栈,,并且调用 ForTest
类析构函数,释放对象 A11。
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-51-
例13.6 用C++异常处理机制来重新编写例13.5。
// 例 13.6: 用C++异常处理机制,对象可以完全释放
// Demonstrating stack unwinding.
#include <iostream>
#include <stdlib.h>
#include <exception>
using namespace std;
class ForTest
{public:
~ForTest() //析构函数
{cout<<"ForTest类析构函数被调用\n";
}
};
//ForTest类定义结束
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-52-
float function3(int k) //function3中可能有
异常
{if(k==0)
throw exception( "function3中出 现异常\n" ); //抛掷
异常类对象
else return 123/k;
}
void function2(int n)
{ForTest A12;
function3(n); //第三次调用
}
void function1(int m)
{ForTest A11;
function2(m); //第二次调用
}
2005-4-27
北京邮电大学电信工程学院计算机技术中心
-53-
void main()
{try
{ function1(0); // 第
一次调用
}
catch(exception &error)
{cout<<error.what()<<endl;
}
}
程序运行结果显示:
ForTest类析构函数被调用
ForTest类析构函数被调用
function3中出现异常
总结
本章介绍了 C++异常处理的机制。在程序设计
中使用这样的异常 处理机制,有助于提高程序
的健壮性、可读性 。而且可以防止因为程序不
正常结束而导致的 资源泄漏,如创建的对象不
能释放等。
本章没有介绍多种 异常处理的结构,而是希望
读者能够抓住最基本的结构: try模块。
另外一个重要的内 容就是通过用户自定义类的
对象来传递异常。
总结
模板分为函数模板和类模板,定义的方
式基本是相同的。在使用上稍有差别:
函数模板通过函数参数的虚实结合就能
得到具体的模板函数。
而使用类模板时,要在类模板名后面具
体说明模板类的实际数据类型。