C++程序设计 第10章 异常处理

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

【例10.1】包含栈满或空异常的完整的程序。
10.3
栈展开与异常捕获
函数try块的使用:
把程序的正常处理代码和异常处理代码分离的最清楚 的方法是定义函数try块(Function try Block)。这 种方法是把整个函数包括在try块中。
【例10.1_1】定义函数try块(Function try Block)。
10.3 栈展开与异常捕获
栈展开时资源的释放:
在栈展开期间,在退出的域中有某个局部量是类对象,栈展 开过程将自动调用该对象的析构函数,完成资源的释放。所以 C++异常处理过程本质上反映的是“资源获取是由构造函数实 现,而资源释放是由析构函数完成” 。采用面向对象的程序设 计,取得资源的动作封装在类的构造函数中,释放资源的动作 封装在类的析构函数中,当一个函数带着未处理的异常退出时, 函数中这种类对象被自动销毁,资源(包括动态空间分配的资 源和打开的文件)释放。所以由文件重构对象应该放在构造函 数中,把对象存入文件应该放在析构函数中。 异常处理应该用于面向对象的程序设计。对非面向对象的程 序设计如果函数动态获得过资源,因异常,这些资源的释放 语句可能被忽略,则这些资源将永远不会被自动释放。
第十章 异常处理
10.1 异常的概念
10.2 异常处理的机制
10.5异常和继承
10.3 栈展开与 异常捕获
10.6异常规范(选读)
10.4 异常的重新抛出 和catch_all子句
10.7 C++标准库异常类 层次结构 (选读)
10.1 异常的概念
异常概念的引入:
异常(exception)是程序可能检测到 的,运行时不正常的情况,如存储空间耗尽、数组越 界、被0除等等。可以预见可能发生在什么地方,但是无法确 知怎样发生和何时发生。特别在一个大型的程序(软件)中, 程序各部分是由不同的小组编写的,它们由公共接口连起来, 错误可能就发生在相互的配合上,也可能发生在事先根本想不 到的个别的条件组合上。 C++提供了一些内置的语言特性来产生(raise)或抛出 (throw)异常,用以通知“异常已经发生”,然后由预先安 排的程序段来捕获(catch)异常,并对它进行处理。这种机 制可以在C++程序的两个无关(往往是独立开发)的部分进行 “异常”通信。由程序某一部分引发了另一部分的异常,这一 异常可回到引起异常的部分去处理(逆着程序的函数调用链)。
10.3
栈展开与异常捕获
catch子句的异常声明与函数参数声明类似,可以是按 值传送,也可以是按引用传递。对大型类对象减少不 必要的复制是很有意义的,所以对于类类型的异常, 其异常声明最好也是被声明为引用。如:
catch(pushOnFull<T> & eObj){ cerr<<”栈满”<<eObj.value()<<”未压栈”<<endl; return 1; } 使用引用类型的异常声明,catch子句能够修改异常对象, 但仅仅是异常对象本身,正常程序部分的量并不会被修改。 与一般类对象不同,实际上异常对象处理完后,生命期也 就结束了。只有需要重新抛出异常(在下一节中讨论), 修改操作才有意义。
10.3 栈展开与异常捕获
catch子句异常声明探讨:
异常声明中可以是一个对象声明。以栈为例,当栈满 时,要求在异常对象中保存不能被压入到栈中的值, pushOnFull类可定义如下:
template <typename T>class pushOnFull{ T _value; public: pushOnFull(T i):_value(i){} //或写为pushOnFull(T i){_value=i;} T value(){return _value;} }; 新的私有数据成员_value保存那些不能被压入栈中的值。该 值即调用构造函数时的实参。 对应在throw表达式中,构造抛出对象也要有实参: throw pushOnFull(data); //data即Push(const &data)中的参数data
10.2 异常处理的机制 流程控制规则:
1.如果没有异常发生,继续执行try块中的代码,与try块相 关联 的catch子句被忽略,程序正常执行,main()返回0。 2.当第一个try块在for循环中抛出异常,则该for循环退出, try块也退出,去执行可处理pushOnFull异常的catch子句。 istack.PrintStack()不再执行,被忽略。 3.如果第二个try块调用Pop()抛出异常,则退出for和try 块,去执行可处理popOnEmpty异常的catch子句。 4.当某条语句抛出异常时,跟在该语句后面的语句将被跳过。 程序执行权交给处理异常的catch子句,如果没有catch子句 能够处理异常,则交给C++标准库中定义的函数 terminate()。
10.2 异常处理的机制
异常与异常抛出:
以栈为例,异常类声明如下: template <typename T>class popOnEmpty{...}; //栈空异常 template <typename T>class pushOnFull{...}; //栈满异常 测到栈满或空就抛出一个异常。 template <typename T>void Stack<T>::Push(const T&data){ if(IsFull()) throw pushOnFull<T>(data); //注意加了括号,是构造一个无名对象 elements[++top]=data; } template<typename T>T Stack<T>::Pop(){ if(IsEmpty()) throw popOnEmpty<T>(); return elements[top--]; } 注意pushOnFull是类,C++要求抛出的必须是对象,所以必 须有“()”,即调用构造函数建立一个对象。
一个函数try块把一组catch子句同一个函数体相关联。如果函 数体中的语句抛出一个异常,则考虑跟在函数体后面的处理代 码来处理该异常。函数try块对构造函数尤其有用。
寻找匹配的catch子句有固定 的过程:如果throw表达式位 于try块中,则检查与try块相关 联的catch子句列表,看是否 有一个子句能够处理该异常, 有匹配的,则该异常被处理; 找不到匹配的catch子句,则 在主调函数中继续查找。如果 一个函数调用在退出时带有一 个被抛出的异常未能处理,而 且这个调用位于一个try块中, 则检查与该try块相关联的 catch子句列表,看是否有一 个子句匹配,有,则处理该异 常;没有,则查找过程在该函 数的主调函数中继续进行。即 这个查找过程逆着嵌套的函数 调用链向上继续,直到找到处 理该异常的catch子句。只要 遇到第一个匹配的catch子句, 就会进入该catch子句,进行 处理,查找过程结束。如最终 未找到,由terminate()处理。
10.3 栈展开与异常捕获
在catch子句中,要取得_value,须调用pushOnFull 中的成员函数value(): catch(pushOnFull<T> eObj){ cerr<<”栈满”<<eObj.value()<<”未压入栈”<<endl; return 1;} 在catch子句的异常声明中声明了对象eObj,用它来调用 pushOnFull类的对象成员函数value()。异常对象是在抛出 点被创建,与catch子句是否显式要求创建一个异常对象无关, 该对象总是存在,在catch子句中只是为了调用异常处理对象 的成员函数才声明为对象,不用类。 *catch子句异常声明中采用对象只是一种形式。甚至 异常并非一个类对象时,也可以用同样的格式,比如 异常为一枚举量,这时就等效于按值传递,而不是为 了调用类对象的公有成员。
10.3 栈展开与异常捕获
catch子句说明:
当try块中的语句抛出异常时,系统通过查看跟在 其后的catch子句列表,来查找可处理该异常的 catch子句。 catch子句由三部分组成:关键字catch、圆括号中的异 常声明以及复合语句中的一组语句。 •catch子句不是函数,所以圆括号中不是形参,而是一个 异常类型声明,可以是类型也可以是对象。 •catch子句的使用:它只有一个子句,没有定义和调用之 分。使用时由系统按规则自动在catch子句列表中匹配。 catch子句可以包含返回语句(return),也可不包含 返回语句。包含返回语句,则整个程序结束。而不包含返 回语句,则执行catch列表之后的下一条语句。
第十章 异常处理
大型和十分复杂的程序往往会产生一些很难查找的甚至是 无法避免的运行时错误。当发生运行时错误时,不能简单地结 束程序运行,而是退回到任务的起点,指出错误,并由用户决 定下一步工作。面向对象的异常处理(exception handling) 机制是C++语言用以解决这个问题的有力工具。 函数执行时,放在try(测试)程序块中的任何类型的数 据对象发生异常,都可被throw表达式抛出,随即逆调用链退 回,直到被catch子句捕获,并在此执行异常处理,报告出现 的异常等情况。从抛出到捕获,应将各嵌套调用函数残存在 栈中的自动对象、自动变量和现场保护内容等进行清除。如 果已退到入口函数还未捕获则由terminate()函数来终结入口 函数。
10.2 异常处理的机制
try块与ca源自文库ch子句的关系实例:
int main(){ int a[9]={1,2,3,4,5,6,7,8,9},b[9]={0},i; stack<int>istack(8); try{ for(i=0;i<9;i++) istack.Push(a[i]); istack.PrintStack(); } catch(pushOnFull<int>){cerr<<”栈满”<<endl;} try{ for(i=0;i<9;i++){b[i]=istack.Pop();} } catch(popOnEmpty<int>){cerr<<”栈空”<<endl;} for(i=0;i<9;i++) cout<<b[i]<<’\t’; cout<<endl; return 0; }
10.2 异常处理的机制
说明:
这里有两个try块,分别对应压栈与出栈;也有两 个catch子句(catch clause),分别处理压栈 时的栈满和出栈时的栈空。 由catch字句捕获并处理异常是第二步。注意与catch语句 分别匹配的是在压栈和出栈成员函数模板中的throw语句, 一个抛出pushOnFull类的无名对象,另一个抛出 popOnEmpty类的无名对象。 在编制程序时有一条惯例:把正常执行的程序与异常处理两 部分分隔开来,这样使代码更易于跟随和维护。在上例中, 我们可以把两个try块合成一个,而把两个catch子句都放在 函数最后。
10.3 栈展开与异常捕获
栈展开:
因发生异常而逐步退出复合语句和函数定义的过程,被称 为栈展开(stack unwinding)。这是异常处理的核心技术。 在栈异常处理的例子中,对popOnEmpty,首先应在 istack的成员函数Pop()中找,因为Pop()中没有try块,不存 在catch子句,所以Pop()带着一个异常退出。下一步是检查 调用Pop()的函数,这里是main(),在main()中对Pop()的调 用位于一个try块中,则可用与该try块关联的catch子句列表 中的某一个来处理,找到第一个popOnEmpty类型异常声明 的catch子句,并进入该子句进行异常处理。 异常对程序的影响通常不仅是在发生异常的那个局部范围中, 而且可能逆调用链而上,甚至整个任务。因此,异常处理应 该在其对程序影响的终结处进行,甚至是在调用该任务的菜 单处进行。
10.2 异常处理的机制
异常处理机制:
throw表达式抛出异常为异常处理的第一步。在堆栈的压 栈和出栈操作中发生错误而抛出的异常,理所当然地应由 调用堆栈的程序来处理。异常并非总是类对象,throw表 达式也可以抛出任何类型的对象,如枚举、整数等等。但 最常用的是类对象。 在C++中异常抛出与异常处理之间有一整套程序设计的机制。 首先采用关键字try,构成一个try块(try block),它包含 了抛出异常的语句。当然也可以是包含了这样的调用语句, 该语句所调用的函数中有能够抛出异常的语句。
相关文档
最新文档