C++程序设计第十五章异常_第1页
C++程序设计第十五章异常_第2页
C++程序设计第十五章异常_第3页
C++程序设计第十五章异常_第4页
C++程序设计第十五章异常_第5页
已阅读5页,还剩27页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、C+程序设计第15章异常程序中经常要检查处理各种错误情形,如果用传统的流程控制语句来处理,很容易使程序逻辑混乱。异常(exception)就是一种专门用于检测错误并处理的一种机制,使程序保持逻辑清晰,并改进程序的可靠性。C+语言提供了基本的异常处理机制。本章主要介绍异常的概念、语句、异常类型架构及应用。可靠的编程应尽可能地、及时地检测到各种异常情形,尽可能在本地处理。尽管有时自己不能处理,也应该向调用方提供详细的出错信息,使调用方能得到充分信息,从而采取合适方式来处理异常。15.1异常的概念异常是什么概念?异常就是在程序运行中发生的难以预料的、不正常的事件而导致偏离正常流程的现象。例如:访问数

2、组元素的下标越界,在越界时又写入了数据;用new动态申请内存而返回空指针(可能是因内存不足);算术运算上溢出或下溢出;整数除法中除数为0;调用函数时提供了无效实参,如指针实参为空指针(如用空指针来调用strlen函数);通过挂空指针或挂空引用来访问对象;输入整数或浮点数失败;I/O错误,等等。上面列出的情形之一如果发生,就可能导致运行错误而终止程序。发生异常将导致正常流程不能进行,就需要对异常进行处理。那么异常处理是什么概念?异常处理(exceptionhandling)就是在运行时刻对异常进行检测、捕获、提示、传递等过程。如果采用传统的if-else语句来检测处理所有可能发生的异常,很容易导

3、致程序流程混乱,分不清正常流程与异常处理,而且在处理一个异常时往往又引入了新的异常。假设要设计一个函数,从一个文本文件中读取数据得到一个float矩阵。该文件应存放一个m*n的float矩阵,头两个整数说明其行数m和列数n。你要把它读入并创建一个矩阵对象,以备下一步计算。如果你认为文本文件不会有错,完全按正常编程,不超过10条语句就能完成。如果这个文本文件是别人提供的,而且你的函数将提供给其它人使用,那么你在每一步都要考虑可能出现的错误,此时就可能需要30条语句来处理。例如,可能的出错情形如下:打开文件出错,文件名可能有误;读取行数m或者列数n可能出错;读取每个元素时都可能出错;矩阵数据可能不

4、完整,也会出错。如果你用传统方式来判断处理以上这些问题,就会发现正常的流程被淹没在多种异常判断处理之中。此时就需要有一种统一的机制能将正常流程与异常处理分开描述,而保持程序逻辑清晰可读,同时各种异常情形能被集中处理。C+提供了引发异常语句throw和捕获处理异常语句try-catch。它们构成了一种特殊的流程控制。用throw引发的每个异常都可以描述为一个对象或一个值。在程序中,每一种异常都可以描述为一种类型,可能是自定义的类,也可能是简单的整数或字符串。在比较完善的编程中,经常用不同的类来描述不同的异常,建立一个异常类型的继承结构,以方便对异常类型的管理和重用。一个函数中当检测到某种异常发生

5、,但自己往往不知道应该如何处理,此时就应该通知调用方知道发生了什么异常。处理异常的一般方式是:在一个函数中发现一个错误但不能处理,就用throw语句引发一个异常,希望它的(直接或间接)调用方能够捕获并处理这个异常。函数的调用方如果能解决该异常,就可使用try-catch语句来捕获并处理这种异常。如果调用方不能捕获处理该异常,异常就被传递到它自己的调用方,最后到达main函数。异常的发生、传递与处理的过程与函数调用堆栈相关。如图15.1所示。main函数中调用f函数,f函数再调用g函数。如果g函数执行return就正常返回到f。如果f执行到return就正常返回到main。这是正常流程。如果g函

6、数在运行时因检测到某种错误而用throw语句引发一个异常,而自己也没有捕获处理,此时该异常就被传递到f的调用方g函数,而且g函数执行终止(注意,不是返回)。对于f来说就是g函数调用发生异常。此时如果f函数没有捕获该异常,那么异常又被传递到它的调用方main函数,此时f函数执行终止。同理,此时如果main也没有捕获该异常,那么程序就必须终止。此时系统可能会跳出一个对话框告知你发生了运行错误。在发生异常、传递异常的过程中,如果有一个函数用try-catch捕获了该异常,就不会导致程序终止。在运行时刻,一个异常只能被捕获一次。假设f函数捕获了这个异常,那么对于它的调用方main函数来说,就等于没有发

