• 1. 第7章 异常处理写函数库的程序员可以检测到库函数运行时的错误(如数组访问越界),但通常却不知道应该如何处理这些错误 异常处理的基本想法是,让一个函数在发现了自己无法处理的错误时抛出一个异常,希望它的(直接或间接)调用者能够处理这个问题。
  • 2. 异常处理传统错误处理方法 异常处理机制 抛出异常 捕获异常 处理异常 异常规格说明
  • 3. 程序逻辑经常对决定程序下一步怎样执行的条件进行测试 执行一个任务 如果这个任务没有正确执行 则执行错误处理 执行下一个任务 如果该任务没有正确执行 则执行错误处理 …
  • 4. 传统错误处理方法可以处理的错误在发生错误的地方就地处理 在检查到一个在局部无法处理的问题时,一个函数可以: 终止程序 abort() /exit() 返回一个表示“错误”的值。 int 返回一个合法值,让程序处于某种非法的状态。 errno 调用一个预先准备好在出现“错误”的情况下用的函数。
  • 5. 处理错误的传统方法处理错误的传统方法:错误处理代码是在整个系统代码中分布的。代码中可能出错的地方都要当场进行错误处理。在写程序时,必须知道所有的错误该如何处理 好处: 程序员阅读代码时能够直接看到错误处理情况,确定是否实现了正确的错误检查。 问题: 代码中受到错误处理的“污染”,使应用程序本身的代码更加晦涩难懂,难以看出代码功能是否正确实现。这样就使代码的理解和维护更加困难。
  • 6. 面向对象中的异常处理面向对象中,程序员经常做的是一些工具(类的设计与实现) 这些工具能够检测出错误,但往往不知道该如何处理错误。错误的处理是由工具的使用者决定 需要一种机制能将检测到的错误告诉使用者
  • 7. 例7-1: #include #include #include // ptototype: void exit(int); double sqroot(double number) { if ( number < 0 ) {cout << "Error! negative input number : " << number << '\n'; cout << "Program terminated!" << '\n'; exit( -1 ); } return sqrt( number ); //system subroutine }
  • 8. void main() { cout<<"Sqrt of 1.5129 is "<
  • 9. 例7-2. 依靠异常来处理求负数开方根的例子(当然此例中并不一定需要异常处理): #include #include //#include //不再需要exit( )  double sqroot(double number) { if ( number < 0 ) throw number; return sqrt( number ); //system subroutine }
  • 10. void main() { try { cout<<"Sqrt of 1.5129 is"<
  • 11. 11异常处理的基本思想函数f()捕获并处理异常函数h() 引发异常函数g()……调用者异常传播方向调用关系
  • 12. 异常处理C++的新异常处理特性: 异常处理将检测发现错误的代码与处理错误的代码分开来。程序员的工作也可做相应分工(例如,库函数程序员负责检测异常,而调用库函数的另一程序员则负责捕获与处理异常)。 使程序员可以删除程序执行“主线条”中的错误处理代码,从而提高程序的可读性和可维护性。
  • 13. 异常处理基础简介如果某段程序可能会抛出异常,则必须通知系统启动异常处理机制。即通过try语句块实现。 异常处理代码的一般形式: try{ 可能抛出异常的代码 } catch(类型1 参数1) { 处理该异常的代码 } catch(类型2 参数2) { 处理该异常的代码 } … …try语句块中包含了可能抛出异常的代码,一旦抛出了异常,则退出try语句块,进入try后面的异常捕获和处理
  • 14. 抛出异常如果程序发生异常情况,而在当前的环境中获取不到异常处理的足够信息,我们可以创建一包含出错信息的对象并将该对象抛出当前环境,发送给更大的环境中。这称为异常抛出。 例: throw myerror(“something bad happened”); myerror是一个类,它以字符串变量为参数 throw int (5) int是一个内部类型,5是一个int类型的常数
  • 15. 当监测到程序出错时,就抛出异常。其格式为: try { …… 监测到程序出错时,throw 异常对象; …… } 其中被抛出的异常对象可以是预定义数据类型(例如int、double、char*等)的数据及其指针和引用,但更经常使用的是各种异常类的对象及其指针和引用。使用这些异常类对象的优点是能够提供更多关于程序错误的信息,包括处理各类错误的方法。这些异常类可以是系统所提供的,更多情况下可以是用户自己定义的。 应该注意:此处要求一定在try程序块中或它所调用的函数中抛出异常。以下是不在try程序块中而在try程序块所调用的函数中抛出异常的例子:  
  • 16. [例1] 在try程序块所调用的函数quotient( )中抛出异常的例子 #include  double quotient( double numerator, double divisor ) { if ( divisor == 0 ) throw divisor; //被抛出的异常对象是预定义类型数据 return numerator / divisor; }  
  • 17. void main() { try { cout << "The quotient of 18/9 is " << quotient ( 18, 9 ) << endl; cout << "The quotient of 4.5/9 is " << quotient ( 4.5, 9 ) << endl; cout << "The quotient of 18/0 is " << quotient ( 18, 0 ) << endl; } catch ( double dd ) {cout << "Exception : divisor " << dd << " used!" << endl; } } 从以上例子中可以看出,当程序不出错时,程序按照顺序控制结构逐条语句地向下执行,并返回除法运算结果。而当程序出错时(即当除数为零时),程序就在抛出异常处中断,不再执行以后的语句,并跳转至catch程序块再继续执行程序。程序运行结果: The quotient of 18/9 is 2 The quotient of 4.5/9 is 0.5 Exception : divisor 0 used!
  • 18. throw与其操作数抛出异常的语句形式: throw <可选的操作数>; throw通常指定一个操作数(或不指定操作数的特殊情况)。throw的操作数可以是任何类型,如果操作数是个对象,则称为异常对象。也可以抛出条件表达式而不是抛出对象,可以抛出不用于错误处理的对象。 抛出异常时,生成和初始化throw操作数的一个临时副本,然后这个临时对象初始化异常处理器中的参数。异常处理器执行完毕和退出时,删除临时对象。
  • 19. 异常抛出实例: 定义一个除法函数,当除数为0时,抛出一个用户定义的异常类对象,该对象能告诉用户发生了除零错误。
  • 20. 异常抛出实例 – 异常类定义 // Class DivideByZeroException to be used in exception // handling for throwing an exception on a division by zero. class DivideByZeroException { private: const char *message; public: DivideByZeroException() : message( "attempted to divide by zero" ) { } const char *what() const { return message; } };记录出现的异常情况Message的默认值告诉用户出现了什么异常
  • 21. 异常抛出实例 – 异常抛出double Div(int x, int y ) { if ( y == 0 ) throw DivideByZeroException(); return static_cast< double > ( x ) / y; }这条语句用默认构造函数生成了一个DivedeByZeroException类的对象,并把这个对象返回给调用它的函数,div函数执行结束
  • 22. 异常捕获 一个函数抛出异常,它必须假定该异常能被捕获和处理。异常捕获机制使得C++可以把问题集中在一处解决。
  • 23. catch捕获异常异常处理器放在catch块中, 形式如下: catch ( <捕获的异常类型> <可选参数> ) { <异常处理器代码> } catch处理器定义自己的范围。catch在括号中指定要捕获的对象类型。catch处理器中的参数可以命名也可以无名。如果是命名参数,则可以在处理器中引用这个参数。 如果是无名参数(只指定匹配抛出对象类型的类型),则信息不从抛出点传递到处理器中,只是控制从抛出点转到处理器中.许多异常都可以这样。 catch (…) 捕获任意类型的异常。
  • 24. 异常捕获原理如果一个异常信号被抛出,异常处理器中第一个参数与异常抛出对象相匹配的函数将捕获该异常信号,然后进入相应的catch语句,执行异常处理程序。
  • 25. 捕获异常的匹配规则匹配条件:下列情况下,catch处理器参数匹配所抛出对象的类型:     1、 实际是同一类型。     2、catch处理器参数类型是所抛出对象类型的public基类。    3、 处理器参数为基类指针或引用类型,而抛出对象为派生类指针或引用类型。    4、catch处理器为catch(...),捕获任意类型的异常。
  • 26. 捕获异常的匹配规则(续)匹配结果:抛出异常对象类型与catch处理器参数类型相符时,则两者相匹配, 程序将执行该类型的catch块(即该类型的异常处理器)。 匹配顺序:catch中在当前活动try块后面第一个匹配所抛出异常对象的异常处理器将捕获该异常。按顺序搜索异常处理器,寻找匹配项,并执行第一个匹配的处理器。处理器执行完毕时,控制恢复到最后一个catch块后面的第一条语句。
  • 27. 捕获异常的匹配规则(续)无匹配情况:若某个抛出对象没有任何匹配的异常处理器,这时匹配搜索会继续到外面一层try块。这个过程一直继续,若最终还是没有任何匹配的异常处理器。这时调用terminate(默认调用abort)终止程序。 多匹配情况:若几个异常处理器都匹配所抛出的对象,这可能有几个原因: 第一,有一个捕获任何异常的catch(…)处理器。 第二,由于继承层次,派生类对象可以由派生类类型的异常处理器和基类类型的异常处理器捕获。 这时执行第一个匹配的异常处理器。
  • 28. 捕获异常的匹配规则(续)catch(...)总是作为try块后面的处理器列表中最后一个处理器。 捕获基类类型的异常处理器放在捕获派生类类型的异常处理器之后。 将带void *参数类型的异常处理器放在具有其他指针类型的异常处理器后面。
  • 29. 除零异常的捕获int main() { int number1, number2; double result; cout << "Enter two integers (end-of-file to end): "; while ( cin >> number1 >> number2 ) { try { result = Div( number1, number2 ); cout << "The quotient is: " << result << endl; } catch ( DivideByZeroException ex ) { cout << "Exception occurred: " << ex.what() << '\n‘; } cout << "\nEnter two integers (end-of-file to end): “; } cout << endl; return 0; }try语句块中包含了可能抛出异常的代码Div,一旦抛出了异常,则退出try语句块,进入try后面的异常捕获和处理double Div(int x, int y ) { if ( y == 0 ) throw DivideByZeroException(); return static_cast< double > ( x ) / y; }
  • 30. int main() { int number1, number2; double result; cout << "Enter two integers (end-of-file to end): "; while ( cin >> number1 >> number2 ) { try { if (number2==0) throw DivideByZeroException(); result = static_cast( number1)/number2; cout << "The quotient is: " << result << endl; } catch ( DivideByZeroException ex ) { cout << "Exception occurred: " << ex.what() << '\n'; } cout << "\nEnter two integers (end-of-file to end): "; } cout << endl; return 0; }该程序实行了什么功能?30
  • 31. 输出结果Enter tow integers (end-of-file to end); l00 7 The quotient is: 14.2857 Enter tow integers (end-of-file to end); 100 0 Exception occurred: attempted to divide by zero Enter tow integers (end-of-file to end); 33 9 The quotient is: 3.66667 Enter tow integers {end-of-file to end):
  • 32. 异常抛出与检测实例二int Div(int x, int y) { if (y==0) throw y; return x/y; }抛出的异常不一定是对象,可以是一个结果为内置类型的表达式
  • 33. int main() { try { cout << Div(6,3) << endl; cout << Div(10,0) << endl; cout << Div(5,2) << endl; } catch (int) {cout << "divide by zero" << endl; } cout << "It’s Over" << endl; return 0; }2 divide by zero It’s Over调用一个函数时可能会收到一个异常。 如何知道是否会收到异常呢? 收到的是什么异常?异常规格声明
  • 34. 异常规格说明传统函数声明:返回类型 函数名(形式参数表); 函数可以抛出任何异常。 通常我们希望在调用函数时,知道该函数会抛出什么样的异常。所以允许在函数原型声明中指出 void f() throw(toobig, toosmall, divzero); 函数会抛出toobig, toosmall, divzero三种异常 void f() throw(); 函数不会有异常抛出。
  • 35. #include class up{}; class down{}; void f(int i) throw(up, down); int main() { for (int i=1;i<=3;++i) try { f(i); } catch (up) {cout << "up catched" << endl; } catch (down) {cout << "down catched" << endl;} return 0; } void f(int i) throw(up,down) { switch(i) {case 1: throw up(); case 2: throw down(); } }up catched down catched
  • 36. 示例#include { char *ptr; try{ //异常模块 if(ptr=new char[64*1024]==NULL) throw”Not Enough Memory!”; } Catch(char *str) //异常错误处理模块 {//… 错误处理代码 Cout<<“Exception:”<
  • 37. 控制通过正常的顺序执行到try语句,执行try块内的保护段; 如果保护段内没有异常,则后面的catch子句不被执行,程序从异常被抛掷的try块后的最后一个catch子句后面的语句继续执行 如果在保护段执行期间或者在保护段调用的任何函数(直接或者间接的调用)中有异常被抛掷,则从通过throw运算数创建的对象中创建一个异常对象(可能包含一个拷贝构造函数) 如果匹配的处理器没有找到,则函数terminate将被自动调用(调用abort终止程序) 如果找到了一个匹配的catch程序,且它通过值进行捕获,则其形参通过拷贝异常对象进行初始化。
  • 38. #include void Excp(); Ccass EX {public EX(){}; ~EX(){}; }; Class Demo {public: Demo() {cout<<“creating a Demo object.”<
  • 39. 小结为了提高程序的鲁棒性,程序需要对各种可能的异常进行处理 某些错误需要异地处理 C++的异常机制由try、throw和catch构成
  • 40. 40抛掷异常的程序段 ...... throw 表达式; ......捕获并处理异常的程序段 try 复合语句 catch(异常类型声明) 复合语句 catch(异常类型声明) 复合语句 …
  • 41. 41若有异常则通过throw操作创建一个异常对象并抛掷。 将可能抛出异常的程序段嵌在try块之中。控制通过正常的顺序执行到达try语句,然后执行try块内的保护段。 如果在保护段执行期间没有引起异常,那么跟在try块后的catch子句就不执行。程序从try块后跟随的最后一个catch子句后面的语句继续执行下去。 catch子句按其在try块后出现的顺序被检查。匹配的catch子句将捕获并处理异常(或继续抛掷异常)。 如果匹配的处理器未找到,则运行函数terminate将被自动调用,其缺省功能是调用abort终止程序。