C++编程思想17.pdf
![资源得分’ title=](/images/score_1.gif)
![资源得分’ title=](/images/score_1.gif)
![资源得分’ title=](/images/score_1.gif)
![资源得分’ title=](/images/score_1.gif)
![资源得分’ title=](/images/score_05.gif)
《C++编程思想17.pdf》由会员分享,可在线阅读,更多相关《C++编程思想17.pdf(23页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、下载下载第1 7章异 常 处 理错误修复技术的改进是提高代码健壮性的最有效方法之一。但是,大多数程序设计人员在实际设计中往往忽略出错处理,似乎是在没有错误的状态下编程。毫无疑问,出错处理的繁琐及错误检查引起的代码膨胀是导致上述问题的主要原因。例如,虽然printf()函数可返回打印参数的个数,但是实际程序设计中没有人检查该值。出错处理引起的代码膨胀将不可避免地增加程序阅读的困难,这对于程序设计人员来说是十分令人烦恼的。C语言中实现出错处理的方法是将用户函数与出错处理程序紧密地结合起来,但是这将造成出错处理使用的不方便和难以接受。异常处理是C+语言的一个主要特征,它提出了出错处理更加完美的方法。
2、1)出错处理程序的编写不再繁琐,也不须将出错处理程序与“通常”代码紧密结合。在错误有可能出现处写一些代码,并在后面的单独节中加入出错处理程序。如果程序中多次调用一个函数,在程序中加入一个函数出错处理程序即可。2)错误发生是不会被忽略的。如果被调用函数需发送一条出错信息给调用函数,它可向调用函数发送一描述出错信息的对象。如果调用函数没有捕捉和处理该错误信号,在后续时刻该调用函数将继续发送描述该出错信息的对象,直到该出错信息被捕捉和处理。在这一章中我们将讨论C语言的出错处理方法,讨论为何该方法在 C语言中不是很理想的,并且无法在C+中使用;然后学习t r y,t h r o w和c a t c h
3、的用法,它们在C+中支持异常处理。17.1 C语言的出错处理本书在第8章以前使用C标准库的assert()宏作为出错处理的方法。第 8章以后assert()被按照原先的设计目的使用:在开发过程中,使用它们,完成后用#define NDEBUG使之失效,以便推出产品。为了在运行时检查错误,assert()被allege()函数和第8章中引入的宏所取代。通常我们会说:“对于出错处理我们必须面对复杂的代码,但是在这个例子中我们不必由此感到烦恼”。allege()函数对一些小型程序很方便,对于复杂的大型程序,所编写的出错处理程序也将更加复杂。在通过检查条件我们能确切地知道做什么的情况下,出错处理就变得
4、十分明确和容易了,因为我们通过上下文得到了所有必要的信息。当然,我们只是在这一点上处理错误。这些都是十分普通的错误,不是这一章的主题。若错误问题发生时在一定的上下文环境中得不到足够的信息,则需要从更大的上下文环境中提取出错处理信息,下面给出了C语言处理这类情况的三种典型方法。1)出错信息可通过函数的返回值获得。如果函数返回值不能用,则可设置一全局错误判断标志(标准C语言中errno()和perror()函数支持这一方法)。正如前文提到的,由于对每个函数调用都进行错误检查,这十分繁琐并增加了程序的混乱度。程序设计者可能简单地忽略这些出错信息,因为乏味而迷乱的错误检查必须随着每个函数调用而出现。另
5、外,来自偶然出现异常的函数的返回值可能并不反映什么问题。2)可使用C标准库中一般不太熟悉的信号处理系统,利用 s i g n a l()函数(判断事件发生的类型)和r a i s e()函数(产生事件)。由于信号产生库的使用者必须理解和安装合适的信号处理系统,所以应紧密结合各信号产生库,但对于大型项目,不同库之间的信号可能会产生冲突。3)使用C标准库中非局部的跳转函数:setjmp()和 longjmp()。setjmp()函数可在程序中存储一典型的正常状态,如果进入错误状态,longjmp()可恢复setjmp()函数的设定状态,并且状态被恢复时的存储地点与错误的发生地点紧密联系。考虑C+语
6、言的出错处理方案时会存在另一个关键性问题:由于 C语言的信号处理技术和s e t j m p/l o n g j m p函数不能调用析构函数,所以对象不能被正确地清除。由于对象不能被清除,它将被保留下来并且将不能再次被存取,所以存在这种问题时实际上是不可能有效正确地从异常情况中恢复出来。下面的例子将演示s e t j m p/l o n g j m p的这一特点:s e t j m p()是一个特别的函数,因为如果我们直接调用它,它就把当前进程状态的所有相关信息存放在 j m p _ b u f中,并返回零。这样,它的行为象通常的函数。然而,如果使用同一个j m p _ b u f调用l o
7、n g j m p(),这就象再次从s e t j m p()返回,即正确地弹出s e t j m p()的后端。这时,返回值对于l o n g j m p()是第二个参数,所以能发现实际上从l o n g j m p()中返回了。可以想象,有多个第17章 异 常 处 理361下载不同的j m p _ b u f,可以弹出程序的多个不同位置的信息。局部 g o t o(用标号)和这个非局部跳转的不同在于我们能通过s e t j m p/l o n g j m p跳转到任何地方(一些限制不在这里讨论)。在C+中的问题是,longjmp()不适用于对象,特别是,当它跳出范围时它不调用析构函数1。析
8、构函数调用是必须的,所以这种方法在C+中不可行。17.2 抛出异常如果程序发生异常情况,而在当前的上下文环境中获取不到异常处理的足够信息,我们可以创建一包含出错信息的对象并将该对象抛出当前上下文环境,将错误信息发送到更大的上下文环境中。这称为异常抛出。如:throw myerror(something bad happened);m y e r r o r是一个普通类,它以字符变量作为其参数。当进行异常抛出时我们可使用任意类型变量作为其参数(包括内部类型变量),但更为常用的办法是创建一个新类用于异常抛出。关键字t h r o w的引入引起了一系列重要的相关事件发生。首先是 t h r o w调
9、用构造函数创建一个原执行程序中并不存在的对象。其次,实际上这个对象正是 t h r o w函数的返回值,即使这个对象的类型不是函数设计的正常返回类型。对于交替返回机制,如果类推太多有可能会陷入困境,但仍可看作是异常处理的一种简单方法,可通过抛出一个异常来退出普通作用域并返回一个值。因为异常抛出同常规函数调用的返回地点完全不同,所以返回值同普通函数调用具有很小的相似性(异常处理器地点与异常抛出地点可能相差很远)。另外,只有在异常时刻成功创建的对象才被清除掉。(常规函数调用则不同,它使作用域内的所有对象均被清除。)当然,异常情况产生的对象本身在适当的地点也被清除。另外,我们可根据要求抛出许多不同类
10、型的对象。一般情况下,对于每种不同的错误可设定抛出不同类型的对象。采用这样的方法是为了存储对象中的信息和对象的类型,所以别人可以在更大的上下文环境中考虑如何处理我们的异常。17.3 异常捕获如果一个函数抛出一个异常,它必须假定该异常能被捕获和处理。正如前文所提到的,允许对一个问题集中在一处解决,然后处理在别处的差错,这也正是 C+语言异常处理的一个优点。17.3.1 try块如果在函数内抛出一个异常(或在函数调用时抛出一个异常),将在异常抛出时退出函数。如果不想在异常抛出时退出函数,可在函数内创建一个特殊块用于解决实际程序中的问题(和潜在产生的差错)。由于可通过它测试各种函数的调用,所以被称为
11、测试块。测试块为普通作用域,由关键字t r y引导:try /code that may generate exceptions362C+编程思想下载1 当我们运行这个例子时会惊奇地发现一些C+编译器调用longjmp()函数清除堆栈中的对象。这是兼容性差的问题。如果没有使用异常处理而是通过差错检查来探测错误,即使多次调用同一个函数,也不得不围绕每个调用函数重复进行设置和代码检测。而使用异常处理时不需做差错检查,可将所有的工作放入测试块中。这意味着程序不会由于差错检查的引入而变得混乱,从而使得程序更加容易编写,其可读性也大为改善。17.3.2 异常处理器异常抛出信号发出后,一旦被异常器处理接收
12、到就被销毁。异常处理器应具备接受任何一种类型的异常的能力。异常处理器紧随t r y块之后,处理的方法由关键字c a t c h引导。每一个c a t c h语句(在异常处理器中)就相当于一个以特殊类型作为单一参数的小型函数。异常处理器中标识符(i d 1、id2 等)就如同函数中的一个参数。如果异常抛出给出的异常类型足以判断如何进行异常处理,则异常处理器中的标识符可省略。异常处理部分必须直接放在测试块之后。如果一个异常信号被抛出,异常处理器中第一个参数与异常抛出对象相匹配的函数将捕获该异常信号,然后进入相应的 c a t c h语句,执行异常处理程序。c a t c h语句与s w i t c
13、 h语句不同,它不需要在每个c a s e语句后加入b r e a k用以中断后面程序的执行。注意,在测试块中不同的函数的调用可能会产生相同的异常情况,但是,这时只需要一个异常处理器。终止与恢复在异常处理原理中含有两个基本模式:终止与恢复。假设差错是致命性的,当异常发生后将无法返回原程序的正常运行部分,这时必须调用终止模式(C+支持)结束异常状态。无论程序的哪个部分只要发生异常抛出,就表明程序运行进入了无法挽救的困境,应结束运行的非正常状态,而不应返回异常抛出之处。另一个为恢复部分。恢复意味着希望异常处理器能够修改状态,然后再次对错误函数进行检测,使之在第二次调用时能够成功运行。如果要求程序具
14、有恢复功能,就希望程序在异常处理后仍能继续正常执行程序,这样,异常处理就更象一个函数调用C+程序中在需要进行恢复的地方如何设置状态(换言之就是使用函数调用,而非异常抛出来解决问题)。另外也可将测试块放入w h i l e循环中,以便始终装入测试块直到恢复成功得到满意的结果。过去,程序员们使用的支持恢复性异常处理的操作系统最终被终止性模式所取代,它取消了恢复性模式。所以虽然恢复性模式初听起来是十分吸引人的,但在实际运用中却并非十分有效。其中一个原因可能是异常发生与异常处理相距较远的缘故。要终止相距较远的异常处理器,但是由于异常可能由很多地点产生,所以对于一个大型系统,从异常处跳转到异常处理器再跳
15、转返回,这在概念上是十分困难的。第17章 异 常 处 理363下载17.3.3 异常规格说明可以不向函数使用者给出所有可能抛出的异常,但是这一般被认为是非常不友好的,因为这意味着他无法知道该如何编写程序来捕获所有潜在的异常情况。当然,如果他有源程序,他可寻找异常抛出的说明,但是库通常不以源代码方式提供。C+语言提供了异常规格说明语法,我们以可利用它清晰地告诉使用者函数抛出的异常的类型,这样使用者就可方便地进行异常处理。这就是异常规格说明,它存在于函数说明中,位于参数列表之后。异常规格说明再次使用了关键字t h r o w,函数的所有潜在异常类型均随着关键字 t h r o w而插入函数说明中。
16、所以函数说明可以带有异常说明如下:void f()throw(toobig,toosmall,divzero);而传统函数声明:void f();意味着函数可能抛出任何一种异常。如果是:void f()throw();这意味着函数不会有异常抛出。为了得到好的程序方案和文件,为了方便函数调用者,每当写一个有异常抛出的函数时都应当加入异常规格说明。1.unexpected()如果函数实际抛出的异常类型与我们的异常规格说明不一致,将会产生什么样的结果呢?这时会调用特殊函数unexpected()。2.set_unexpected()unexpected()是使用指向函数的指针而实现的,所以我们可通过
17、改变指针的指向地址来改变相对应的运算。这些可通过类似于 set_new_handler()的函数set_unexpected()来实现,set_unexpected()函数可获取不带输入和输出参数的函数地址和v o i d返回值。它还返回u n e x p e c t e d指针的先前值,这样我们可存储unexpected()函数的原先指针值,并在后面恢复它。为了使用set_unexpected()函数,我们必须包含头文件E X C E P T.H。下面给出一实例展示本章所讨论的各个特点的简单使用:364C+编程思想下载作为异常抛出类,up 和f i t分别被创建。通常异常类均是小型的,但有时
18、它们包含许多额外信息,这样异常处理器可通过查询它们来获得辅助信息。f()函数在它的异常规格说明中声明函数的异常抛出只能是类 up 和 f i t,并且函数体的定义同函数的异常规格说明是一致的。函数 g()(v e r s i o n 1)被函数f()调用,但并不抛出异常,因此这也是可行的。当函数g()(v e r s i o n 1)被修改以后得g()(v e r s i o n 2),g()(v e r s i o n 2)仍是f()的调用函数,但其具有异常抛出功能。函数g()修改以后f()函数具有了新的异常抛出,但最初创建的f()函数对于这些却未加声明,这样就违反了异常规格说明。my_un
19、expected()函数可以没有输入或输出参数,它是按照定制的unexpected()函数的正确格式编写的。它仅仅打出一条有关异常的信息就退出,所以一旦被调用,我们就可以观察到这条信息。新函数unexpected()不必有返回值(可以按照这种方法编写程序,但这是错误的)。然而它却可抛出另一个异常(也可使它抛出同一个异常),或者调用函数exit()或abort()。如果函数unexpected()抛出一个异常,异常处理器将在异常抛出时开始搜寻 u n e x c e p t e d异常。(这种特点对于u n e x c e p t e d()来说是独特的)虽然new_handler()函数的指针
20、可为空,但unexpected()函数的指针却不能为空。它的缺省值指向terminate()(后面将会介绍)函数,但是,只要我们使用异常抛出和异常规格说明,我们就应该编写自己的unexpected()函数,用于记录或者再次抛出异常及抛出新的异常或终止程序运行。在主程序中,为了对所有的潜在异常进行检测,测试块被放入 f o r循环中。注意这里提到的第17章 异 常 处 理365下载实现方法很象前文介绍的恢复模式,将测试块放入f o r,while,do 或 if 的循环语句中,并利用每一个异常来试图消除差错问题;然后再一次的调用测试块对潜在异常进行检测。由于程序中f()的函数声明引入了u p和f
21、 i t两类异常,因此只有该两类异常可被抛出。因为f()的函数声以后要抛出的整型,所以修改后的 g()(v e r s i o n 2)会使得函数my_unexpected()被调用。(我们可使用任意的异常类型,包括内部类型。)函数set_unexcepted()被调用后,它的返回值可被忽略,但也可以被保存为函数指针,并在随后用于恢复unexcepted()的原先指针。17.3.4 更好的异常规格说明我们可能觉得在前面介绍的已存在的异常规格说明规则并非十分可靠,并且void f();应该意味着函数没有异常抛出,但按照前面的规则这正好相反,它表示可抛出任意类型的异常。如果程序员要抛出任意类型的异
22、常,我们可能会想他应该说明如下void f()throw(.);/not in C+因为函数声明应当更加清晰,所以这是一个改进。但不幸的是,我不能总是通过查看程序代码来知道函数是否有异常抛出例如,函数的异常抛出发生在存储分配过程中。较为糟糕的是由于调用了在异常处理之前引入的函数而出现非有意的异常抛出。(函数可能与一个新版本的异常抛出相连接)所以采用不明确的描述,如:void f();表示有可能有异常抛出,也可能没有。这种不明确的描述对于避免阻碍程序执行是十分必要的。17.3.5 捕获所有异常前面论述过,如果函数没有异常规格说明,任何类型的异常都有可能被函数抛出。为了解决这个问题,应创建一个能捕
23、获任意类型的异常的处理器。这可以通过将省略号加入参数列表(la C)中来实现这一方案。catch(.)cout an exception was thrown endl;为了避免漏掉异常抛出,可将能捕获任意异常的处理器放在一系列处理器之后。在参数列表中加入省略号可捕获所有的异常,但使用省略号就不可能有参数,也不可能知道所接受到的异常为何种类型。17.3.6 异常的重新抛出有时需要重新抛出刚接收到的异常,尤其是在我们无法得到有关异常的信息而用省略号捕获任意的异常时。这些工作通过加入不带参数的t h r o w就可完成:catch(.)cout an exception was thrown en
24、dl;t h r o w;366C+编程思想下载如果一个c a t c h句子忽略了一个异常,那么这个异常将进入更高层的上下文环境。由于每个异常抛出的对象是被保留的,所以更高层上下文环境的处理器可从抛出来自这个对象的所有信息。17.3.7 未被捕获的异常如果测试块后面的异常处理器没有与某一异常相匹配,这时内层对异常的捕获失败,异常将进入更高层的上下文环境中(高层测试块一般不最先进行异常接收),这个过程一直进行直到在某个层次异常处理器与该异常相匹配,这时这个异常才被认为是被捕获了,进一步的查询也将停止。假如任意层的处理器都没有捕获到这个异常,那么这个异常就是“未捕获的”或“未处理的”。如果已存在
25、的异常在被捕获之前又有一个新的异常产生将造成异常不能被获取,最常见的这种情况的产生原因是异常对象的构造函数自身会导致新的异常。1.terminate()如果异常未能被捕获,特殊函数terminate()将自动被调用。如同函数unexception()终止函数一样,它实际上也是一个指向函数的指针。在 C标准库中它的缺省值为指向函数abort()的指针,abort()函数可以不用调用正常的终止函数而直接从程序中退出(这意味着静态全局函数的析构函数不用被调用)。如果一个异常未被捕获,析构函数不会被调用,则异常将不会被清除。含有未捕获的异常将被认为是程序错误。我们可将程序(如果有必要,包括 main(
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- C+ 编程 思想 17
![提示](https://www.taowenge.com/images/bang_tan.gif)
限制150内