7、生异常。异常编程的目的是改善程序的可靠性。在大型复杂程序中,完全不发生异常几乎不可能,用传统的if-else语句来检查所有可能的异常情形,也有很大困难。编程正确性总是依赖某些假设成立为前提,异常编程就是要分析识别这些假设不成立的情形,采用面向对象编程技术建立各种异常类型并形成继承性架构,以处理程序中可能发生的各类异常。15.异2常类型的架构C+的异常类型可以是任何类型,既可以是基本类型,如int整数、char*字符串,也可以是自定义类型。在比较规范的编程中,往往不能将基本类型作为异常类型。这是因为基本类型所能表示的异常种类有限。例如在一个程序中int类型只能表示一种异常情形,如果在不同函数中多

8、处引发不同语义的int异常,就很难区别不同int值的含义。可能表示访问数组的下标越界,也可能表示打开文件不成功。在比较规范的编程中往往根据各种错误情形,利用类的继承性建立一个异常类型的架构,作用如下:对所处理的各种错误情形进行准确描述、抽象和归类。方便扩展新的异常类型。在编程中方便选取引发正确的异常类型,也方便按类型来捕获处理异常。图15.2是定义在exception和vstdexcept中的一个异常类型架构。在头文件exception中定义了基类exception和一组函数,在vstdexcept中定义了一组派生类,表示各类具体的异常。标准模板库STL中的部分函数就利用了这个架构。下面简单介

9、绍各种异常类型。类exception是所有异常类的基类,其公共成员如下:classexceptionpublic:exception()throw();/缺省构造函数exception(constexception&rhs)throw();/拷贝构造函数exception&operator=(constexception&rhs)throw();/赋值操作函数virtualexception()throw();/虚析构函数virtualconstchar*what()constthrow();/虚函数;注意到每个函数原型末尾都有“throw()”,称为函数的异常规范(exception-spe

10、cification),括号中为空说明该函数中不会引发任何异常出来。如果一个函数在执行时可能引发某种异常类型,就应该在“thow(异常类型表)”中说明,以告知调用方。每个异常对象都至少包含一个字符串,来说明异常发生的原因或出错性质,称为出错信息。因此大多派生异常类都提供含字符串形参的构造函数。例如:classinvalid_argument:publiclogic_errorpublic:invalid_argument(conststring&what_arg);/构造函数;一般地,派生类继承基类的虚函数what(),返回出错信息。如果需要的话,派生类可以改写这个虚函数,以提供更多信息。类l

11、ogic_error表示逻辑错误的异常类型,此类错误是在特定代码执行之前就违背了某些前置条件,例如,数据越界out_of_range,函数调用实参无效invalid_argument等,也包括特定领域相关的错误domain_error。读者可自行扩展新类型。例如,访问数据的下标越界可作为out_of_range的派生类。一些逻辑错误意味着编程有误,一般通过改进编程能避免。类runtime_error表示运行期错误,在程序执行期间才能检测的错误。例如算术运算可能导致上溢出overflow_error、下溢出underflow_error、数值越界range_error等。读者可自行扩展新的类型,

12、如空指针错误可作为runtime_error的派生类。一些运行期错误有一定偶然性,与执行环境有关,如内存不足、打开文件失败等,此类错误并不能通过改进自身编程来消除。一个异常类所包含的信息越多,对于此类错误的检测和处理就越有利。例如,要说明下标越界错误,就应该说明该下标的当前值是多少,可能的话,还应说明合理的下标范围。这需要添加新的数据成员以及相应的成员函数。例如:classIndex_out_of_range:publicout_of_rangeconstintindex;public:Index_out_of_range(intindex1,conststring&what_arg):ind

13、ex(index1),out_of_range(what_arg)intgetIndex()constreturnindex;再如,要说明读取文件到特定位置时发生数据错误,就应该说明文件名、出错位置、所读到的数据等信息。后面将详细介绍这些派生类的设计。大多数异常派生类都很简短,关键是对异常的识别和命名。C+异常分为两类:有命名的和未命名的。有命名的异常是有类型的,基本类型(如int)或字符串(char*)或自定义类型。未命名的异常是在运行时刻某种底层错误引起的,例如,整数相除时除数为0,通过挂空指针或挂空引用来访问对象,破坏当前函数的堆栈使函数返回到错误地址等。未命名异常虽然也能被捕获,但不能

