异常处理结构
C++中通过try、throw、catch结构实现了异常的检测、抛出及捕捉。简单的异常处理过程如下所示:
try{ //异常检测
…
throw 异常类型1对应的异常值; //抛出异常,这里抛出异常类型1的异常值
}
catch(异常类型1) //捕捉异常类型1的异常
{
进行异常处理的语句
}
catch(异常类型2) //捕捉异常类型2的异常
{
进行异常处理的语句
}
上述结构中,try块检测异常,若出现异常则由throw抛出,通常throw后跟随抛出的异常值或异常对象,本段代码中抛出的是异常类型1的异常值。抛出异常后从try块对应的后续各catch子句中依次寻找与抛出异常类型匹配的结构,匹配成功时进入该catch子句完成异常处理。
通过上述异常处理描述,总结异常处理的基本流程如下:
1、对某段可能产生异常的代码或函数使用try结构进行检测。
2、如果在执行try结构期间没有引起异常,则跟在try后面的catch结构不会执行。程序从try结构后跟随的最后一个catch子句后面的语句继续向下执行。
3、如果在执行try结构期间发生异常,则在异常发生的位置使用throw抛出异常,一个异常对象将被创建。
4、抛出异常后,若异常的抛出点在一个try结构中,则该try语句后的catch子句会按顺序检查异常类型是否与声明的类型匹配;若异常抛出点不在任何try结构中,或抛出的异常与各个catch子句声明的类型皆不匹配,则结束当前函数的执行,回到当前函数的调用点,把调用点作为异常的抛出点,重复上述过程,直到异常被某catch子句捕获,执行catch子句内容完成异常处理,当前try-catch结构执行完毕。
5、若始终未找到与异常匹配的catch子句,即到main()函数还未实现异常匹配,则运行库函数terminate(),终止程序。
下面对C++中检测、抛出、捕捉异常的三种语法规则及使用方法进行详细介绍。
1、 检测异常try
try结构用于检测异常,具体的语法格式如下所示:
try{
语句
}
关键字try后用花括号包含待检测的语句块,在try结构中应直接包含抛出异常的throw语句或调用包含抛出异常的函数,try结构中的内容如下所示:
try{
… //可能产生异常的代码
throw 异常值; //抛出异常
//function(); //调用可能会抛出异常的函数
}
catch(type1)
{
… //对应类型的异常处理代码
}
catch(type2)
{
… //对应类型的异常处理代码
}
try块检测到异常后,会去最近进入的还未退出的try块寻找能与异常类型匹配的catch结构,若匹配不成功,则可以寻找更外层的try块对应的catch结构,直到异常类型匹配或变为未捕捉的异常。
需要注意的是,try块定义了一个语句块,在其中可以定义局部变量,在try块外部该局部变量不可用。
2、 抛出异常throw
抛出异常是通过throw语句实现的,该语句由throw及表达式构成,定义格式如下:
throw 表达式;
语法形式上,throw类似于return语句。throw后面的表达式可以是常数、变量或对象。这里表达式类型比表达式值更重要,因为在catch结构捕捉异常时,是针对抛出异常类型进行判断,如果类型一致,则catch捕捉该异常,否则再去后续的catch块中重新进行匹配查找。
在使用throw抛出异常时,可以给出如下代码进行异常抛出:
throw 1; //抛出int类型的异常,数值为1
throw ("异常"); //抛出char *类型的异常,内容为“异常”
catch块中是通过异常值类型完成匹配,因此若想通过throw对抛出的多个异常分别进行处理,需要throw后加不同类型的表达式,而不能根据表达式的不同数值区分不同类型的异常,若有如下代码:
throw 1; //抛出值为1的int类型异常
throw 2; //抛出值为2的int类型异常
上述代码抛出的异常将被同一个catch块处理。
3、 捕获异常catch
抛出异常后,程序的控制权将被异常类型匹配成功的catch块获得,catch块用于处理异常,catch块也被称为异常处理器,其定义结构如下所示:
catch(异常类型1声明)
{
}
catch(异常类型2声明)
{
}
…
catch(异常类型n声明)
{
}
通常try块对应的catch块会有多个,用于处理不同类型的异常。catch后面括号中的异常类型声明可以是类型或对象声明,花括号中描述用于异常处理的代码。catch块的书写格式类似于函数定义,异常类型声明相当于函数参数。
catch块必须直接放在try块之后,根据catch的排列顺序依次对抛出的异常进行测试,若异常类型与某catch块的异常类型声明匹配则进入该catch块完成异常处理。
另外,在catch块中也可定义局部变量,该变量在catch块外不可用。
接下来通过一个案例来学习检测、抛出、捕获异常的流程,如例1所示。
例1
1 #include <iostream>
2 #include <string>
3 using namespace std;
4
5 int main()
6 {
7 int int_n1, int_n2; //定义两个整型变量
8 cout << "Please input two integers:"; //输入提示
9 cin >> int_n1 >> int_n2; //输入两个整数
10 try
11 {
12 cout << "Maybe exception code:" << endl; //提示可能出现异常的代码
13 if (int_n2 == 0) //除数为0则抛出异常
14 {
15 throw 0;
16 }
17 else
18 {
19 cout << int_n1 << "/" << int_n2 << " = "
20 << (int_n1 / int_n2) << endl; //除数非零显示相除结果
21 }
22 }
23 catch(int) //捕捉参数为整型的异常
24 {
25 cout << "exception:div 0!" << endl; //异常处理代码
26 }
27 system("pause");
28 return 0;
29 }
若输入的除数为0,则程序的运行结果如图1所示。
图1 例1运行结果
例1中异常处理的基本结构如代码第10-26行所示。第10-22行对可能产生异常的代码使用try块进行测试,若除数为0则通过第15行的throw抛出异常。throw后面是整数0,抛出了int类型的异常,try块后catch处理的正是int类型的异常,则显示相应的异常处理信息。
抛出异常和捕捉异常的代码通常会封装在不同的模块中,因为包含有抛出异常的代码会实现某个功能,为了代码的复用,将抛出异常的代码封装为独立的模块,与捕捉异常的代码实现了分离。例如,可将例7-2中的整数相除操作封装为独立函数,则抛出、捕获异常会分列在不同模块,通过try、throw、catch结构实现了不同模块间的通信。
接下来将7-2中的整数相除操作封装成函数,如例2所示。
例2
1 #include <iostream>
2 #include <string>
3 using namespace std;
4
5 int int_div(int a, int b) //实现整数相除的函数
6 {
7 if (b == 0){ //若除数为0,抛出异常
8 throw 0;
9 }
10 return a / b;
11 }
12 int main()
13 {
14 int int_n1, int_n2; //定义两个整型变量
15 while (1){ //循环多次读取数据,完成相除操作
16 cout << "Please input two integers:";
17 cin >> int_n1 >> int_n2; //输入两个整型数
18 try {
19 cout << "Maybe exception code:" << endl;
20 cout << int_n1 << "/" << int_n2 << " = "
21 << int_div(int_n1, int_n2) << endl; //除数非零显示相除结果
22 }
23 catch (int) //捕捉整型异常
24 {
25 cout << "exception:div 0!" << endl; //异常处理代码
26 }
27 }
28 system("pause");
29 return 0;
30 }
程序运行结果如图2所示。
图2 例2运行结果
例2中代码第5-11行的int_div()函数实现了整数相除,若除数为0则抛出整数异常。main()函数中通过循环结构完成多对数据的相除操作,在第18-26行测试并捕捉异常,本例中try结构没有直接包含throw语句,而是包含了可能抛出异常的函数调用int_div()。
从运行结果分析抛出异常、捕捉异常结构分离的程序,其执行流程为:
通过try块中调用含有throw语句的函数int_div(),若出现异常,由于int_div()函数中不包含捕获异常的代码,程序从int_div()函数返回,不再执行throw语句后续内容。控制权从int_div()函数返回到其调用者main()函数中,在main()函数中寻找类型匹配的catch块,执行完相应的catch块后,执行main()函数剩余语句。
若int_div()函数没有抛出异常,从该函数返回main()函数后,忽略catch块,执行后续代码。