第9章 异常处理_第1页
第9章 异常处理_第2页
第9章 异常处理_第3页
第9章 异常处理_第4页
第9章 异常处理_第5页
已阅读5页,还剩28页未读 继续免费阅读

下载本文档

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

文档简介

1、第第9 9章章 异常处理异常处理 程序在运行过程中,由于用户输入错误、越界访问和系统环境资源不足等原因,会导致程序运行不正常或崩溃。程序在设计时必须考虑软件的容错能力,即应对运行时可能出现错误的位置和错误处理方法。在大型应用软件中,相当一部分代码是用于处理程序异常状况的,异常处理是程序的重要组成部分。C+语言的异常处理机制能有效地进行异常检测、异常抛出、异常捕获和异常处理,成为提高程序稳键性的重要手段之一。 本章主要学习异常的抛出和捕获方法、堆栈展开、重新抛出异常、异常与继承以及标准库中的异常类等知识。 9.1 9.1 异常概述异常概述9.2 9.2 异常处理机制异常处理机制9.3 9.3 构

2、造函数、析构函数和异常构造函数、析构函数和异常9.4 9.4 标准库的异常类层次结构标准库的异常类层次结构9.5 9.5 案例实训案例实训第第9 9章章 异常处理异常处理9.1 9.1 异常概述异常概述 程序在设计和运行过程中均可能出现各式各样的错误,依据错误产生的原因,主要分为3类:语法错误、逻辑错误和运行错误。l 语法错误是程序在编译、连接时,编译器报告的错误。此类错误产生的原因主要是程序结构不合规则、变量没有定义、拼写错误或缺少相关文件等。编译器基本上能正确指出这类错误的位置,修改也比较简单。l 逻辑错误是程序能正常编译、连接并运行,但结果错误或偶尔报错。此类错误是由算法设计有误或考虑问

3、题不周全等因素引起,通过调试或测试,通常能查找出错误的原因。l 运行错误是程序在执行过程中错误的输入或运行环境没有满足等因素,导致程序非正常终止。运行错误虽然是由于软件在使用过程中用户使用不当或环境资源不足等外在因素引起的,但通常可以事先预料。 异常处理(Exception Handling)就是在运行时刻对运行错误进行检测、捕获和提示等过程。传统的C语言处理运行时错误的方法是用if-else语句检测处理可能发生的异常,其特征是测试程序是否被正确地执行。如果不是,则执行错误处理代码,否则继续运行。虽然这种方式的异常处理也能满足设计要求,但是程序的正常处理流程和错误处理逻辑混合在一起,正常的程序

4、流程被“淹没”在异常判断与处理之中,增加了阅读、修改和维护程序的难度,在多人合作开发的大型软件中该问题更加突出。9.1 9.1 异常概述异常概述9.2 9.2 异常处理机制异常处理机制 异常处理作为C+语言的一部分,引入了关键字try(检测异常)、throw(抛出异常)和catch(捕获异常)。在函数中检测到某种错误发生后,函数自己并不处理异常,而是由throw语句引发并抛出异常,仅告知调用函数发生了什么异常。在调用函数中用try-catch语句检测异常并处理。如果抛出的异常没有被调用函数捕获处理,异常就被传递到更高一层的函数调用,最后到达主函数。9.2 9.2 异常处理机制异常处理机制9.2

5、.1 异常的抛出 抛出异常的语法格式为: throw 表达式 如果在某段程序中检测到可能发生的异常,则用throw语句抛出表达式的值作为发生的异常,异常的数据类型是异常捕获的依据。若函数执行了throw语句,则其后的语句将不再执行,程序流程将返回到调用函数,其功能与return语句相似。 抛出的异常在调用函数中捕获并处理,调用函数中需要用try-catch结构语句来捕获异常并处理。【例9-1】零为除数的异常处理。9.2 9.2 异常处理机制异常处理机制程序说明: (1)从运行结果可知,程序运行到divide(34, 0)时,函数抛出了异常,并被第2个catch块捕获,输出错误提示信息。try块