14、提供确切的出错信息。建立异常类型架构本质上就是对各种异常情形的识别与命名,对于异常处理具有重要作用。15.3异常处理语句C+语言的异常处理语句包括引发异常语句throw和捕获处理语句try-catch。语句引发异常语句的语法格式为:throw表达式;其中,关键字throw表示要引发一个异常到当前作用域之外。表达式值的类型作为异常事件的类型,并将表达式的值传给捕获处理该类型异常的程序。表达式的值可能是一个基本类型的值,也可能是一个对象。如果要引发一个对象,对象类应该事先设计好。一个类表示了一种异常事件,应描述该类异常发生的原因、语境以及可能的处理方法等。如果在一个函数编程中发现了自己不能处理的错

15、误情形,就可使用throw语句引发一个异常,将它引发到当前作用域之外。如果当前作用域是一个函数,就将异常传递给函数的调用方,让调用方来处理。throw与return相似,表达式也相似,都会中止后面代码的执行。throw语句执行将控制流转到异常捕获语句处理。这将导致throw语句下面相邻语句不能执行,而且会自动回收当前作用域中的局部变量。例如:throwindex;/弓I发一个int异常,index是一个int变量throwindexoutofrange;/弓I发一个constchar*异常throwinvalid_argument(denominatoriszero);/弓发invalid_a

16、rgument异常最后一个throw语句执行过程是,先创建一个invalid_argument对象,然后再将该对象弓发到当前作用域之外。throw与return的含义不同。一个函数的返回值表示正常执行的结果,要作为显式说明的函数规范。throw语句虽然也能终止当前函数的执行,但表示不正常的执行结果。一个函数只有一种返回类型,但可能弓发多种类型的异常。为了说明一个函数可能引发哪些类型的异常,可用异常规范(exceptionspecification)来说明,就是“throw(异常类型表)”。例如下面是一个求商函数:doublequotient(intnumrator,intdenominator

17、)throw(invalid_argument)if(denominator=0)throwinvalid_argument(denominatoriszero);returndouble(numrator)/denominator;该函数的第一个形参除以第二个形参,返回商作为结果。该函数的原型中包含了异常规范:throw(invalidargument),说明该函数的调用可能引发invalid_argument异常。函数体中检查第二个形参(即除数),如果除数为0,就弓发该异常。尽管异常规范目前还起不到语法检验的作用,但起码能告知函数的调用方注意捕获哪些类型的异常,而不是仅仅等待函数的返回值。

18、异常对函数设计具有重要作用。传统的C函数设计不用异常。如果函数有多种结果,返回类型只能有一个,就要添加形参来表示其它结果。添加的形参往往是指针类型,在调用时要提供变量地址作为实参,在调用返回之后再判断得到什么结果。例如,一个求商函数可能设计如下:doublequotient(intnumrator,intdenominator,int*isValid)if(denominator=0)*isValid=0;/用0表示无效除数return0;*isValid=1;/用1表示有效除数returndouble(numrator)/denominator;上面函数中商作为返回值,添加了一个引用形参来表

19、示除数是否有效。另一种可行的C函数设计是返回一个int值,0表示无效除数,1表示有效除数,而将商作为形参,如下所示:intquotient(intnumrator,intdenominator,double*result)if(denominator=0)return0;*result=double(numrator)/denominator;return1;可以看出,传统的C函数设计把除数为0作为一种特殊情形,用if语句加以判断处理,需要更多的函数形参,导致函数定义复杂化。另一方面,调用方在调用函数返回之后,就要立即用一个if语句来判断返回值是否为1,如为1,商才有效,如不为1,商无效。这对

20、调用方有一种强迫性,必须立即做出判断,这将导致程序逻辑复杂化,而且难以清晰表达正常流程。异常是C+提供的一种新概念,表示了偏离正常流程的小概率事件。异常不应该使正常流程的描述复杂化,也不应该让调用方忽视可能发生的异常。调用方可以选择在适当的地方集中捕获处理多种异常,就要用到try-catch语句。使用throw语句,应注意以下要点:根据当前异常情形,应选择更准确、更具体的异常类型来引发,而避免引发抽象的类型。例如,如果在new申请内存之后,如果发现返回空指针,此时应引发OutOfMemory类型的异常,而不是NullPointer异常,也不是更抽象的runtime_error或者excepti

