异常处理
异常:程序执行期间,可检测到的不正常情况。
例如:0作除数;数组下标越界;打开不存在的文件;远程机器连接超时;malloc失败等等。
程序的两种状态:
正常状态和异常状态,发生不正常情况后,进入异常状态,从当前函数开始,按调用链的相反次序,查找处理该异
常的程序片断。
1.throw 表达式
语义:用表达式的值生成一个对象(异常对象),程序进入异常状态。
Terminate函数,终止程序的执行。
2.try-catch语句
try{
包含可能抛出异常的语句;
}catch(类型名 [形参名]){
}catch(类型名 [形参名]){
}
注:1、
指出函数可以抛出的所有异常类型名。
语法:值类型函数名(形参表) throw(类型名表) 函数体
throw(); //为空时,表示没有异常
throw(类型名); // 类型的异常
2、catch(...) 表示捕获所有的异常情况
代码示例:
1 #include <iostream>
2 using namespace std;
3
4 class Error
5 {
6 public:
7 int i;
8 };
9
10 void fun() throw(Error) // 规范只抛出Error异常
11 {
12 Error er;
13 er.i = 1;
14 throw er; // 抛出Error异常
15 }
16 void main()
17 {
18 try{
19 fun();
20 }
21 catch(...) // 捕获所有的异常情况
22 {
23 cout<< "ss" << endl;
24 }
25 }
注意点:【摘】
1.异常处理仅仅通过类型而不是通过值来匹配的,否则又回到了传统的错误处理技术上去了,所以catch块的参数可以没有参数名称,只需要参数类型,除非要使用那个参数。
2.虽然异常对象看上去像局部对象,但并非创建在函数栈上,而是创建在专用的异常栈上,因此它才可以跨接多个函数而传递到上层,否则在栈清空的过程中就会被销毁。
3.函数原型中的异常说明要与实现中的异常说明一致,否则容易引起异常冲突。由于异常处理机制是在运行时有异常时才发挥作用的,因此如果函数的实现中抛出了没有在其异常说明列表中列出的异常,则编译器并不能检查出来。但是当运行时如果真的抛出了这样的异常,就会导致异常冲突。因为你没有提示函数的调用者:该函数会抛出一种没有被说明的即不期望的异常,于是异常处理机制就会检测到这个冲突并调用标准库函数unexcepted(),unexcepted()的默认行为就是调用terminate()来结束程序。
实际工作中使用set_unexcepter()来预设一个回调函数。
4.当异常抛出时局部对象如何释放?
Bjarne Stroustrup引入了“resource acquistion is initialization”思想,异常处理机制保证:所有从try到throw语句之间构造起来的局部对象的析构函数将被自动调用,然后清退堆栈(就像函数正常退出一样)。如果一直上溯到main函数后还没有找到匹配的catch块,那么系统调用terminate()终止整个程序,这种情况下不能保证所有局部对象会被正确地销毁。
5.catch块的参数应采用引用传递而不是值传递,不仅可以提高效率,还可以利用对象的多态性。另外,派生类的异常扑获要放到父类异常扑获的前面,否则,派生类的异常无法被扑获。catch(void *)要放到catch(...)前面。
6.编写异常说明时,要确保派生类成员函数的异常说明和基类成员函数的异常说明一致,即派生类改写的虚函数的异常说明至少要和对应的基类虚函数的异常说明相同,甚至更加严格,更特殊。