6、中的后面两个语句均没有被执行,程序正常结束。try块后面的catch块有两个。由于divide函数抛出的值是int类型,故与第2个catch块相匹配。异常捕获的匹配原则是抛出的表达式的数据类型与catch块声明的类型是否一致。 (2)如果通过注释符消除divide函数中throw语句的作用,则运行程序时将出现如图9-1所示的对话框。如果在主函数中去除try和catch语句,则程序因没有捕获和处理异常语句,在运行时将弹出调试对话框。9.2 9.2 异常处理机制异常处理机制【例9-2】允许错误输入的计算三角形面积程序。程序说明: (1)运行时输入的第一行数据2、6、9为不能构成三角形的错误数据,程

7、序能给出错误提示并继续运行。异常处理机制使得程序具有容错能力。 (2)throw string(“错误:三角形两边之和不能小于第三边。”)语句中的string不能少,否则程序会因异常类型不匹配而终止。9.2 9.2 异常处理机制异常处理机制9.2.2 异常的捕获与处理try-catch语句是专门用于捕获处理异常的语句,其格式如下:try catch( ) catch( ) catch(.) 9.2 9.2 异常处理机制异常处理机制说明:(1)try子句中的程序段称为受保护代码块(又称try代码块),该代码块中包含可能引发异常的代码。异常可能是由try代码块中的代码直接产生,也可能是由于调用其他

8、函数产生的,或者由于代码块中的代码启动的深层嵌套函数调用产生的。try代码块中直接或间接地存在可能抛出异常的throw语句。(2)紧随try子句之后是catch子句,一个try子句可以有多个catch子句。通常每个catch子句仅能捕获一类异常,catch子句的括号中只能有一种异常类型和一个异常变量,用于指明该子句所捕获的异常类型和接受所捕获对象或值。catch(.)是能匹配任何异常类型的catch子句,不过它不能判别所捕获的异常类型和具体的异常变量值,故不能提供准确的错误信息,在多个catch子句中通常它排在最后。9.2 9.2 异常处理机制异常处理机制如果用两个及以上不同的catch子句捕

9、获同一种类型的异常,则会产生编译时错误。(3)catch子句捕获异常后,对应异常处理代码将被执行。通常处理代码所完成的操作有:给出错误提示、资源回收、消除出错影响、重新抛出异常等。(4)try-catch语句仅适合处理异常,并不对程序的正常流程产生作用。此外,catch子句只能捕获由其自身所在异常处理块引发的异常。(5)如果抛出的异常没有找到相匹配的catch子句,则该异常将被传递到外层作用域,即调用该异常处理模块的主调函数。9.2 9.2 异常处理机制异常处理机制【例9-3】求一元二次方程的根,用异常进行容错处理。 9.2 9.2 异常处理机制异常处理机制程序说明:(1)C+语言中,对结构体

10、类型进行了扩充。与类类型相似,既可以定义数据成员,也可以定义成员函数。它们的差别仅仅是结构体默认的访问控制为public,而类为private。例程中root结构体用于记录方程的根,并重载了流输出运算符。(2)while(cin a b c)循环语句中用数据输入语句cin a b c为判别条件。Z是流结束标志,当cin从输入缓冲中检测到Z,则返回0,于是程序结束循环。事实上,如果输入任何一个非数值字母,由于字母不是实型数,cin也返回0,循环同样结束。9.2 9.2 异常处理机制异常处理机制9.2.3 重新抛出异常与堆栈展开 catch子句捕获异常后,有可能不能完全处理该异常,此时catch子

11、句在完成一些自己的处理后,可以将该异常重新抛出(Rethrow),把异常传递给函数调用链中上一级的try-catch代码块进行捕获处理。如果上一级调用函数没有捕获从被调函数传递的异常或者就没有try-catch语句,则该异常被传递到更上一级的catch子句,这种传递终止于主函数。如果主函数也没有处理该异常,则调用在C+标准库中定义的函数terminate()函数终止程序。l 重新抛出异常语句为空throw语句,即throw;语句。l 抛出的异常沿着逆函数调用链向上传递,终止于捕获并处理异常的函数。 9.2 9.2 异常处理机制异常处理机制l 在函数调用与被调用的过程中,程序形成了一个函数调用链

