1.
异常处理的实现:
如果在执行一个函数过程中出现异常,可以不在本函数中立即处理,而是发出一个信息,传给它的上一级(即调用它的函数),它的上级捕捉到这个信息后进行处理。如果上一级的函数也不能处理,就再传给其上一级,由其上一级处理。如此逐级上送,如果到最高一级还无法处理,最后只好异常终止程序的执行。
C++处理异常的机制是由3个部分组成的,即检查(try)、抛出(throw)和捕捉(catch)。把需要检查的语句放在try块中,throw用来当出现异常时发出一个异常信息,而catch则用来捕捉异常信息,如果捕捉到了异常信息,就处理它。
throw语句一般是由throw运算符和一个数据组成的,其形式为
throw 表达式;
try-catch的结构为
try
{被检查的语句}
catch(异常信息类型 [变量名])
{进行异常处理的语句}
(1) 被检测的函数必须放在try块中,否则不起作用。
(2) try块和catch块作为一个整体出现,catch块是try-catch结构中的一部分,必须紧跟在try块之后,不能单独使用,在二者之间也不能插入其他语句。但是在一个try-catch结构中,可以只有try块而无catch块。即在本函数中只检查而不处理,把catch处理块放在其他函数中。
(3) try和catch块中必须有用花括号括起来的复合语句,即使花括号内只有一个语句,也不能省略花括号。
(4) 一个try-catch结构中只能有一个try块,但却可以有多个catch块,以便与不同的异常信息匹配。
(5) catch后面的圆括号中,一般只写异常信息的类型名, 如catch(double)
catch只检查所捕获异常信息的类型,而不检查它们的值。因此如果需要检测多个不同的异常信息,应当由throw抛出不同类型的异常信息。
异常信息可以是C++系统预定义的标准类型,也可以是用户自定义的类型(如结构体或类)。如果由throw抛出的信息属于该类型或其子类型,则catch与throw二者匹配,catch捕获该异常信息。catch还可以有另外一种写法,即除了指定类型名外,还指定变量名,如catch(double d),此时如果throw抛出的异常信息是double型的变量a,则catch在捕获异常信息a的同时,还使d获得a的值,或者说d得到a的一个拷贝。什么时候需要这样做呢?有时希望在捕获异常信息时,还能利用throw抛出的值,如
catch(double d)
{cout<<″throw ″<<d;}
这时会输出d的值(也就是a值)。当抛出的是类对象时,有时希望在catch块中显示该对象中的某些信息。这时就需要在catch的参数中写出变量名(类对象名)。
(6) 如果在catch子句中没有指定异常信息的类型,而用了删节号“…”,则表示它可以捕捉任何类型的异常信息,如catch(…) {cout<<″OK″<<endl;}
它能捕捉所有类型的异常信息,并输出″OK″。这种catch子句应放在try-catch结构中的最后,相当于“其他”。如果把它作为第一个catch子句,则后面的catch子句都不起作用。
(7) try-catch结构可以与throw出现在同一个函数中,也可以不在同一函数中。当throw抛出异常信息后,首先在本函数中寻找与之匹配的catch,如果在本函数中无try-catch结构或找不到与之匹配的catch,就转到离开出现异常最近的try-catch结构去处理。
(8) 在某些情况下,在throw语句中可以不包括表达式,如throw;表示“我不处理这个异常,请上级处理”。
(9) 如果throw抛出的异常信息找不到与之匹配的catch块,那么系统就会调用一个系统函数terminate,使程序终止运行。
#include <iostream>
#include <cmath>
using namespace std;
void main()
{double triangle(double,double,double);
double a,b,c;
cin>>a>>b>>c;
try//在try块中包含要检查的函数
{while(a>0 && b>0 &&c>0)
{cout<<triangle(a,b,c)<<endl;
cin>>a>>b>>c;
}
}
catch(double) //用catch捕捉异常信息并作相应处理
{cout<<″a=″<<a<<″,b=″<<b<<″,c=″<<c<<″,thatis not a triangle!″<<endl;}
cout<<″end″<<endl;
}
double triangle(double a,double b,double c) //计算三角形的面积的函数
{double s=(a+b+c)/2;
if(a+b<=c||b+c<=a||c+a<=b)throw a; //当不符合三角形条件抛出异常信息
return sqrt(s*(s-a)*(s-b)*(s-c));
}
(1) 首先把可能出现异常的、需要检查的语句或程序段放在try后面的花括号中。(2) 程序开始运行后,按正常的顺序执行到try块,开始执行try块中花括号内的语句。如果在执行try块内的语句过程中没有发生异常,则catch子句不起作用,流程转到catch子句后面的语句继续执行。
(3) 如果在执行try块内的语句(包括其所调用的函数)过程中发生异常,则throw运算符抛出一个异常信息。throw抛出异常信息后,流程立即离开本函数,转到其上一级的函数(main 函数)。throw抛出什么样的数据由程序设计者自定,可以是任何类型的数据。
(4) 这个异常信息提供给try-catch结构,系统会寻找与之匹配的catch子句。
(5) 在进行异常处理后,程序并不会自动终止,继续执行catch子句后面的语句。由于catch子句是用来处理异常信息的,往往被称为catch异常处理块或catch异常处理器。
//在函数嵌套的情况下检测异常处理
#include <iostream>
using namespace std;
int main( )
{void f1( );
try
{f1( );}//调用f1( )
catch(double)
{cout<<″OK0!″<<endl;}
cout<<″end0″<<endl;
return 0;
}
void f1( )
{void f2( );
try
{f2( );} //调用f2( )
catch(char)
{cout<<″OK1!″;}
cout<<″end1″<<endl;
}
void f2( )
{void f3( );
try
{f3( );} //调用f3( )
catch(int)
{cout<<″Ok2!″<<endl;}
cout<<″end2″<<endl;
}
void f3( )
{double a=0;
try
{throw a;} //抛出double类型异常信息
catch(float)
{cout<<″OK3!″<<endl;}
cout<<″end3″<<endl;
}
(1)
程序运行结果如下:
OK0! (在主函数中捕获异常)
end0 (执行主函数中最后一个语句时的输出)
(2)
如果将f3函数中的catch子句改为catch(double),而程序中其他部分不变,则程序运行结果如下:
OK3! (在f3函数中捕获异常)
end3 (执行f3函数中最后一个语句时的输出)
end2 (执行f2函数中最后一个语句时的输出)
end1 (执行f1函数中最后一个语句时的输出)
end0 (执行主函数中最后一个语句时的输出)
(3) 如果在此基础上再将f3函数中的catch块改为
catch(double)
{cout<<″OK3!″<<endl;throw;}
程序运行结果如下:
OK3! (在f3函数中捕获异常)
OK0! (在主函数中捕获异常)
end0 (执行主函数中最后一个语句时的输出)
2.
带异常处理的函数原型:
为便于阅读程序,使用户在看程序时能够知道所用的函数是否会抛出异常信息以及异常信息可能的类型,C++允许在声明函数时列出可能抛出的异常类型
double triangle(double,double,double) throw(double);
表示triangle函数只能抛出double类型的异常信息。如果写成
double triangle(double,double,double) throw(int,double,float,char);
则表示triangle函数可以抛出int,double,float或char类型的异常信息。异常指定是函数声明的一部分,必须同时出现在函数声明和函数定义的首行中,否则在进行函数的另一次声明时,编译系统会报告“类型不匹配”。
如果在声明函数时未列出可能抛出的异常类型,则该函数可以抛出任何类型的异常信息。
如果想声明一个不能抛出异常的函数,可以写成以下形式:
double triangle(double,double,double) throw();//throw无参数
这时即使在函数执行过程中出现了throw语句,实际上也并不执行throw语句,并不抛出任何异常信息,程序将非正常终止。
3.
创建对象的异常处理:
如果在try块(或try块中调用的函数)中定义了类对象,在建立该对象时要调用构造函数。在执行try块 (包括在try块中调用其他函数) 的过程中如果发生了异常,此时流程立即离开try块。这样流程就有可能离开该对象的作用域而转到其他函数,因而应当事先做好结束对象前的清理工作,C++的异常处理机制会在throw抛出异常信息被catch捕获时,对有关的局部对象进行析构(调用类对象的析构函数), 析构对象的顺序与构造的顺序相反,然后执行与异常信息匹配的catch块中的语句
#include <iostream>
#include <string>
using namespace std;
class Student
{public:
Student(int n,string nam)//定义构造函数
{cout<<″constructor-″<<n<<endl;
num=n;name=nam;}
~Student( ){cout<<″destructor-″<<num<<endl;}//定义析构函数
void get_data( ); //成员函数声明
private:
int num;
string name;
};
void Student::get_data( ) //定义成员函数
{if(num==0) throw num; //如num=0,抛出int型变量num
else cout<<num<<″ ″<<name<<endl; //若num≠0,输出num,name
cout<<″in get_data()″<<endl; //输出信息,表示目前在get_data函数中
}
void fun( )
{Student stud1(1101,″Tan″); //建立对象stud1
stud1.get_data( ); //调用stud1的get_data函数
Student stud2(0,″Li″); //建立对象stud2
stud2.get_data( ); //调用stud2的get_data函数
}
int main( )
{cout<<″main begin″<<endl; //表示主函数开始了
cout<<″call fun( )″<<endl; //表示调用fun函数
try
{fun( );} //调用fun函数
catch(int n)
{cout<<″num=″<<n<<″,error!″<<endl;} //表示num=0出错
cout<<″main end″<<endl; //表示主函数结束
return 0;
}
程序运行结果如下:main begin
call fun( )
constructor-1101
1101 tan
in get_data()
constructor-0
destructor-0
destructor-1101
num=0,error!
main end