21、on。准确具体的异常信息对于调用方的处理非常重要,否则就可能导致误解。如果一个函数中使用throw语句引发异常到函数之外,应该在函数原型中用异常规范准确描述,即“throw(异常类型表)”,使调用方知道可能引发的异常类型,提醒调用方不要忽视。虽然throw语句可以在函数中任何地方执行,但应尽可能避免在构造函数、析构函数中使用throw语句,因为这将导致对象的构建和撤销过程中出现底层内存错误,可能会导致程序在捕获到异常之前就被终止。后面15.8节将分析其原因。一般来说,异常发生总是有条件的,往往在一条if语句检测到某个假设条件不成立时,才用throw语句引发异常,以阻止下面代码执行。在一个函数中

22、无条件引发异常,只有一个理由,就是不想让其它函数调用,例如,一些实体类的拷贝构造函数和赋值操作函数如果不想被调用,就将这些函数设为私有,同时用一条throw语句避免本类其它函数执行。千万不要认为,只要我的编程中没有throw语句就不会引发异常,没有异常就是可靠的。你可以暂时忽略异常,但当假设条件不满足,异常总会发生。当异常发生时你就不知道在何处出现异常,也不知道什么原因导致异常,更不知道如何处理能使程序继续执行。语句捕获处理异常的语句是try-catch语句,一条try-catch语句由一个try子句(一条复合语句)和多个catch子句组成。一个catch子句包括一个异常类型及变量和一个异常处

23、理器(一条复合语句)。语法格式如下:try可能引发异常的语句序列;/受保护代码catch(异常类型1异常变量1)处理代码1;/异常处理器1catch(异常类型2异常变量2)处理代码2;/异常处理器2.catch(.)处理代码;/异常处理器其中,关键字try之后的一个复合语句称为try子句。这个复合语句中的代码被称为受保护代码,包含多条语句。受保护代码描述正常的执行流程,但这些语句的执行却可能引发异常。如果执行没有发生异常,try-catch语句就正常结束,开始执行其下面语句。如果引发了某种类型的异常,就按catch子句顺序逐个匹配异常类型,捕获并处理该异常。如果异常被捕获,而且处理过程中未引发

24、新的异常,try-catch语句就正常结束。如果异常未被捕获,该异常就被引发到外层作用域。图15.3表示了try-catch语句的组成结构。图语句的组成结构一条语句执行引发异常,有以下3种可能的原因:1、该语句是throw语句。2、调用函数引发了异常。3、表达式执行引发了未命名的异常,如整数除数为0、挂空访问等例如下面try-catch语句,调用了前面介绍的求商函数quotient。tryTOC o 1-5 h zresult=quotient(n1,n2);/AcoutThequotientisresult;/B/.catch(invalid_argumentex)/Ccoutinvalid

25、_argument:ex.what();/DA行调用quotient函数,如果没有引发异常,就执行B行,然后try-catch语句就执行完毕。如果A行引发了某种异常,B行就不执行,从C行开始匹配异常类型,因为A行函数调用可能引发的异常类型正式catch子句要捕获的异常类型invalid_argument,故此该异常对象就替代了ex形参,之后再执行后面的一个复合语句,D行调用异常对象ex的成员函数得到错误信息,然后打印出来。try-catch语句执行完毕。无论是否发生异常,这个try-catch语句都能执行完毕,下面语句都能执行。异常是按其类型进行捕获处理的。一个catch子句仅捕获一类异常。一

26、个catch子句由一个异常类型及变量和一个异常处理器(一条复合语句)构成。异常类型及变量指明要捕获的异常的类型,以及接受异常对象的变量。例如catch(invalid_argumentex),要捕获的异常类型为invalid_argument,如果真的捕获到该类异常,那么变量ex就持有这个异常对象,这个对象就是前面用throw语句引发出来的。有一种特殊的catch子句,就是catch(.),该子句能匹配任何类型的异常,包括未命名的异常,不过异常对象或值不能被变量捕获,故此不能提供确切的错误信息。在多个catch子句中,这种catch子句应该排在最后。在执行try子句中的受保护代码时,如果引发一

27、个异常,系统就到catch子句中寻找处理该异常类型的入口。这种寻找过程称为异常类型匹配。按如下步骤进行:由throw语句引发异常事件之后,系统依次检查catch子句以寻找相匹配的处理异常事件入口。如果某个catch子句的异常类型说明与被引发出来的异常事件类型相一致,该异常就被捕获,然后执行该子句的异常处理器代码。如果有多个catch子句的异常类型相匹配,按照前后次序只执行第一个匹配的异常处理代码。因此较具体的派生类异常应该在匹配在前,以提供最具体详细的信息,而较抽象的基类异常应该排在后面。若没有找到任何相匹配的catch子句,该异常就被传递到外层作用域。如果外层作用域是函数,就传递到函数的调用

