1、C+程序设计,第10章(1) 异常处理,主要内容,运行时错误 异常处理的概念 异常处理的实现机制 异常的捕获与处理过程 异常处理中动态资源的释放 异常处理中对象的构造与析构 抛出异常对象 异常接口声明,运行时错误,程序中常见的错误:编译时错误:指程序在编译时,系统就能发现的语法错误(如:变量未定义、括号不配对、语句末尾缺分号、关键字拼写错等等)。对于程序中的语法错误,编译系统会告知在第几行出错以及什么样的错,因而较容易发现并加以纠正。运行时错误:指程序通过编译且能投入运行,但在运行过程中,会发生某些异常现象(如:出现除数为 0 的情况、输入数据时类型有错、存储空间耗尽、数组越界、文件打开失败而
2、无法读数等等),导致运行结果不正确,或程序异常终止,或发生死机现象。对于程序中潜在的运行时错误,常因较隐蔽不容易发现而难以纠正,尤其对于大型软件,要找出所有潜在的运行时错误几乎是不可能的。,异常处理的概念,程序中引入异常处理机制,以提高程序的健壮性:目标:提高程序的容错能力,使程序不仅在正确的情况下能正常运行,在有错的情况下也能作出相应的处理,使程序运行时不至于莫名其妙终止,或发生死机现象。任务:在程序设计时,就要事先分析其运行时可能出现的各种异常现象,分别制定出相应的处理方法,并将各个异常处理代码段嵌入到程序中。机制:程序运行时,若出现了某种异常现象,因程序本身对其已有防范措施,可以在异常发
3、生时停止发生异常的操作,并抛出异常信息,流程随即沿调用链退回,转入相应的异常处理代码段处理,而程序的其它部分仍然能够继续执行。 关于异常:异常处理中所涉及的异常,是指程序中可能检测到的、或可预见可能发生地的某些运行时的不正常情况。C+中,在建立异常抛出与异常处理之间有一整套程序设计的机制。,异常处理的实现机制,异常处理机制的组成: 异常的监测(try)异常的抛出(throw)异常的捕获并处理(catch) 在异常的发生地,需要抛出异常的程序段中使用 throw 将异常信息抛出。格式: throw ; 在异常的监测地,等待捕获并处理异常的程序段中使用 try catch 结构。格式: try 被
4、监测的语句序列 catch ( 异常1的类型声明 ) 类型1的异常处理代码段 catch ( 异常2的类型声明 ) 类型2的异常处理代码段 实现:程序运行过程中,若 try 块中任何类型的数据对象发生异常,都可通过 throw语句将异常信息抛出,流程随即沿调用链退回,直到该异常被其匹配的 catch 子句捕获,并在此执行异常处理;若 try 块中没有发生异常,则流程跳过该 try - catch 结构中的所有 catch 子句,然后继续执行。,异常处理的实现机制,关于 try catch 结构的使用:异常的监测try子句:将程序中可能出现异常,需要监测的语句或语句序列用 try 子句将其嵌在块
5、中,实施异常监测。 异常的捕获并处理catch子句:一个 catch子句,捕获并处理一种类型的异常,其后的圆括号中是所捕获异常的类型声明,而花括号中是相应的异常处理代码段。完整的 try - catch 结构:在一个 try - catch 结构中,只能有一个 try 子句, catch子句紧跟其后,二者之间不能插入其他语句,但可以有多个 catch 子句,以便捕获并处理不同类型的异常。 如: try 被监测的语句序列 catch ( double ) catch ( int ) 注意:若一个 catch 子句, 其后的圆括号中用删节号 “” 来指定其捕获异常的类型,则表示该 catch 子句
6、可以捕捉任何类型的异常。但这种 catch 子句应放在 try catch 结构中的最后,若作为第一个 catch子句,则其后的 catch子句都不起作用。,异常处理的实现机制,关于 throw 语句的使用:抛出异常throw运算符:只要是在 try 块中的语句,或该语句所调用的函数中发生异常,都可使用 throw 运算符,创建一个异常信息并抛出。 异常信息:的值可以是C+中任何类型的数据,因此异常信息可以是基本类型的数据,也可以是自定义类型的数据,如:类的对象。throw 语句的位置:throw 语句与 try catch 结构可以出现在同一个函数中,也可以不在。当发生异常,执行 throw
7、 语句将异常信息抛出后,首先在本函数中寻找与之匹配的 catch 子句;若本函数中无 try - catch 结构或找不到与之匹配的 catch 子句,流程就退回到其上一层函数去处理;若上一层函数也无 try catch 结构或找不到与之匹配的 catch 子句,流程就再退回到更上一层函数去处理 ; 若最终退回到 main() 函数中仍找不到与之匹配的 catch 子句,则系统调用库函数 terminate() 来终止程序运行。注意:若一个 catch 块中,也出现了 throw 语句,且句中未给出要抛出的异常信息,则表示:“ 我不处理该异常,继续抛出! ”,将当前正在处理的异常信息再次抛出,
8、请上一级处理。,【例】(除数为0,发生运行时错误,程序异常终止。) # include int divide ( int x , int y ) return ( x / y ) ; void main ( ) int a , b , c ; cout a b c ;cout “(” (a+b) “)(” (a+c) “)= ” divide ( a+b , a+c ) endl ;cout “(” (a+b) “)(” (a-c) “)= ” divide ( a+b , a-c ) endl ;cout “(” (a+c) “)(” (a+b) “)= ” divide ( a+c , a
9、+b ) endl ;cout “(” (a+c) “)(” (a-b) “)= ” divide ( a+c , a-b ) endl ;cout “ok!好棒!n” ; ,【例】(除数为0,发生运行时错误,进行异常处理。) # include int divide ( int x, int y ) if ( y = 0 ) throw y ; return ( x / y ) ; void main ( ) int a , b , c ; cout a b c ;try cout “(”(a+b)“)(”(a+c)“)= ” divide ( a+b , a+c ) endl ;cout
10、“(”(a+b)“)(”(a-c)“)= ” divide ( a+b , a-c ) endl ;cout “(”(a+c)“)(”(a+b)“)= ” divide ( a+c , a+b ) endl ;cout “(”(a+c)“)(”(a-b)“)= ” divide ( a+c , a-b ) endl ; catch ( int ) cout “对除数为 0 的异常,已进行处理!n” ; cout “ok!好棒!n” ; ,异常的捕获与处理过程,异常的抛出:程序运行过程中,流程进入到某一处时发生了某种异常,且已执行了 throw 语句将该异常信息抛出,流程就不再执行该 throw
11、 语句后面的语句,随即改变流程的方向去寻找 “捕获该异常的 catch 子句”,而寻找过程是 “逆着函数调用链逐层回退到各个主调函数” 中进行。 寻找 “捕获该异常的 catch 子句”:是指要寻找的那个 catch 子句中,其所声明的异常类型与 throw 语句所抛出的异常信息在类型上匹配。 寻找过程是 “逆着函数调用链逐层回退到各个主调函数” 中进行:是指首先在 throw 语句本身所在的函数中寻找与其所抛出异常信息在类型上相匹配的 catch 子句;若本层函数中无 try catch 结构或找不到与之匹配的 catch 子句,流程就沿着函数调用链退回到其上一层函数中去处理;若上一层函数中
12、也无 try - catch 结构或也找不到与之匹配的 catch 子句,流程就再继续沿着函数调用链退回到更上一层函数中去处理 ;直到在某一层函数中寻找到,或已退回到 main() 函数中仍未找到 为止。,异常的捕获与处理过程,捕获到:在寻找过程中,只要遇到第一个与之匹配的 catch 子句,流程就进入该 catch 子句并执行块中的代码段,完成对该异常的处理。异常处理结束后,程序继续执行,流程将跳过该 try catch 结构中的其余 catch 子句后继续执行。 未捕获到:在寻找过程中,若已退回到 main() 函数中仍找不到与之匹配的 catch 子句,则系统调用库函数 terminat
13、e() 来终止程序运行。 terminate() 的缺省行为是调用 abort() 函数,表示从程序中非正常退出。 特别提示:从异常抛出到异常捕获并处理,在逐层退出函数调用链的过程中,对于所退出的函数,其残存于局部数据区、自由存储区中的对象、变量等资源应适时释放!其中:在局部数据区的资源,是由系统自动释放的。而在自由存储区的资源,是用 new 运算符动态获得的,系统不会自动释放!因此,在异常处理中,对这些资源的释放必须加以考虑!,【例】(函数嵌套调用;异常的监测、抛出、捕获并处理。) # include void f1( ) , f2( ) , f3( ) ; void main( ) cou
14、t “进入 main() 函数了!n” ;try f1( ) ; f2( ) ; catch ( double ) cout “ok!0001n” ; cout “退出 main() 函数了!n” ; void f1( ) cout “进入 f1() 函数了!n” ;try f2( ) ; catch ( char ) cout “ok!1001n” ; catch ( double ) cout “ok!1002n” ; cout “退出 f1() 函数了!n” ; void f2( ) cout “进入 f2() 函数了!n” ;try f3( ) ; catch ( int ) cout
15、 “ok!2001n” ; cout “退出 f2() 函数了!n” ; ,void f3() cout a ;int b ;cout b ;double c ;cout c ;try if ( a=0 ) throw a;if ( b=0 ) throw b;if ( c=0 ) throw c; catch ( char ) cout “ok!3001n” ; cout “退出 f3() 函数了!n” ; ,(请思考:将f3()函数修改如下,观察运行结果。) void f3( ) cout a ;int b ;cout b ;double c ;cout c ;try if ( a=0 )
16、 throw a ;if ( b=0 ) throw b ;if ( c=0 ) throw c ; catch ( char ) cout “ok!3001n” ; throw ; cout “退出 f3() 函数了!n” ; ,(请思考:将f3()函数修改如下,观察运行结果。) void f3() cout a ;int b ;cout b ;double c ;cout c ;try if ( a=0 ) throw a ;if ( b=0 ) throw b ;if ( c=0 ) throw c ; catch ( ) cout “ok!3001n” ; throw ; cout “
17、退出 f3() 函数了!n” ; ,(请思考:将f3()函数修改如下,观察运行结果。) void f3( ) cout a ;short b ;cout b ;double c ;cout c ;try if ( a=0 ) throw a ;if ( b=0 ) throw b ;if ( c=0 ) throw c ; catch ( char ) cout “ok!3001n” ; cout “退出 f3() 函数了!n” ; ,异常处理中动态资源的释放,从异常抛出到异常捕获并处理,在逐层退出函数调用链的过程中,对于所退出的函数,其残存于局部数据区、自由存储区中的对象、变量等资源应适时释
18、放!其中:在局部数据区的资源,由系统自动释放;而在自由存储区的资源,是函数用 new 运算符动态获得的,系统不会自动释放!因此,在异常处理中,对这些资源的释放必须加以考虑!若因异常处理,导致对于所退出的函数,其动态获得的资源不能正常释放,应考虑在对应的 catch 子句中释放这些资源!但是,由于无法预知所有可能的异常,也就无法对任何异常都定义出相应的 catch 子句来释放这些资源。通常方法是:使用通用的 catch ( ) 子句与 throw 语句组合使用,使任何的异常都可进入该 catch 子句,进入后先释放这些动态资源,然后将异常重新抛出,由上一层函数再去处理。例: void fun (
19、 ) int *p = new int 10 ; try 被监测的语句序列 /可能抛出多种类型的异常catch ( ) delete p ; /异常退出时释放动态数组throw ; delete p; /正常退出时释放动态数组,【例】(异常处理、动态资源释放问题。) #include void f( ) cout pai; if ( pai=0) throw pai; int *pb = new int 5 ;cout pbi; if ( pbi=0 ) throw pbi; delete pa ; delete pb ; catch ( char ) cout “输入0字符的异常,已处理了!
20、n”; catch ( int ) cout “输入0整数的异常,已处理了!n”; cout “退出 f() 函数了!n”; void main( ) cout “进入 main() 函数了!n” ;f( ); cout “退出 main() 函数了!n” ; ,【例】(异常处理中的动态资源释放。) #include void f( ) cout pai; if ( pai=0 ) throw pai; cout pbi; if ( pbi=0) throw pbi; catch ( . ) delete pa ; delete pb ; throw ; delete pa ; delete
21、pb ; cout “退出 f() 函数了!n” ; void main( ) cout “进入 main() 函数了!n” ;try f( ) ; catch ( char ) cout “输入0字符的异常,已处理了!n”; catch ( int ) cout “输入0整数的异常,已处理了!n”; cout “退出 main() 函数了!n” ; ,异常处理中对象的构造与析构,异常处理中对象的构造与析构从异常抛出到异常捕获并处理,在退出函数调用链的过程中,对于所退出的函数,其残存于局部数据区、自由存储区中的对象、变量等资源应适时释放!其中:对于所退出的函数,其残存在局部数据区的对象,系统将
22、自动撤消,撤消前系统将对其析构。对于所退出的函数,其在自由存储区动态创建的对象,系统不会自动撤消!因此,在异常处理的 catch 子句中,必须考虑用 delete 运算符来释放相应的动态对象!,抛出异常对象,抛出异常对象:在程序运行过程中,流程进入到某一处时发生了某种异常,也可以通过 throw 语句中的表达式,建立某个异常类的对象,并将其抛出。格式: throw 某个异常类的对象 ; 注意:若所抛出的某个异常类对象是在 throw 语句中建立并抛出,则按 C+ 的规定,其生命期只在该表达式中有效。为了使 throw 语句中建立并抛出的异常类对象在离开 throw 语句后仍然有效,C+是这样处
23、理:在执行 “ throw 表达式 ” 时,通过调用异常类的构造函数创建一个临时对象,再将临时对象拷贝到一个被称为异常对象的存储区中,然后撤销该临时对象,而异常存储区中的那个异常对象,其生命期会保证持续到该异常被捕捉并处理完。,【例】(定义一个异常类、抛出异常对象、异常处理中的构造和析构。) #include class Except /定义异常类 public: Except( ) cout “构造 Except 异常类的对象了! 对象地址=” (int) this endl ; Except( ) cout “析构 Except 异常类的对象了! 对象地址=” (int) this end
24、l ; const char * show( ) const return “看看我吧,我就是异常!” ; ; class Demo /定义 Demo 类 public: Demo( ) cout “构造 Demo 类的对象了! 对象地址=” (int) this endl ; Demo( ) cout “析构 Demo类的对象了! 对象地址=” (int) this endl ; ;,void f1( ) cout “进入 f1() 函数了!n” ; Demo d ; /局部对象cout “在 f1() 函数中抛出 Except 异常类的对象!n” ;throw Except( ) ; /创
25、建一个异常类Except的临时对象并抛出,注意其生命期。cout “退出 f1() 函数了!n” ; void f2( ) cout “进入 f2() 函数了!n” ; Demo *p = new Demo ; /动态对象cout “在 f2() 函数中抛出 Except 异常类的对象!n” ;throw Except( ) ; /创建一个异常类Except的临时对象并抛出,注意其生命期。delete p ; cout “退出 f2() 函数了!n” ;,void main( ) cout “进入 main() 函数了!n” ;Demo d1 ; /局部对象Demo *p1 = new Dem
26、o ; /动态对象try Demo d2 ; /局部对象Demo *p2 = new Demo ; /动态对象f1( ) ;f2( ) ; delete p2 ; catch ( Except e ) cout “捕获到 Except 类型的异常:n” ;cout e.show( ) endl ; catch ( char *str ) cout “捕获到其它类型的异常:n” ;cout str endl ; delete p1 ;cout “退出 main() 函数了!n” ; ,(请思考: main()函数修改如下,观察运行结果。) void main( ) cout “进入 main()
27、 函数了!n” ;Demo d1 ; /局部对象Demo *p1 = new Demo ; /动态对象try Demo d2 ; /局部对象Demo *p2 = new Demo ; /动态对象f2( ) ;f1( ) ; delete p2 ; catch ( Except e ) cout “捕获到 Except 类型的异常:n” ;cout e.show( ) endl ; catch ( char *str ) cout “捕获到其它类型的异常:n” ;cout str endl ; delete p1 ;cout “退出 main() 函数了!n” ; ,(请思考: main()函数
28、修改如下,观察运行结果。) void main( ) cout “进入 main() 函数了!n” ;Demo d1 ; /局部对象Demo *p1 = new Demo ; /动态对象try Demo d2 ; /局部对象Demo *p2 = new Demo ; /动态对象f2( ) ;f1( ) ; delete p2 ; catch ( Except ,异常接口声明,异常接口声明:可以在函数的头部,通过 throw 子句列出该函数可能抛出的所有异常类型。【例】 void fun( ) throw ( A ,B ,C ,D ) 函数体 若函数的头部无异常接口声明,则该函数可以抛出任何类型的异常。【例】 void fun( ) 函数体 不允许抛出任何类型异常的函数声明: 【例】 void fun( ) throw ( ) 函数体 ,