12、。在程序的堆栈区,调用函数的活动记录和自动变量依照函数调用链的顺序压入堆栈。如果被抛出的异常在当前函数中没有捕获处理或重新抛出该异常,则函数调用堆栈便被“展开”,当前函数将终止执行,自动变量被销毁,活动记录被弹出,流程返回到上一级调用函数。从本质上说,堆栈展开(Stack Unwinding )是异常处理的核心技术。l 堆栈在展开期间,函数将结束执行,编译器能保证释放异常发生之前所创建的局部对象。如果局部对象是类类型的,则自动调用该对象的析构函数。l 异常使用不当可能导致动态内存空间“泄漏”。如果函数执行过程中,已用new创建一个对象,但在用delete撤消该对象之前引发了异常,程序控制流程离

13、开了当前函数,指向动态对象的指针随着堆栈展开被清除,而动态内存中的对象却不能自动回收,就会造成内存泄漏。9.2 9.2 异常处理机制异常处理机制下面的程序将导致动态内存空间释放语句没有执行:#include using namespace std;double divide(double *m, double *n) if(*n) = 0)throw n;return *m/(*n);int main() try double *pm = new double(100);double *pn = new double(0);cout *pm / *pn = divide(pm,pn) endl

14、;delete pm;delete pn;catch(double *exp) cout 分母不能为 *exp endl;return 0;9.2 9.2 异常处理机制异常处理机制l 当异常处理模块在接收到一个异常时,可能无法或只能部分处理该异常,此时异常处理代码模块可以抛出该异常。下面的例子演示了异常重新抛出和terminate()终止函数的用法。【例9-4】重新抛出异常。9.2 9.2 异常处理机制异常处理机制程序说明:(1) 程序中,主函数调用functionA(),functionA()调用了functionB(),functionB()又调用functionC()。函数functio

15、nC()产生的异常被重新抛出,该异常沿逆函数调用链一直传递到主函数。 函数functionB()没有捕获函数functionC重新抛出的异常,异常直接传递到函数functionA。函数functionA的处理代码仅输出了字符串,又重新抛出异常。在主函数中输出传递的异常内容后,也同样抛出该异常。(2) 主函数调用terminate函数处理异常,该函数默认是调用系统的abort()函数。如图9-2所示为terminate函数调用abort()函数出现的错误提示对话框。9.2 9.2 异常处理机制异常处理机制l 去除程序中的注释符,即让函数ending()和set_terminate()生效,再执行

16、程序,运行结果在最后一行显示“程序异常结束!”信息,不再弹出如图9-2所示的对话框。(3)函数functionA()中的cout“函数A调用了函数B!”,使用户可以像使用普通指针一样使用auto_ptr对象。l auto_ptr对象被称为智能指针,它解决了异常可能导致内存泄漏的问题。但是,它并不是完美无缺的,一般不能用它指向数组,更不要将auto_ptr对象作为STL容器的元素。在新的C+标准中,shared_ptr将作为更好的方案,用于替代auto_ptr。l 程序运行时,系统分配给程序的动态内存空间是有限的。当new运算符执行失败时,C+标准规定将抛出一个bad_alloc异常。Visua

17、l C+ 2010支持该标准,但早期的Visual C+ 6.0在new失败时只是返回0。9.3 9.3 构造函数、析构函数和异常构造函数、析构函数和异常【例9-5】在构造函数中引发异常和auto_ptr应用。9.3 9.3 构造函数、析构函数和异常构造函数、析构函数和异常程序说明: (1)程序中定义了一个用于描述动物信息的类AnimalInfo,分别用于存储动物图片、叫声和视频的3个类(Picture、Sound、Video)均使用了动态存储空间,并且为能引发异常申请了较大的内存空间。 所有的类均遵循:在构造函数中,用new运算符申请分配内存空间。在析构函数中,用delete运算符释放内存空

18、间。从程序运行结果可知,程序在创建“狮子”对象时,执行到video = new Video;语句抛出了bad_alloc异常。此时狮子对象Picture和Sound子对象已被创建,但它们的析构函数因异常的抛出而没有执行,引发内存泄漏。 “大象”对象因已完全构造,其析构函数正常运行,不受异常影响,故不存在内存泄漏。9.3 9.3 构造函数、析构函数和异常构造函数、析构函数和异常(2)2)运行结果的最后一行内容运行结果的最后一行内容bad allocationbad allocation为为bad_allocbad_alloc异常返回的信息。异常返回的信息。(3)(3)程序中给出了程序中给出了An

