程序消亡的三种形式:无疾而终(程序正常运行结束)、他杀(系统对异常进行处理使程序终止)、自杀(在程序里自己设置的对异常处理使程序运行终止)。
关于C++处理异常的机制是由三部分组成的,即检查(try)、抛出(throw)和捕捉(catch)。把可能出现异常的语句放在try块中,throw是用来异常发出,(抛出一个异常信息)。而catch则是用来捕获异常,如果捕获到了异常就可以处理它(catch是按照类型来捕获的)
如例1、:
void FunTest()
{
int *p = (int*)malloc(0x7fffffff);
try
{
if (p == NULL)
{
throw 1;
}
}
catch (int err)
{
cout << "FunTest()"
}
}
int main()
{
FunTest();
system("pause");
return 0;
}
在执行判断申请空间时会出现如下异常
因此我们捕获这个异常,我们throw的是整型因此在捕获的时候我们也需要捕获整型的异常。
例2:
void FunTest()
{
int *p = (int*)malloc(0x7fffffff);
try
{
if (p == NULL)
{
throw 1;
}
}
catch (int err)
{
cout << "FunTest()";
}
}
若我们将catch中的参数改成字符类型的,则会发生不匹配,也就会捕捉不到这个异常,程序就会崩溃。
例3:
void FunTest()
{
int *p = (int*)malloc(0x7fffffff);
try
{
if (p == NULL)
{
int err = 1;
cout << &err << endl;
throw err;
}
}
catch (int& err)
{
cout << &err << endl;
cout << "FunTest()";
}
}
看它们的打印地址的结果:
抛出异常后会释放局部存储对象,所以被抛出的对象也就还给系统了,throw表达式会初始化一个 抛出特殊的异常对象副本(匿名对象),异常对象由编译管理,异常对象在传给对应的catch处理之 后撤销。
例4:
void FunTest1()
{
int *p = (int*)malloc(0x7fffffff);
try
{
if (p == NULL)
{
int err = 1;
cout << &err << endl;
throw err;
}
}
catch (char& err)
{
cout << &err << endl;
cout << "FunTest()";
}
}
void FunTest2()
{
try
{
FunTest1();
}
catch (char& err)
{
cout << &err << endl;
cout << "FunTest()";
}
}
int main()
{
try
{
FunTest2();
}
catch (int err)
{
cout << "FunTest1()";
}
system("pause");
return 0;
}
上述例子实际上就是栈展开
例5、
class A{};
class B :public A{};
void FunTest1()
{
int err = 1;
throw err;
}
void FunTest2()
{
throw B();//抛出一个派生类无名对象
}
void FunTest3()
{
int array[10];
throw array;
}
void FunTest()
{
cout << "FunTest()" << endl;
}
void FunTest4()
{
throw FunTest;
}
int main()
{
try
{
FunTest1();
FunTest2();
FunTest3();
FunTest4();
}
catch (const int&err)
{
cout << "FunTest1()" << endl;
cout << typeid(err).name() << endl;
}
catch (A &err)//捕获基类类型
{
cout << "FunTest2()" << endl;
cout << typeid(err).name() << endl;
}
catch (int* err)
{
cout << "FunTest3()" << endl;
cout << typeid(err).name() << endl;
}
catch (void (*err)())
{
(*err)();
cout << typeid(err).name() << endl;
}
system("pause");
return 0;
}
异常对象的类型与catch说明符的类型必须完全匹配。只有以下几种情况例外
1. 允许从非const对象到const的转换。
2. 允许从派生类型到基类类型的转换。
3. 将数组转换为指向数组类型的指针,将函数转换为指向函数类型的指针
1和3的情况和我们曾在函数模板里类型的转换原理相同
异常的重新抛出
有可能单个的catch不能完全处理一个异常,在进行一些校正处理以后,希望再交给更外层的调用链 函数来处理,catch则可以通过重新抛出将异常传递给更上层的函数进行处理。
例6、
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<stdlib.h>
using namespace std;
void FunTest1()
{
FILE*fp = fopen("1.txt", "r");
try
{
if (fp == NULL)
{
int err = 1;
throw err;
}
}
catch (int &err)
{
cout << "this is a error"<<endl;//在这里捕捉到异常之后,打印一句话
throw;//将异常再次抛出
}
fclose(fp);
}
void FunTest2()
{
try
{
FunTest1();
}
catch (int &err)
{
cout << "again catch error" << endl;
}
}
catch(…)
通常用来捕获未知异常
异常规范
1、成员函数在类内声明和类外定义两处必须有相同的异常规范
class A
{
void FunTest1()throw(int);
};
void A::FunTest1()
{
throw '1';
}
int main()
{
system("pause");
return 0;
}
在VS2013的集成开发环境下对这个程序进行编译
会发现程序会编译通过只有一个警告,客观来讲这个编译应该是不能通过的因为在类里声明的抛出异常的类型和在类外定义的不一致,但由于编译器的原因编译通过了
我们可以在Linux下测试看这个程序是否能够编译通过
很明显在Linux下这个程序是无法编译通过的。
2、、函数抛出一个没有被列在它异常规范中的异常时(且函数中抛出异常没有在函数内部进行处理), 系统调用C+ +标准库中定义的函数unexpected( ).
3、
void FunTest()throw()
{
}
int main()
{
FunTest();
system("pause");
return 0;
}
4、派生类的虚函数的异常规范必须与基类虚函数的异常规范一样或更严格(是基类虚函数的异常 的子集)。因为:派生类的虚函数被指向基类类型的指针调用时,保证不会违背基类成员函数的异常 规范。
class A
{
public:
virtual void FunTest1()throw(int);
};
class B:public A
{
public:
virtual void FunTest1()throw(int, double)
{
throw 1.0;
}
};
void A::FunTest1()throw(int)
{
throw 1;
}
这里的重写也是为了实现多态,既可以调用基类的抛出的异常,也可以调用派生类的。
简单剖析为什么不能在构造函数和析构函数里面抛异常
构造函数的作用:构造函数为了完成对象的创建和初始化,如果我们在构造函数里抛出一个异常,那么对象可能还没有被创建完成或者初始化完成,就会导致对象不完整,因此不能在构造函数里面抛出一个异常。
析构函数的作用:析构函数是为了完成对资源的清理,如果我们在析构函数里面抛出一个异常,那么很可能就会导致资源还没被清理完,造成内存的泄露。