异常处理可以让我们把文件的检测和解决问题的过程分离开来。
在C++语言中,我们通过抛出一条表达式来引发一个异常。被抛出的表达式的类型以及当前的调用链共同决定了哪段处理代码将被用来处理该异常。被选中的处理代码是在调用链中与抛出对象类型匹配的最近的处理代码。
当执行一个throw时,跟在throw后面的语句不再执行。并且程序的控制权从throw转移到与之匹配的catch模块。意思就是:
1. 沿着调用链的函数可能会提早退出
2. 程序开始执行异常处理代码时,沿着调用链创建的对象将被销毁。
例子:
#include <iostream>
using namespace std;
#include <cstdio>
class Test
{
public:
Test(int a):_a(a){};
~Test()
{
printf("析构\n");
}
void test()
{
printf("%d\n", _a);
throw 1;
printf("%d\n", _a);
}
private:
int _a;
};
int main()
{
try
{
Test t(3);
t.test();
printf("2");
}
catch(int e)
{
cout<<"error"<<endl;
}
return 0;
}
结果输出:
3
析构
error
这就说明了在try
语句中实例化了t
并执行了t.test()
输出了_a
的值为3,t.test()
中throw
了一个错误1,那么此时test()
函数立马结束了,回到try
语句中,printf("2")
语句也不会被执行并且创建对象t
被提前销毁了,调用析构函数输出了析构
,程序寻找与之匹配的catch,输出了error。
try寻找匹配的catch的过程称为栈展开:
当抛出一个异常后,程序暂停当前函数的执行过程并立即开始寻找
与之匹配的catch语句。当throw出现在一个try语句块中,检查与该try块关联的catch子句。如果找到了匹配的catch,就使用该catch处理异常,如果这一步没找到匹配的catch且该try语句嵌套在其他try块中,则继续检查与外层try匹配的catch子句。如果还是找不到匹配的catch,则退出当前的函数,在调用当前函数的外层函数中继续寻找。栈展开过程沿着嵌套函数的调用链不断查找,直到找到了与异常匹配的catch子句为止:或者也可能一直没找到匹配的catch,则退出主函数后查找过程终止。
一个异常如果没有被捕获,则它将终止当前程序。
同时需要注意的是,如果在try
语句中有申请的内存时,必须在throw
之前把内存释放了,否则会造成内存泄漏。所以我们一般将内存的释放写在析构函数中防止内存的泄漏。
在catch
语句的匹配中,如果参数类型是一个对象,并且throw
语句抛出的是该对象的派生类的话,那么匹配到了之后,catch
语句中只能使用基类的成员,如果抛出的是该对象的基类,那么不会匹配到该catch
语句。
理论上异常会发生在程序执行的任何时刻,如果异常发生在构造函数初始化列表时,则需要进行另外的处理。因为初始化列表发生在执行构造函数之前,意思就是构造函数之前的try
语句还未生效,异常发生了。解决方法的例子如下:
class Test
{
public:
Test(int num)
try:tArray(new int[num])
{
cout << " Constructing " << num << endl;
}
catch(bad_alloc& err)
{
cout<<"failed"<<endl;
}
int *tArray;
};