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