28、方。一个异常的生命期从创建、初始化之后,被throw引发出来,然后被某个catch子句捕获,其生命期就结束了。一个异常从引发出来到被捕获,可能穿越多层作用域或函数调用。如果到main函数都未被捕获,将导致程序被迫终止。从图15.3中可以看出,try-catch语句的执行结果有两个:正常和异常。表15.1分析了try-catch语句的4种具体情形。表语句执行结果序号结果具体情形1正常完毕受保护代码未引发异常2正常完毕受保护代码引发了异常,但异常被某个catch子句捕获3异常退出受保护代码引发了异常,但未被catch子句捕获4异常退出受保护代码引发了异常,而且被某个catch子句捕获,但在异常处理

29、器中又引发了新的异常,或者用“throw;”语句把刚捕获的异常又重新引发出来分析下面try-catch语句的可能结果:tryresult=quotient(n1,n2);coutThequotientisresult;/.catch(invalid_argumentex)coutinvalid_argument:ex.what();catch(logic_errorex)coutlogic_error:ex.what();catch(exceptionex)coutexception:ex.what();catch(.)coutsomeunexpectedexception;上面try子句中调

30、用了可能引发异常的函数quotient。这个try语句包含了4个catch子句,这4个catch子句的次序是较具体的派生类放在前面,较抽象的基类放在后面。最后一个catch子句可匹配捕获任意类型的异常,但因得不到异常对象,故此不能提供更多信息。在一次执行时,如果引发异常,只能有一个catch子句捕获处理该异常。由于最后一个catch子句能捕获所有类型的异常,而且所有的异常处理器代码中都不会引发异常,因此该try-catch语句的执行结果是表中第1种或者第2种情形。对于try-catch语句的理解和应用,应注意以下几点。try子句中的代码,称为受保护代码,实际上是受到下面若干catch子句的保护

31、,使得try子句代码可以放心去描述正常处理流程,而无需每执行一步都要用if语句来判断是否发生异常情形。并非try子句都可能引发异常,也并非catch子句要捕获try子句所引发的所有异常,当前函数只需捕获自己能处理的异常。多个catch子句之间,不允许基类异常在前、派生类在后,否则将出现语法警告,这使得列在后面的派生类捕获不到异常,而排在前面的基类先捕获到了。try-catch语句仅适合处理异常,并不能将其作为正常流程控制。例.子3例15-1控制流程测试。#includevoidtestExcept(inti)tryif(i=1)throwcatchmewheni=1;/Aif(i=2)TOC

32、o 1-5 h zthrowi;/Bif(i=0)intd=(i+1)/i;/Ccoutdendl;/Dcouti=i;catch(inti)/Ecoutcatchanint:i;catch(char*ex)/Fcoutcatchastring:ex;catch(.)/Gcoutcatchanexceptionunknown;cout的下标越界异常。标准模板库STL提供的向量vectorvT是支持元素随机访问的一种常用容器,它有两种随机访问形式:operator和at(),后者可引发out_of_range异常。#include#includeusingnamespacestd;voidmai

33、n()tryvectorvec(4);/Ainti=0;for(i=0;i4;i+)veci=i+1;for(i=0;i=4;i+)coutveci;coutendl;for(i=0;i=4;i+)coutvec.at(i)coutendl;catch(out_of_rangeex)coutoutofcatch(.)/B/Cnoexception/Dthrowexceptionwheni=4range:ex.what()endl;coutunexpectedn执行程序,输出如下:1234-336860191234outofrange:invalidvectorsubscript上面程序测试两种

34、按下标随机访问元素的成员函数。A行先创建了一个向量,包含4个int元素。B行对这4个元素初始化。C行调用operator来访问元素,输出第1行,当下标越界时,并没有引发任何异常,只是读取的vec4元素的值是随机值。D行调用at(intindex)来访问元素,输出第2行。当下标越界时,引发了out_of_range异常,而不会按非法下标读取值。例15-3除数为0的异常。在整数除法中,如果除数为0就引发底层未命名异常,因此有必要在除法执行之前判断除数是否为0,如果除数为0就引发一个命名的异常来通知调用方。编程如下:#include#includeusingnamespacestd;doublequ

35、otient(intnumrator,intdenominator)throw(invalid_argument)if(denominator=0)throwinvalid_argument(denominatoriszero);/Areturndouble(numrator)/denominator;voidmain()intn1,n2;doubleresult;coutn1n2)tryresult=quotient(n1,n2);coutThequotientisresult;TOC o 1-5 h zcatch(invalid_argumentex)/Bcoutinvalid_argum

