C++中try_catch_throw的做异常处理
选择异常处理的编程方法的具体原因如下:
1、 把错误处理和真正的工作分开来;
2、 代码更易组织,更清晰,复杂的工作任务更容易实现;
3、 毫无疑问,更安全了,不至于由于一些小的疏忽而使程序意外崩溃了;
4、 由于C++中的try catch可以分层嵌套,所以它提供了一种方法使得程序的控制流可以安全的跳转到上层(或者上上层)的错误处理模块中去。(不同于return语句,异常处理的控制流是可以安全地跨越一个或多个函数 )。
5、 还有一个重要的原因就是,由于目前需要开发的软件产品总是变得越来越复杂、越来越庞大,如果系统中没有一个可靠的异常处理模型,那必定是一件十分糟糕的局面。
异常处理仅仅通过类型而不是通过值来匹配的,否则又回到了传统的错误处理技术上去了,所以catch块的参数可以没有参数名称,只需要参数类型,除非要使用那个参数。
其中关键字try表示定义一个受到监控、受到保护的程序代码块;关键字catch与try遥相呼应,定义当try block(受监控的程序块)出现异常时,错误处理的程序模块,并且每个catch block都带一个参数(类似于函数定义时的数那样),这个参数的数据类型用于异常对象的数据类型进行匹配;而throw则是检测到一个异常错误发生后向外抛出一个异常事件,通知对应的catch程序块执行对应的错误处理。
1、还是给一个例子吧!如下:
int main()
{
cout << "In main." << endl;
//定义一个try block,它是用一对花括号{}所括起来的块作用域的代码块
try
{
cout << "在 try block 中, 准备抛出一个异常." << endl;
//这里抛出一个异常(其中异常对象的数据类型是int,值为1)
//由于在try block中的代码是受到监控保护的,所以抛出异常后,程序的
//控制流便转到随后的catch block中
throw 1;
cout << "在 try block 中, 由于前面抛出了一个异常,因此这里的代码是不会得以执行到的" << endl;
}
//这里必须相对应地,至少定义一个catch block,同样它也是用花括号括起来的
catch( int& value )
{
cout << "在 catch block 中, 处理异常错误。异常对象value的值为:"<< value << endl;
}
cout << "Back in main. Execution resumes here." << endl;
return 0;
}
2、语法很简单吧!的确如此。另外一个try block可以有多个对应的catch block,可为什么要多个catch block 呢?这是因为每个catch block匹配一种类型的异常错误对象的处理,多个catch block呢就可以针对不同的异常错误类型分别处理。毕竟异常错误也是分级别的呀!有致命的、有一般的、有警告的,甚至还有的只是事件通知。例子如下:
int main()
{
try
{
cout << "在 try block 中, 准备抛出一个int数据类型的异常." << endl;
throw 1;
cout << "在 try block 中, 准备抛出一个double数据类型的异常." << endl;
throw 0.5;
}
catch( int& value )
{
cout << "在 catch block 中, int数据类型处理异常错误。"<< endl;
}
catch( double& d_value )
{
cout << "在 catch block 中, double数据类型处理异常错误。"<< endl;
}
return 0;
}
3、一个函数中可以有多个trycatch结构块,例子如下:
int main()
{
try
{
cout << "在 try block 中, 准备抛出一个int数据类型的异常." << endl;
throw 1;
}
catch( int& value )
{
cout << "在 catch block 中, int数据类型处理异常错误。"<< endl;
}
//这里是二个trycatch结构块,当然也可以有第三、第四个,甚至更多
try
{
cout << "在 try block 中, 准备抛出一个double数据类型的异常." << endl;
throw 0.5;
}
catch( double& d_value )
{
cout << "在 catch block 中, double数据类型处理异常错误。"<< endl;
}
return 0;
}
4、上面提到一个try block可以有多个对应的catch block,这样便于不同的异常错误分类处理,其实这只是异常错误分类处理的方法之一(暂且把它叫做横向展开的吧!)。另外还有一种就是纵向的,也即是分层的、trycatch块是可以嵌套的,当在低层的trycatch结构块中不能匹配到相同类型的catch block时,它就会到上层的trycatch块中去寻找匹配到正确的catch block异常处理模块。例程如下:
cout << "在 try block 中, 准备抛出一个int数据类型的异常." << endl;
cout << "在 catch block 中, int数据类型处理异常错误。"<< endl;
cout << "在 try block 中, 准备抛出一个double数据类型的异常." << endl;
cout << "在 catch block 中, double数据类型处理异常错误。"<< endl;
5、讲到是trycatch块是可以嵌套分层的,并且通过异常对象的数据类型来进行匹配,以找到正确的catch block异常错误处理代码。这里就不得不详细叙述一下通过异常对象的数据类型来进行匹配找到正确的catch block的过程。
(1) 首先在抛出异常的trycatch块中查找catch block,按顺序先是与第一个catch block块匹配,如果抛出的异常对象的数据类型与catch block中传入的异常对象的临时变量(就是catch语句后面参数)的数据类型完全相同,或是它的子类型对象,则匹配成功,进入到catch block中执行;否则到二步;
(2) 如果有二个或更多的catch block,则继续查找匹配第二个、第三个,乃至最后一个catch block,如匹配成功,则进入到对应的catch block中执行;否则到三步;
(3) 返回到上一级的trycatch块中,按规则继续查找对应的catch block。如果找到,进入到对应的catch block中执行;否则到四步;
(4) 再到上上级的trycatch块中,如此不断递归,直到匹配到顶级的trycatch块中的最后一个catch block,如果找到,进入到对应的catch block中执行;否则程序将会执行terminate()退出。
另外分层嵌套的trycatch块是可以跨越函数作用域的,例程如下:
cout << "在 try block 中, 准备抛出一个int数据类型的异常." << endl;
//由于这个trycatch块中不能找到匹配的catch block,所以
//它会继续查找到调用这个函数的上层函数的trycatch块。
cout << "在 catch block 中, int数据类型处理异常错误。"<< endl;
cout << "在 try block 中, 准备抛出一个double数据类型的异常." << endl;
cout << "在 catch block 中, double数据类型处理异常错误。"<< endl;
cout << "在 catch block 中, int数据类型处理异常错误。"<< endl;
6、刚才提到,嵌套的trycatch块是可以跨越函数作用域的,其实这里面还有另外一层涵义,就是抛出异常对象的函数中并不一定必须存在 trycatch块,它可以是调用这个函数的上层函数中存在trycatch块,这样这个函数的代码也同样是受保护、受监控的代码;当然即便是上层调用函数不存在trycatch块,也只是不能找到处理这类异常对象错误处理的catch block而已,例程如下:
//由于这个函数中是没有trycatch块的,所以它会查找到调用这个函数的上
cout << "在 try block 中, 准备抛出一个double数据类型的异常." << endl;
cout << "在 catch block 中, double数据类型处理异常错误。"<< endl;
cout << "在 catch block 中, int数据类型处理异常错误。"<< endl;
//如果这里调用这个函数,那么由于main()已经是调用栈的顶层函数,因此不能找到对应的catch block,所以程序会执行terminate()退出。
// [特别提示]:在C++标准中规定,可以在程序任何地方throw一个异常对象,并不要求一定只能是在受到try block监控保护的作用域中才能抛出异常,但如果在程序中出现了抛出的找不到对应catch block的异常对象时,C++标准中规定要求系统必须执行terminate()来终止程序。 因此这个例程是可以编译通过的,但运行时却会异常终止。这往往给软件系统带来了不安全性。与此形成对比的是java中提供的异常处理模型却是不允许出现这样的找不到对应catch block的异常对象,它在编译时就给出错误提示,所以java中提供的异常处理模型往往比C++要更完善,后面的章节会进一步对这两种异常处理模型进行一个详细的分析比较。
语法小结:throw抛出值,catch接受,当然,throw必须在"try语句块"中才有效。
(i)、程序接受到throw语句后就会自动调用析构器,把该域(try后的括号内)对象clean up,然后再进
这种机制会引起一些致命的错误,比如,当"类"有指针成员变量时(又是指针!),在 "类的构建器
"中的throw语句引起的退出,会导致这个指针所指向的对象没有被析构。这里很基础,就不深入了,提
示一下,把指针改为类就行了,比如模板类来代替指针,在模板类的内部设置一个析构函数。
(ii)、语句"throw;"抛出一个无法被捕获的异常,即使是catch(...)也不能捕捉到,这时进入终止函数
问题a:抛出异常,但是catch不到异常怎么办?(注意没有java类似的finally语句)
在catch没有捕获到匹配的异常的时候,会调用默认的终止函数。可以调用set_terminate()来设置终止函数,参数是一个函数指针,类型是:void (*terminate)()。
到这里,可以题个问题:"没有try-catch,直接在程序中"throw;",会怎么样?"
void fun(type1,type2) try----try放在函数体后
void fun () throw(except1,except2,except3)
// 后面括号里面是一个异常参数表,本例中只能抛出这3中异常
void fun () throw() // 参数表为空,不能抛出异常
问题b:假设fun()中抛出了一个不在"异常参数表"中的异常,会怎么样?
答:调用set_terminate()中设定的终止函数。然而,这只是表面现象,实际上是调用默认的unexpected()函数,然而这个默认的unexpected()调用了set_terminate()中设定的终止函数。可以用set_unexpected()来设置unexpected,就像set_terminate()一样的用法,但是在设定了新的"unexpected()"之后,就不会再调用set_terminater中设定的终止函数了。
这个语法是很有用的,因为在用别人的代码时,不知道哪个地方会调用什么函数又会抛出什么异常,用一个异常参数表在申明时限制一下,很实用。
抛出异常(也称为抛弃异常)即检测是否产生异常,在C++中,其采用throw语句来实现,如果检测到产生异常,则抛出异常。该语句的格式为:
如果在try语句块的程序段中(包括在其中调用的函数)发现了异常,且抛弃了该异常,则这个异常就可以被try语句块后的某个catch语句所捕获并处理,捕获和处理的条件是被抛弃的异常的类型与catch语句的异常类型相匹配。由于C++使用数据类型来区分不同的异常,因此在判断异常时,throw语句中的表达式的值就没有实际意义,而表达式的类型就特别重要。
【范例20-2】处理除数为0的异常。该范例将上述除数为0的异常可以用try/catch语句来捕获异常,并使用throw语句来抛出异常,从而实现异常处理
double fuc(double x, double y) //定义函数
cout << "The result of x/y is : " <<res<<endl;
catch (double) //处理异常,捕获到异常后执行此代码
cerr << "error of dividing zero. ";
注意:一般来说,throw语句通常与try- catch或try-finally语句一起使用,可以使用throw语句显式引发异常。
语法小结:throw抛出值,catch接受,当然,throw必须在"try语句块"中才有效。
(i)、程序接受到throw语句后就会自动调用析构器,把该域(try后的括号内)对象clean up,然后再进
这种机制会引起一些致命的错误,比如,当"类"有指针成员变量时(又是指针!),在 "类的构建器
"中的throw语句引起的退出,会导致这个指针所指向的对象没有被析构。这里很基础,就不深入了,提
示一下,把指针改为类就行了,比如模板类来代替指针,在模板类的内部设置一个析构函数。
(ii)、语句"throw;"抛出一个无法被捕获的异常,即使是catch(...)也不能捕捉到,这时进入终止函数
问题a:抛出异常,但是catch不到异常怎么办?(注意没有java类似的finally语句)
在catch没有捕获到匹配的异常的时候,会调用默认的终止函数。可以调用set_terminate()来设置终止函数,参数是一个函数指针,类型是:void (*terminate)()。
到这里,可以题个问题:"没有try-catch,直接在程序中"throw;",会怎么样?"
void fun(type1,type2) try----try放在函数体后
void fun () throw(except1,except2,except3)
// 后面括号里面是一个异常参数表,本例中只能抛出这3中异常
void fun () throw() // 参数表为空,不能抛出异常
问题b:假设fun()中抛出了一个不在"异常参数表"中的异常,会怎么样?
答:调用set_terminate()中设定的终止函数。然而,这只是表面现象,实际上是调用默认的unexpected()函数,然而这个默认的unexpected()调用了set_terminate()中设定的终止函数。可以用set_unexpected()来设置unexpected,就像set_terminate()一样的用法,但是在设定了新的"unexpected()"之后,就不会再调用set_terminater中设定的终止函数了。
这个语法是很有用的,因为在用别人的代码时,不知道哪个地方会调用什么函数又会抛出什么异常,用一个异常参数表在申明时限制一下,很实用。