在介绍C++异常处理之前先回顾一下在C语言中我们是如何来处理异常的。
1、终止程序(除数为零)
2、返回错误值(errno,GetLastError()获取系统出现的最近的错误码)
3、返回合法值,让程序处于某种非法状态(atoi函数)
4、调用程序预先准备好的在出现错误时用的函数(回调函数)
5、直接退出,暴力解决(abort(),exit()函数)
6、使用goto语句
7、setjmp()与longjmp()结合
6、7可以看一下如下代码:
#include<iostream>
using namespace std;
int Div(int left,int right)
{
int ret = 0;
if(right == 0)
goto R;
http://www.baidu.com; //此处http也为标签,所以不会出现编译错误
ret = left /right;
R: return ret;
}
int main()
{
cout<<Div(10,0)<<endl;//现在除数为0会返回ret的初值0
system("pause");
return 0;
}
#include<iostream>
using namespace std;
#include<setjmp.h>
jmp_buf buf;
void FunTest1()
{
longjmp(buf, 1);
}
void FunTest2()
{
longjmp(buf, 2);
}
int main()
{
int iState = setjmp(buf);//setjmp初值为0,应注意 setjmp应先调用,并且在调用setjmp的函数返回之前调用longjmp
if(iState == 0)
{
FunTest1();
FunTest2();
}
else
{
switch(iState)
{
case 1:
cout<<"FunTest1()"<<endl;
break;
case 2:
cout<<"FunTest2()"<<endl;
break;
}
}
system("pause");
return 0;
}
接下来我们看看C++是如何处理异常的
异常的抛出和捕获
1、异常是通过抛出对象引发的,该对象类型决定了该激活哪个处理代码
2、被选中的处理代码是调用链中与该对象类型匹配,且离抛出异常最近的那个
3、抛出异常后会释放局部存储对象,所以被抛出的对象也就还给系统了,throw表达式会初始化一个抛出特殊的异常对象副本,异常对象由编译管理,异常对象再传给对应的catch处理之后撤销
void FunTest()
{
FILE *fp = fopen("1.txt", "rb");
if(NULL == fp)//打开文件失败
{
char err = 2;
cout<<(int*)&err<<endl;//err的地址004AFA27
throw err;//抛出特殊的异常对象副本
}//若FunTest()中有catch块就在FunTest中直接捕获
}
int main()
{
try
{
FunTest();
}
catch(char& err1)//按类型捕获,但并不是FunTest中throw的err
{
cout<<(int*)&err1<<endl;//err1的地址004AF95B,与err不同
}
system("pause");
return 0;
}
栈展开
我们先看下面一段代码
void FunTest1()
{
FILE *fp;
try
{
fp = fopen("1.txt","rb");
if(NULL == fp)
{
int err = 1;
cout<<(int*)&err<<endl;
throw err;
}
}
catch(char err)
{
return;
}
fclose(fp);
}
void FunTest2()
{
int* p =(int*)malloc(0x7fffffff);
if(NULL == p)
{
throw 2;
}
free(p);
}
void FunTest3()
{
int* p = new int[10];
try
{
FunTest1();
}
catch(int err)
{
delete[] p;
throw;
}
}
int main()
{
try
{
FunTest1();
FunTest2();
FunTest3();
}
catch(int& err1)
{
cout<<(int*)&err1<<endl;
switch(err1)
{
case 1:
cout<<"打开文件失败"<<endl;
case 2:
cout<<"申请空间失败"<<endl;
}
}
catch(char err1)
{}
catch(double err1)
{}
catch(...)
{
cout<<"未知错误"<<endl;
}
system("pause");
return 0;
}
在抛出异常的时候,会暂停当前函数的执行,开始查找对应的匹配catch子句。
首先检查throw本身是否在try块内,如果是再查找匹配的catch语句。若有匹配的则处理。
像上面示例的程序中throw在try块内,但当前函数中没有匹配的catch。程序会退出当前函数栈,在调用函数(FunTest3)的栈中进行查找,在FunTest3中虽然捕获到了FunTest的错误,但捕获后它又重新抛出,最后只能在主函数中寻找,找到对应catch。若到达main函数的栈依然没有匹配的,就会终止程序。
所以把沿着调用链查找匹配的catch子句的过程叫做栈展开。如下图。
异常捕获一些规则
1、允许非const到const转化
2、允许派生类到基类转化(派生类是一个基类 is-a)
3、允许数组到指针转化
4、允许函数到函数指针的转化
void FunTest1()
{
int err = 1;
throw err;
}
class B
{};
class C:public B
{};
void FunTest2()
{
throw C();
}
void FunTest3()
{
int array[10];
throw array;
}
void FunTest5()
{
cout<<"FunTest5()"<<endl;
}
void FunTest4()
{
throw FunTest5;
}
int main()
{
try
{
FunTest4();
FunTest3();
FunTest2();
FunTest1();
}
catch(const int& err)
{
cout<<err<<endl;
}
catch(B& err)
{
cout<<"B()"<<endl;
}
catch(int* err)
{
cout<<"int*"<<endl;
}
catch(void(*p)())
{
p();
}
return 0;
}
注:
构造函数不能抛出异常,因为构造函数完成对象的构造和初始化,若抛出异常可能导致对象不完整或没有完全初始化
析构函数也不能抛出异常,因为析构函数完成资源的清理,若在此抛出异常可能导致内存泄露。
自定义异常类:
#include<iostream>
#include<string>
using namespace std;
////////////////////////////////////////////顶层类 Exception///////////////////////
class Exception
{
public:
Exception(int errID, string strErrCode)
: _errID(errID)
, _strErrCode(strErrCode)
{}
virtual void What()const = 0;
protected:
int _errID;
string _strErrCode;
};
class DBException:public Exception
{
//
public:
DBException(int errID, string strErrCode)
: Exception(errID, strErrCode)
{}
virtual void What()const
{
cout<<"错误码:"<<_errID<<endl;
cout<<"描述符: "<<_strErrCode<<endl;
}
};
class NetException:public Exception
{
//
public:
NetException(int errID, string strErrCode)
: Exception(errID, strErrCode)
{}
virtual void What()const
{
cout<<"错误码:"<<_errID<<endl;
cout<<"描述符: "<<_strErrCode<<endl;
}
};
// 数据库
//
void FunTest1()
{
DBException db(1, "插入数据失败");
throw db;
}
void FunTest2()
{
NetException ne(2, "网络异常");
throw ne;
}
int main()
{
try
{
FunTest1();
FunTest2();
}
catch(const Exception& e)
{
cout<<typeid(e).name()<<endl;
e.What();
}
catch(const exception& e)
{
cout<<typeid(e).name()<<endl;
e.what();
}
catch(...)
{
cout<<"未知异常!"<<endl;
}
system("pause");
return 0;
}