36、ent:ex.what();catch(logic_errorex)/Ccoutlogic_error:ex.what();catch(exceptionex)/Dcoutexception:ex.what();catch(.)/Ecoutsomeunexpectedexception;cout和vexception中分别提供了terminate()函数,前者是老版本,作为全局函数,后者是新版本,定义在std命名空间之中。在发生下面情形之一时将自动执行terminate()函数:1、引发异常最终未能捕获。2、析构函数在系统堆栈释放时引发了异常。3、在引发某个异常之后系统堆栈遭破坏。缺省的ter

37、minate函数将调用abort函数,但abort函数不执行清理而简单终止程序,因此常常需要自行定义一个函数,作为terminate函数调用的函数,这要先准备一个无参且无返回的函数f,然后调用set_terminate(f),将函数f作为终止处理器。例15-4terminate函数的例子。#include#includeusingnamespacestd;voidterm_func()/Acoutterm_func()wascalledbyterminate().n;/.cleanuptasksperformedhere/Ifthisfunctiondoesnotexit,abortiscal

38、led.exit(-1);voidmain()inti=10,j=0,result;set_terminate(term_func);/Btryif(j=0)throwDividebyzero!;/Celseresult=i/j;catch(int)coutCaughtanintegerexception.n;cout中的相关定义如下:typedefvoid(*terminate_handler)();/函数指针类型,终止处理器terminate_handlerset_terminate(terminate_handlerph)throw();voidterminate();头一行说明了一种函

39、数指针的类型名,第二行说明了一个函数set_terminate,将一个函数ph说明为新的终止处理器。最后一行是异常处理器函数,缺省将调用abort函数。C行引发的异常类型为constchar*,显然不能被下面的catch子句捕获,该异常将导致程序终止,将执行terminate函数,因为B行设置了新的终止处理函数term_func,那么新的函数得到执行。通常情况下,设置终止函数的目的是释放资源,然后调用exit函数来终止程序。标准C+还支持意外处理器unexpectedhandler,但VC+6版本并不支持。15.扩5展新的异常类型虽然前面图15.2给出了一个异常类型架构,但经常需要扩展自己的异

40、常类型。例如,虽然out_of_range类能用于说明下标越界,但未说明发生异常的下标究竟值是什么。再如,前面例子中除数为0的异常使用了invalid_argument类,而实际上除数为0可能有多种情形,而不一定都作为函数实参,因此有必要自行定义除数为0的异常类。图15.4给出了一组扩展的异常类型。扩展异常类主要是以logic_error和runtime_error为基类来定义派生类。下面构造了一个头文件exceptions.h,包含了一组常用的异常类。#ifndefEXCEPTIONS#defineEXCEPTIONS#include#includeusingnamespacestd;/下标

41、越界,记录下标classIndex_out_of_range:publicout_of_rangeconstintindex;public:Index_out_of_range(intindex1,conststring&what_arg):index(index1),out_of_range(what_arg)intgetIndex()constreturnindex;/除数为0classDivideByZero:publicruntime_errorpublic:DivideByZero(conststring&what_arg):runtime_error(what_arg);/空指针c

42、lassNullPointer:publicruntime_errorpublic:NullPointer(conststring&what_arg):runtime_error(what_arg);/无可用内存classOutOfMemory:publicruntime_errorpublic:OutOfMemory(conststring&what_arg):runtime_error(what_arg);/一般IO异常的基类classIOException:publicruntime_errorpublic:IOException(conststring&what_arg):runtim

43、e_error(what_arg);/打开文件失败,记录文件名/可能是要读的文件不存在,也可能是要写的文件不能创建classOpenFileException:publicIOExceptionpublic:OpenFileException(conststring&filename):IOException(filename);/读取文件到特定位置时转换失败,记录文件出错位置/出错位置可以是文本文件的数据位置,第n项出错/也可以是二进制文件的字节位置,第n个字节出错classReadFileFail:publicIOExceptionconstlongerrPos;public:ReadFi

44、leFail(longpos,conststring&what_arg):errPos(pos),IOException(what_arg)constlonggetErrPos()constreturnerrPos;#endif读者可自行扩展合适的派生类,以适合软件开发的具体需要。下面部分例子要使用这些异常类型。15.异6常类型的应用利用扩展的异常类型,就可对许多已有程序进行改进。例如前面矩阵类模板TMatrixvT中,有一个公有成员函数elemAt如下:templateT&TMatrix:elemAt(intr,intc)/按下标访问元素if(r=row)throwr;/行下标越界,引发in

