程序异常处理文档格式.docx
《程序异常处理文档格式.docx》由会员分享,可在线阅读,更多相关《程序异常处理文档格式.docx(30页珍藏版)》请在冰点文库上搜索。
继续运行就是异常处理之后,在紧接着异常处理的代码区域中继续运行。
在C++中,异常是指从发生问题的代码区域传递到处理问题的代码区域的一个对象。
见图 :
发生异常的地方在函数k()中,处理异常的地方在其上层函数f()中,处理异常后,函数k()和g()都退栈,然后程序在函数f()中继续运行。
如果不用异常处理机制,在程序中单纯地嵌入错误处理语句,要实现这一目的是艰难的。
异常的基本思想是:
(1)实际的资源分配(如内存申请或文件打开)通常在程序的低层进行,如图中的k()。
(2)当操作失败、无法分配内存或无法打开一个文件时.在逻辑上如何进行处理通常是在程序的高层,如图中的f(),中间还可能有与用户的对话。
(3)异常为从分配资源的代码转向处理错误状态的代码提供了一种表达方式。
如果还存在中间层次的函数,如图中的g(),则为它们释放所分配的内存提供了机会,但这并不包括用于传递错误状态信息的代码。
从中可以看出,C什异常处理的目的,是在异常发生时,尽可能地减小破坏,周密地善后,而不去影响其它部分程序的运行。
-这在大型程序中是非常必要的。
例如对于以前所讲的程序调用关系,如处理文件打开失败异常的方法,那么,异常只能在发生的函数k()中进行处理,无法直接传递到函数f()中,而且调用链中的函数g()的善后处理也十分困难。
二、异常的实现
使用异常的步骤是:
(1)定义异常(try语句块)将那些可能产生错误的语句框定在try语句中;
(2)定义异常处理(catch语句块)
将异常处理的语句放在catch块中,以便异常被传递过来时就处理它;
(3)抛掷异常(throw语句)
检测是否产生异常,若产生异常,则抛掷异常。
例如,下面的程序,设置了防备文件打不开的异常:
例题1
#include<
iostream.h>
stdlib.h>
voidmain(intargc,char**argv)
{ifstreamsource(argv[1]);
//打开文件
charline[128];
try{
if(source.fail())//如果打开失败
throwargv[1];
}
catch(char*s)
{
cout<
erroropeningthefile"
s<
exit
(1);
}
while(!
source.eof())
{source.getline(line,sizeof(line));
line<
source.close();
运行结果:
假定C盘中没有abc.txt文件,有xyz.txt文件,
内容为:
Howareyou?
Fine!
两行语句,
则运行结果为:
在c:
\>
提示符后输入命令ch10_1abc.txt
屏幕显示结果为:
erroropeningthefileabc.txt
若输入命令ch10_1xyz.txt则屏幕显示结果为:
例题2:
一个除零异常:
#include<
doubleDiv(double,double);
voidmain()
{
try
{cout<
7.3/2.0="
Div(7.3,2.0)<
cout<
7.3/0.0="
Div(7.3,0.0)<
7.3/1.0="
Div(7.3,1.0)<
catch(double)
exceptofdevidingzero!
\n"
;
Thatisok.\n"
doubleDiv(doublea,doubleb)
{if(b==0.0)throwb;
returna/b;
运行结果为:
7.3/2.0=3.65
Thatisok.
三、异常处理机制
在处理程序和语句之间的相互作用使异常在大型应用程序中变得复杂。
通常人们希望抛掷被及时捕获,以避免程序突然终止。
此外,跟踪抛掷很重要,因为捕获确定该程序的后继进展。
例如,抛掷和捕获可以用来重新开始程序内的一个过程,或者从应用程序的一部分跳到另一部分,或者回到菜单。
例如,项目的代码说明了异常处理机制。
voidf()
try{g();
}
catch(Range)
{//……}
catch(Size)
catch(…)
voidg()
h();
voidh()
try
{h1();
catch(Size)
{//...
throw10;
catch(Matherr)
voidh1()
//...
throw(Size);
throwRange;
h2();
h3();
throw;
voidh2()
throwMatherr;
voidh3()
throwSize;
函数f()中的catch(...)块,参数为省略号,定义一个"
默认"
的异常处理程序。
通常这个处理程序应在所有异常处理块的最后,因为它与任何throw都匹配,目的是为避免定义的异常处理程序没能捕获抛掷的异常而使程序运行终止。
函数h()中的catch(Size)块,包含有一个抛掷异常语句throw10,当实际执行这条语句时,将沿着调用链向上传递被函数f()中的catch(...)所捕获。
如果没有f()中的catch(...),那么,异常将被系统的terminate()函数调用,后者按常规再调用abort()。
函数h1()中的抛掷throwSize,由于不在本函数的try块中,所以只能沿函
数调用链向上传递,结果被h()中的catch(Size)捕获。
函数h1()中的抛掷throwRange,在try块中,所以首先匹配try块后的异常处理程序,可是没有被捕获,因而它又沿函数调用链向上,在函数f()中,catch(Range)块终于捕获了该抛掷。
函数h1()中的catch(Size)块,包含一个抛掷throw,没有带参数类型,它表示将捕获到的异常对象重新抛掷出去,于是,它将沿函数调用链向上传递,在h()中的catch(Size)块,捕获了该抛掷。
函数h2()中的抛掷throwMatherr,首先传递给h1()中的catch块组,但未能被捕获,然后继续沿调用链向上,在h()中的catch(Matherr)块,捕获了该抛掷。
函数h3()中的抛掷throwSize,向上传递给h1()中的catch块组,被catch(Size)块捕获。
四、使用异常的方法
可以把多个异常组成族系。
构成异常族系的一些实力又数学错误异常族系和文件处理错误异常族系。
在C++代码中把异常组在一起有两种方式:
异常枚举族系和异常派生层次结构。
例如,下面的代码是一个异常枚举族系的例子:
enmuFileErrors{nonExist,wrongformat,diakSeekError,...};
intf()
try{//...throwwrongFormat;
catch(FileErrorsfe)
{switch(fe)
{casenonExist:
casewrongFormat:
//...
casediskSeekError:
在try块中有一个throw,它抛掷一个FileError枚举中的常量。
这个抛掷可被catch(FileErrors)块捕获到,接着后者执行一个switch,对照情况列表,匹配捕获到的枚举常量值。
上面的异常族系也可按异常派生层次结构来实现,如下例
所示:
classFileErrors{};
classNonExist:
publicFileErrors{};
classWrongFormat:
classDiskSeekError:
{try{//...throwWrongFormat;
catch(NonExist)
{//...}
catch(DiskSeekError)
catch(FileErrors)
上面的各异常处理程序块定义了针对类NonExist和DiskSeekError的派生异常类对象,针对FileErrors的异常处理,既捕获FileErrors类对象,也捕获WrongFormat对象。
异常捕获的规则除了前面所说的,必须严格匹配数据类型外,对于类的派生,下列情况可以捕获异常:
(1)异常处理的数据类型是公有基类,抛掷异常的数据类型是派生类;
(2)异常处理的数据类型是指向公有基类的指针,抛掷异常的数据类型是指向派生类的指针。
对于派生层次结构的异常处理,catch块组中的顺序是重要的。
因为"
catch(基类)"
总能够捕获"
throw派生类对象"
。
所以"
块总是放在"
catch(派生类)"
块的后面,以避免"
永远不能捕获异常。
五、异常的再提出(rethrowing)
有时候会发生一个异常处理句柄接收了一个异常却发现不能处理这个异常的情况。
这时,这个异常可以被再提出以便于其它句柄能够更好的处理。
异常的再提出可以通过一个空的throw表达语句来实现。
但是,这种表达语句只能出现于一个异常处理句柄中。
例如,
showWindow();
//提出CoDialogException类异常
catch(CoWindowException&
WinExc)
WinExc.repaint();
//异常再提出
voidg()
f();
catch(CoDialogException&
DialogExc)
{/*异常处理语句*/}
WindowExc)
上述例子中,尽管CoDialogException类异常是由函数f()中的CoWindowException类处理句柄再提出的,但是它仍然由函数g()中的CoDialogException类异常处理句柄来处理。
此外,任何异常都可以通过一种特殊的接收方式catch(...)来接收和处理。
例如下面例子中的f()函数可以接收任何异常并再提出。
catch(...)//接收任何异常
//某些处理语句
值得注意的是,异常的再提出并不对异常对象进行进一步的拷贝。
#include
iostream>
string>
using
namespace
std;
enum
{SUCCESS,
FAILURE};
class
File
{
public:
(const
char
*)
{}
bool
IsValid()
const
{return
false;
}
int
OpenNew()
FAILURE;
};
Exception
{/*..*/};
//general
base
for
exceptions
FileException:
public
FileException(const
*p)
:
s(p)
*
Error()
return
s.c_str();
private:
string
s;
void
func(File&
);
main()
try
//outer
f
("
db.dat"
func(f);
//
1
catch(...)
7
//this
handler
will
catch
the
re-thrown
exception;
//note:
same
exception
type
is
required
caught"
0;
func(File
&
f)
//inner
if
(f.IsValid()
==
false
)
throw
FileException("
2
catch(FileException
fe)
3
//first
chance
to
cope
with
invalid
file
specification"
fe.Error()<
(f.OpenNew()
!
=
SUCCESS)
(5)
//re-throw
original
and
let
a
higher
deal
it
6
在上面的例子中,函数func()在main()中的try
block里被调用
(1)。
第二个在func()中的try
block抛出一个FileException类型的异常
(2)。
这个异常被func()内的catch
block所捕获(3)。
那个catch
block试图通过打开一个新文件进行补救,但是失败了(5),并且FileException异常被重新抛出(6)。
最终,那个重新抛出的异常被main()中的catch(…)所捕获(7)。
六.在异常对象中携带更多的信息
异常对象如同其它类对象一样可以携带信息。
所以一个异常对象可以被用来将一些有用信息从提出点携带到接收处理点。
这些信息可以是当程序在运行过程中出现非正常情况时程序使用者想要知道的。
例如一个程序使用者可能想知道一个矢量的下标,当这个矢量下标超限时。
classCoVector
CoVector(int);
classRange;
int&
operator[](inti);
protected:
int*pInt_;
inttheSize;
classCoVector:
Range
CoVector:
Range(int);
intindex_;
Range:
Range(inti):
index_(i)
{/*...*/}
CoVector:
operator[](inti)
if(0<
=i&
i<
theSize_)
returnpInt_[i];
throwRange(i);
voidf(constCoVector&
v)
inttemp=v[169];
catch(constCoVector:
Range&
r)
cout<
"
badindex="
<
r.index_<
endl;
实际上,catch后面括弧中的表达语句实际上类似于函数的参数定义。
七、句柄的次序
异常处理句柄有先后次序之分。
因为一个派生(derived)异常类对象可以被几个句柄接收,所以在排列异常处理句柄顺序时应该特别小心。
另外,一个类型严格匹配的处理句柄并不比一个需要类型转换的处理句柄更有优先权。
例如下面的例子就很糟糕。
classCoWindow
{/*...*/};
classCoButton:
publicCoWindow
voidf(intv)
typedefvoid(*PCF)(constchar*);
if(v)throw&
v;
//其它表达语句
catch(void*pVoid){/*...*/}
catch(PCFpFunction){/*...*/}
catch(constCoWindow&
win){/*...*/}
catch(constCoButton&
button){/*...*/}
catch(...){/*...*/}
return;
在上面例子中,(void*)处理句柄不可能允许它后面的PCF处理句柄被调用。
类似的,因为CoWindow类处理句柄将会接收任何CoWindow类及它的衍生类对象,所以CoButton类的处理句柄也不会被调用。
依赖于你所使用的编译器,有的编译器可能在编译时警告你一个从类B派生来的类D句柄放在类B句柄后面。
但是,如果一个接收任何异常的句柄catch(...)不是最后一个句柄,编译器会给出编译错误。
派生类组织异常;
ClassMatherr{};
ClassOverflow:
publicMatherr{};
ClassUnderflow:
Class:
Zerodivide:
publicMatherr{};
…
Try{
Cath(Overflow)
Cath(Uunderflow)
{
Cath(Zerodivideflow)
Cath(Matherr)
八、异常提出过程中的对象构造和析构
当一个程序由于异常而中断时,所有的从try开始构造的自动变量类的对象都会被清除、释放。
这种调用自动变量类的析构函数的过程称为堆栈清除(stackunwinding)。
下面给出了一个实例。
classCoClass
intv_;
CoClass(intv=0):
v_(v)
CoClass(int):
v_<
~CoClass()
~CoClass():
classCoError
CoError(intv=0):
v_(v)
CoError(int):
CoError(constCoError&
ve):
v_(ve.v_)
):
~CoError()
~CoError():
intf(intv)
if(v==13)
CoClassvc(0);
throwCoError(v);
returnv;
intmain()
CoClassvc(169);
f(13);
catch(constCoError&
e)
Caught:
e.v_<
return0;
这个例子给出了下面输出结果。
当catch(constCoErrore)时
169