19、imalInfoAnimalInfo类的另一种实现。去除注释类的另一种实现。去除注释/ /* *和和* */ /,为前面的,为前面的AnimalInfoAnimalInfo类实现加上注释。运行程序,得到下面的结果:类实现加上注释。运行程序,得到下面的结果:call Picture Constructor.call Sound Constructor.call Video Constructor.大象对象调用AnimalInfo构造函数。call Picture Constructor.call Sound Constructor.call Sound Destructor.call Pictu

20、re Destructor.大象对象调用AnimalInfo析构函数。call Video Destructor.call Sound Destructor.call Picture Destructor.bad allocation9.3 9.3 构造函数、析构函数和异常构造函数、析构函数和异常l 从该结果可见,程序也是在为“狮子”对象分配Video空间时发生bad_alloc异常,但没有产生内存泄漏,因为Sound和Picture类的析构函数均被执行,释放了已分配的内存。l 析构函数的作用是释放对象构造时所占用的资源,那么析构函数中抛出异常将会发生什么呢?当程序在为某个异常进行堆栈展开时,

21、析构函数如果再抛出异常,将会导致调用标准库中的terminate函数,通常再引发调用abort函数,使程序非正常终止。9.4 9.4 标准库的异常类层次结构标准库的异常类层次结构 C+的异常类型既可以是int、double等基本数据类型,也可以是结构体、类等用户自定义的构造数据类型。程序中如果用基本数据类型表示异常,存在异常含义难以区分的问题。例如,程序中不能多处抛出含有不同语义的int类型异常,否则将难以区分这些异常的含义。 良好的编程规范是利用类的继承性构建一个异常类型的架构,对错误进行归类和描述。在C+的标准库中定义了一个异常类层次,其结构如图9-3所示。9.4 9.4 标准库的异常类层

22、次结构标准库的异常类层次结构9.4 9.4 标准库的异常类层次结构标准库的异常类层次结构 C+标准库的异常类分别定义在4个头文件中:头文件中定义了异常类exception;头文件中定义了runtime_error和logic_error异常类及其子类;头文件中定义了bad-alloc异常类;头文件中定义了bad_cast异常类。 exception异常类包含了虚函数what(),在派生类中可以对其重新定义,生成相应的错误消息。基类exception只通知异常的产生,不提供更多的信息。 runtime_error异常类用于描述在运行时才能检测到的错误,它派生了以下3个子类:l range_err

23、or:该异常类用于描述结果超出了有意义的值域范围。l overflow_error:该异常类用于表示计算上溢。l underflow_error:该异常类用于描述计算下溢。9.4 9.4 标准库的异常类层次结构标准库的异常类层次结构l logic_error异常类表示逻辑错误,用来描述在程序运行前检测到的错误。logic_error的派生类domain_error异常类表示参数的结果值不存在;invalid_argument异常类表示不合适的参数;length_error异常类用于描述试图生成一个超出该类型最大长度的对象;out_of_range异常类表示使用了一个超出有效范围的值。l bad

24、_alloc异常类用于描述因无法分配内存而由new抛出的异常。l bad_cast异常类是在dynamic_cast失败时抛出该异常类对象,dynamic_cast运算符的作用是进行类型转换,dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。l 标准异常类可以直接应用于程序中,也可以在已定义的异常类之上派生出自定义的异常类型。l 顺序表是一种重要的数据结构,它使用一块连续的内存空间保存数据元素,并用元素所存储的物理位置来表示元素之间的先后关系。顺序表在创建和使用时可能因自由存储空间不足出现异常,可能由于访问、插入或删除操作所指定的位置错误出现访问错误,也可能因存储空间已满,不能再插入新元素,需要引发异常。【例9-6】标准异常类应用。设计具有异常处理功能的顺序表类。9.4 9.4 标准库的异常类层次结构标准库的异常类层次结构程序说明:(1) 在SeqList类的I

温馨提示

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

评论

0/150

提交评论