45、t异常if(c=col)throwc;/列下标越界,引发int异常returndprc;原先是引发int类型异常,这容易与其它异常混淆。现在就可以使用更明确的Index_out_of_range类型的异常。上面2条throw语句就可以分别改为:throwIndex_out_of_range(r,rowindexinelemAt(int,int);throwIndex_out_of_range(c,colindexinelemAt(int,int);TMatrix模板中另一个公有成员函数operator()(intr,intc)调用了elemAt函数,所以operator()(intr,intc

46、)函数也会引发下标越界异常。这些函数都没有显式说明异常规范:throw(Index_out_of_range)这是由于VC+6没有对异常规范进行语法检查(Java语言要求明确的异常规范,否则语法编译出错)。不过规范的设计应该显式说明每个函数的异常规范,以提示调用方可能引发哪些异常,避免遗忘捕获处理。例15-5设计一个函数从一个文本文件中读取多个浮点数,放入一个向量vector中,显示各元素,给出元素的个数,并按升序排序。要读取的文本文件包含任意多的浮点数,用分隔符分开,例如:11.27.83.4这个例子将演示多种异常类型的引发和处理。编程如下:#include#include#include#

47、includeexceptions.husingnamespacestd;voidgetVectorFromFile(char*filename,vector&vfs)throw(NullPointer,OpenFileException,ReadFileFail)if(filename=NULL)throwNullPointer(filenameisnull);:ifstreamifs(filename,ios:in|ios:nocreate);if(!ifs)stringmsg=openfile:;msg+=filename;msg+=failforread;throwOpenFileEx

48、ception(msg);floatf;while(!ifs.eof()ifsf;if(ifs.fail()stringmsg=readfilefail:;msg+=filename;throwReadFileFail(vfs.size(),msg);vfs.push_back(f);ifs.close();return;voidmain()trycharfilename200;coutfilename;vectorvf;getVectorFromFile(filename,vf);cout元素个数:vf.size():endl;for(inti=0;ivf.size();i+)coutvf.

49、at(i);coutendl;sort(vf.begin(),vf.end();coutaftersortedn;for(i=0;ivf.size();i+)coutvf.at(i);coutendl;catch(NullPointerex)coutex.what()endl;catch(OpenFileExceptionex)coutex.what()endl;catch(ReadFileFailex)coutex.what()atex.getErrPos()itemn;catch(out_of_rangeex)coutindexoutex.what()endl;catch(exceptio

50、nex)coutex.what()endl;catch(.)coutexceptionunknown引用作为结果。该函数可能引发3种命名异常,用异常规范throw说明,以提示调用方。该函数直接引发3种类型的异常:1、检查形参指针是否为空,可能引发NullPointer异常;2、打开文件,可能引发OpenFileException异常,保存了出错的文件名;3、读浮点数,可能引发ReadFileFail异常,保存了文件读错的位置。在函数getVectorFromFile执行过程中只要引发任何一种异常,就不能得到结果。主函数中使用try-catch语句来完成计算并捕获处理各种异常。先输入一个文件名,

51、并说明一个vectorvfloat变量,再调用函数getVectorFromFile来得到结果,下面就是显示、排序、再显示。其中在调用at(i)函数时可能引发out_of_range异常。执行程序,在第1行输入一个文件名array.txt,输出如下:inputfilenametoread:array.txt元素个数:12:aftersorted读者可自行改变输入或改变文本文件,引入各种错误,看程序运行是否能正确判断各种异常。例如:用NULL值来调用函数,看是否导致NullPointerException。输入错误的文件名是否会导致OpenFileException。把某个浮点数的字符该为字符,

52、看是否导致ReadFileFail。try子句中的代码描述了正常执行的逻辑,而各种异常的捕获处理都用catch子句描述。这样就能将正常流程与异常处理分割开,不仅提高了程序的可读性和可维护性,而且增强了应对多种错误的能力,提高了编程可靠性。15.函7数设计中的异常处理在函数设计中何时要用到异常?有以下3个原则:1、遇到小概率事件,应考虑使用异常。一种小概率事件往往就是一种异常情形,例如,函数的指针形参在调用时却得到了空指针实参。再例如,要输入一个浮点数,但实际输入错误。小概率事件也意味着在可靠性要求不高的前提下可以推迟处理、甚至忽略。前面很多例子都有这样一个前提,即小概率事件不会发生。反之,如果

53、不是小概率事件,就不适合用异常。例如,读文件到文件尾eof判断,就不适合将读到文件尾作为一种异常来处理,它不是小概率事件,因为每一次读取都应判断是否到达文件尾。2、遇到某种情形,根据当前信息不能确定应该如何处理,应考虑用异常来通知调用方处理。例如,一个函数从文本文件中读浮点数序列,文件名由形参提供,假如按调用方提供的文件名打开文件失败,应如何处理?此时合理的办法就是告诉调用方,这个文件名打开失败了,由调用方来决定是换一个文件名,还是放弃。反之,对于某种情形,如果函数可以处理而且不违背约定,那么这种情形就不适合作为异常。例如对于堆栈stack操作pop,只有先判断堆栈不为空,才能弹出pop元素。

54、堆栈为空这种情形不适合作为异常。3、向调用方报告的某种结果的描述比较复杂,就应考虑使用异常。传统的C语言编程常用不同的int值来表示各种错误。例如一个函数从文本文件中读浮点数序列,可以让该函数返回一个int值,而且约定返回0表示正常,-1表示实参空指针,-2表示打开文件失败,-3表示读取数据失败等。但返回-2时,还应告知打开失败的文件名。当返回-3时,不仅要告知文件名,还应告知导致读取失败的具体位置,即第几个元素读取失败,这样才方便调用方有效解决问题,此时就需要用异常来详细描述。在一个函数设计中,要调用一个可能引发某种异常的函数时,有哪些处理方式?当前函数有下面4种选择:1、捕获该异常并进行处

55、理,使自己的调用方不需要捕获处理该异常。2、捕获该异常,在处理代码中转换为另一种异常,再引发出去,让调用方来捕获处理新的异常。3、捕获该异常,处理(可能是记录异常发生),再将捕获到的异常引发出去,让外层调用方来捕获处理。在处理代码中用不带表达式的throw语句可以转发已捕获到的异常。4、不捕获该异常,让外层调用方来捕获处理。可能是没有try-catch语句,也可能有try-catch但没有catch子句能匹配所发生的异常类型。应采取何种处理方式取决于当前函数所承担的异常处理的责任。第1种方式完全承担了该种异常处理的责任,使外层调用方可以放心调用而无需关心会发生此类异常。第4种方式则完全不承担责

56、任,调用方必须考虑如何处理间接引发的异常。第2种和第3种方式介于两者之间,承担了部分责任,能捕获处理异常,也能引发异常。无论采用哪一种方式,函数的异常规范应告知调用方可能会引发哪些类型的异常。这应该是函数约定的一个重要部分。例15-6异常处理流程的例子#include#includeusingnamespacestd;floatgetValue(inti)/Atryif(i0)throwindexisoutofrange;/Bthrowchar*if(i=0)throw3.14f;/Cthrowfloatif(i=1)throwi;/Dthrowintif(i=2)throw2.718;/Et

57、hrowdoublecoutintryblock,i=iendl;catch(intindex)/Fcatchintcoutcatchintexception:indexendl;catch(floatf)coutcatchfloatexception:fendl;throw;/Gthrowfloatcatch(char*msg)coutcatchchar*exception:msgendl;throwexception(msg);/Hthrowexceptioncoutbelowtry-catch,i=iendl;returni+1;voidmain()for(inti=-1;i=3;i+)

58、tryfloatf=getValue(i);coutf=fendl;catch(floatf)coutmain:catchafloatexception:fendl;catch(doubled)coutmain:catchadoubleexception:dendl;catch(exceptionex)coutmain:catchanexception:ex.what()endl;catch(.)coutcatchanexceptionunknownendl;执行程序,输出如下(行号是为了方便解释而加入的):catchchar*exception:indexisoutofrangemain:c

59、atchanexception:indexisoutofrangecatchfloatexception:3.14main:catchafloatexception:3.14catchintexception:1belowtry-catch,i=1f=2main:catchadoubleexception:2.718intryblock,i=3belowtry-catch,i=3f=4A行定义的函数没有显式给出异常规范。在函数体中的try子句中用throw引发了4类异常,后面的3个catch子句分别捕获了3类异常,但异常处理器代码中又引发了异常。这些异常的引发、捕获、再引发的关系如下:i=-1

60、;引发char*异常,被捕获,再引发exception异常出来。i=0;引发float异常,被捕获,再用throw;转发float异常出来。i=1;引发int异常,被捕获,没有再引发其它异常出来。i=2;引发double异常,没有被捕获。这样可以知道getValue函数的异常规范为throw(exception,float,double)。在main函数中用i=-1到3来调用该函数,并用try-catch语句来捕获所有这些异常。输出情况如下:i=-1,输出前2行。i=0,输出第3、4行。i=1,输出第5、6、7行。i=2,输出第8行。i=3,无异常,输出第9、10、11行。能否在捕获异常之后再

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论