一、传统错误的处理方式:
1、终止程序(除数为0)
2、返回一个标识错误的值,附加错误码(GetLasrError)
执行下述代码,若是该文件不存在,会出现错误,
方式1:直接从监视窗口输出错误:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<iostream>
#include<Windows.h>
using namespace std;
int main()
{
FILE* pf = fopen("2.txt", "r");
if (NULL == pf){
size_t errNo = GetLastError();
cout << errNo << endl;
}
//正常操作
//关闭文件
fclose(pf);
system("pause");
return 0;
}
方式2:工具---错误查找---输入错误值---即可查看错误原因
3、返回一个合法值,让程序处于某种非法的状态(坑爹的atoi())
atoi把用户类型的字符串转换为整型的数据,从字符串第一个位置开始依次往后找,直到遇到第一个非字符串的数字终止。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<iostream>
#include<Windows.h>
using namespace std;
int main()
{
cout << "12345" << endl;
cout << atoi("12345" )<< endl;
cout << "123fsg344" << endl;
cout << atoi("123fsg344" )<< endl;
FILE* pf = fopen("2.txt", "r");
if (NULL == pf){
size_t errNo = GetLastError();
cout << errNo << endl;
}
//正常操作
//关闭文件
fclose(pf);
system("pause");
return 0;
}
4、调用一个预先注备好在出现“错误”的情况下用的函数(回调函数)
回调函数:函数是自己写的,但是不一定是自己调用-
5、暴力解决方式:abort()或者exit():代码会终止
6、使用goto语句:事件发生后跳转到某一个位置,根据具体的标签跳到不同的位置(指定的标签的位置),一般不提倡使用goto,打乱了程序正常执行的流程。【共同只能在当前函数作用域中跳转,不能跳出当前函数的作用域】
http://www.baidu.com在C语言中相当于:http:是一个标签; //是一个注释【把要显示的东西当作一个链接来显示,但是在编译阶段被当成语法进行检测,只是一个标签后面跟着一个注释】
7、setjmp()和longjmp():长调转,可以从一个函数里面跳转到另一个函数的位置
把错误处理代码放到一个统一的位置进行处理,把正常代码和错误处理代码划分开了
#include<setjmp.h>
jmp_buf buff;//定义一个结构体类型的变量,必须包含16个整型
void TestFile()
{
FILE* pf = fopen("2.txt", "r");
if (NULL == pf){
longjmp(buff, 1);//跳转到buff位置的1号错误,把第二个参数交给外部的state
}
//正常逻辑操作
fclose(pf);
}
void TestMalloc()
{
int *p = (int*)malloc(sizeof(int)* 10);
if (NULL == p)
longjmp(buff, 2);
//正常的逻辑操作
free(p);
}
int main()
{
//1、返回保存的位置
//2、返回0
int state = setjmp(buff);//第一次调用正常情况下返回0
//把返回值交给state,保留程序执行的表,
//buff保存程序从赋值的位置开始寄存器里面的信息
//调转是跳转到给state赋值的位置
if (0 == state){
TestFile();
TestMalloc();
}
else
{
switch (state)
{
case 1:
cout << "打开文件失败" << endl;
break;
case 2:
cout << "malloc申请空间失败" << endl;
break;
}
}
return 0;
}
setjump必须先调用,在异常位置通过调用longjmp以恢复先前被保存的程序执行点,否则将导致不可预测的结果,甚至程序崩溃 在调用setjmp的函数返回之前调动longjmp,否则结果不可预料
二、c++中对异常的处理,先将异常的地方抛出(用throw关键字抛出异常),并且发生异常的代码要进行捕获。
(1)异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个处 理代码
a、抛出的字符类型的异常,整型不可以捕获到,说明没有进行类型转化,最后会导致程序崩溃。
void TestFile()
{
FILE* pf = fopen("2.txt", "r");
if (NULL == pf)
throw 1;//存在异常就先先抛出类型的异常
//正常操作
//关闭
fclose(pf);
}
void TestMalloc()
{
int *p = (int *)malloc(0x7fffffff);
if (NULL == p)
throw '1';//申请空间失败,抛出字符类型的异常
//正常的操作
//释放
free(p);
}
int main()
{
//对于可能抛出异常的代码,尝试去进行捕捉
try{
TestFile();
TestMalloc();
}
catch (int err)//try必须和catch联合使用,抛出异常后要进行捕获,按照类型进行捕获
//throw 1相当于抛出整型类型异常的1号,此时若是要捕获则捕获整型类型的异常
{
cout << err << endl;
}
system("pause");
return 0;
}
产生申请空进失败(存储空间不足,无法处理此命令)
b、对字符类型的异常也要进行捕获,以下代码会成功
int main()
{
//对于可能抛出异常的代码,尝试去进行捕捉
try{
TestFile();
TestMalloc();
}
catch (int err)//try必须和catch联合使用,抛出异常后要进行捕获,按照类型进行捕获
//throw 1相当于抛出整型类型异常的1号,此时若是要捕获则捕获整型类型的异常
{
cout << err << endl;
}
catch (char err)
{
cout << err << endl;
}
system("pause");
return 0;
}
2、被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最 近的那一个
(1)下面程序对malloc抛出的异常进行了两次捕获,一次在TestMalloc函数里面,一次在main函数里面,
void TestFile()
{
FILE* pf = fopen("2.txt", "r");
if (NULL == pf)
throw 1;//存在异常就先先抛出类型的异常
//正常操作
//关闭
fclose(pf);
}
void TestMalloc()
{
int *p = (int *)malloc(0x7fffffff);
//对抛出的异常进行捕获
try
{
if (NULL == p)
throw '1';
}
catch (char err)
{
cout << err << endl;
}
//正常程序
free(p);
}
int main()
{
try{
TestFile();
TestMalloc();
}
catch (int err)//try必须和catch联合使用,抛出异常后要进行捕获,按照类型进行捕获
//throw 1相当于抛出整型类型异常的1号,此时若是要捕获则捕获整型类型的异常
{
cout << err << endl;
}
catch (char err)
{
cout << err << endl;
}
system("pause");
return 0;
}
先捕获到TestMalloc函数体里面的异常,此时异常捕获到之后,抛出异常,说明异常已经被处理了
(2)在整型类型的捕获中添加对字符类型的捕获,此时不匹配,
void TestFile()
{
FILE* pf = fopen("2.txt", "r");
try{
if (NULL == pf)
throw 1;
}
catch (char err)
{
cout << err << endl;
}
//正常操作
fclose(pf);
}
void TestMalloc()
{
int *p = (int *)malloc(0x7fffffff);
//对抛出的异常进行捕获
try
{
if (NULL == p)
throw '1';
}
catch (char err)
{
cout << err << endl;
}
//正常程序
free(p);
}
int main()
{
try{
TestFile();
TestMalloc();
}
catch (int err)//try必须和catch联合使用,抛出异常后要进行捕获,按照类型进行捕获
//throw 1相当于抛出整型类型异常的1号,此时若是要捕获则捕获整型类型的异常
{
cout << err << endl;
}
catch (char err)
{
cout << err << endl;
}
system("pause");
return 0;
}
此时TestFile函数没有捕获到异常,但是main函数捕获到了异常
3、抛出异常后会释放局部存储对象,所以被抛出的对象也就还给系统了,throw表达式会初始化一个抛出特殊的异常对象副本(匿名对 象),异常对象由编译管理,异常对象在传给对应的catch处理之后撤销
(1)函数抛出异常,说明函数已经返回了,是以一种异常的方式退出的,类似于运行结束,应该将函数中的栈、对象等清理掉
class A
{
public:
A()//构造函数,构造当前this对象
{
cout << "A::A()" << this << endl;
}
A(const A&)//拷贝构造函数,拷贝构造当前this对象
{
cout << "A::A(const A&)" << this << endl;
}
~A()//析构函数,析构当前this对象
{
cout << "A::~A()" << this << endl;
}
};
void TestFile()
{
A a;//若是调用对象a,则说明栈上的对象已经被销毁了
FILE* pf = fopen("2.txt", "r");
if (NULL == pf)
throw 1;
fclose(pf);
}
int main()
{
try{
TestFile();
}
catch (int err)
{
cout << err << endl;
}
catch (char err)
{
cout << err << endl;
}
system("pause");
return 0;
}
说明清理了栈上的空间
(2)副本:将整型异常1换为拷贝构造函数出的对象a
打印外部函数a的地址和主函数中异常抛出的对象的地址,最后一个析构函数析构的是抛出异常对象的副本
void TestFile()
{
A a;//若是调用对象a,则说明栈上的对象已经被销毁了
FILE* pf = fopen("2.txt", "r");
if (NULL == pf)
{
cout << "&a= " << &a << endl;
throw a;
}
fclose(pf);
}
int main()
{
try{
TestFile();
}
catch (const A& ra)
{
cout << "&ra= " << &ra << endl;
}
system("pause");
return 0;
}
三、栈展开
void f1()
{
throw 1;
}
void f2()
{
f1();
}
void f3()
{
f2();
}
int main()
{
try
{
f3();
}
catch (int err)
{
cout << err << endl;
}
return 0;
}
在主函数中就拦截了异常,则异常就不存在了。主函数是最后一次捕获异常的机会,出了主函数就跑到了操作系统中,使操作系统崩溃。
四、异常捕获的匹配规则
异常对象的类型与catch说明符的类型必须完全匹配
(1)允许从非const对象到const的转换
class Exception
{};
void TestFunc()
{
Exception e;
throw e;
}
int main()
{
try
{
TestFunc();
}
catch (const Exception& e)
{
cout << "Exception--->const Exception" << endl;
}
return 0;
}
结果可以进行捕获
(2)允许从派生类型到基类类型的转换
class Exception
{};
class BadAlloc :public Exception
{};
void TestFunc()
{
BadAlloc e;
throw e;
}
int main()
{
try
{
TestFunc();
}
catch (const Exception& e)
{
cout << "Exception--->const Exception" << endl;
}
return 0;
}
结果可以捕获到
(3)将数组转换为指向数组类型的指针,将函数转换为指向函数类型的指针
void TestFunc()
{
int array[10];
throw array;
}
int main()
{
try
{
TestFunc();
}
catch (const Exception& e)
{
cout << "Exception--->const Exception" << endl;
}
catch (int* array)
{
cout << "int[10]--->int *" << endl;
}
return 0;
}
结果可以捕获
抛出函数异常
void Test()
{}
void TestFunc()
{
throw Test;
}
int main()
{
try
{
TestFunc();
}
catch (const Exception& e)
{
cout << "Exception--->const Exception" << endl;
}
catch (int* array)
{
cout << "int[10]--->int *" << endl;
}
catch (void (*p)())
{
p();
}
return 0;
}
五、异常重新抛出(在外部必须捕获里面调用函数的异常)
有可能单个的catch不能完全处理一个异常,在进行一些校正处理以后,希望再交给更外层的调用链函数来处理,catch则可以通过重新抛出将异常传 递给更上层的函数进行处理
1、对于以下代码,如果TestFunc0()存在异常TestFunc1()没有进行捕获,delete语句不会执行,空间不能释放,代码存在内存泄漏。
void TestFunc0()
{
throw 1;
}
void TestFunc1()
{
int *p = new int[10];
//正常操作
TestFunc0();
delete[] p;
}
int main()
{
try
{
TestFunc1();
}
catch (int err)
{
cout << err << endl;
}
return 0;
}
捕获TestFunc0()中的异常,将空间释放掉。不是自己函数中的异常,不用管是什么类型
void TestFunc0()
{
throw 1;
}
void TestFunc1()
{
int *p = new int[10];
//正常操作
try
{
TestFunc0();
}
catch (...)
{
delete[] p;
throw; //函数中没有必要处理的异常,继续抛出
}
}
int main()
{
try
{
TestFunc1();
}
catch (int err)
{
cout << err << endl;
}
return 0;
}
catch (...)
{
delete[] p;
return;
}中异常不用处理,因为不知道具体是什么类型的异常,将异常继续抛出。在主函数中将异常进行捕获
六、异常规范:在c++11中已经取消了
(1)当前函数抛出异常,只能抛出整型和double类型的异常,抛出其他类型的异常,编译器会报错
void TestFunc()throw(int, double)
{
throw 1;
}
一般都会维护一个自己的异常体系结构
class Exception
{
public:
//构造方法
Exception(int errNo, const string& strMsg)
:_errNo(errNo)
, _strMsg(strMsg)
{}
virtual void What() throw()=0;//虚函数在基类不能抛出异常
//等于0是抽象类,不能实例化对象
protected:
int _errNo;
string _strMsg;
};
//网络异常对应的类
class NetException :public Exception
{
public:
NetException(int err, const string& strMsg)
:Exception(err, strMsg)
{}
//对基类中的虚函数进行重写
virtual void What()
{
cout << "服务器端数据包有问题" << _errNo << " " << _strMsg << endl;
}
};
//如果没有错,操作数据库,和数据库中的信息进行对比
class DBException :public Exception
{
public:
DBException(int err, const string& strMsg)
//const string&相比于string类型的好处是,不用创建对象,代码效率较高
:Exception(err, strMsg)
{}
virtual void What()
{
cout << "数据库未打开" << _errNo << " " << _strMsg << endl;
}
};
void TestFunc1()
{
NetException netErr(505, "数据包格式被篡改了");
throw netErr;
}
//操作数据库,数据库可能存在错误
void TestFunc2()
{
DBException dbErr(605, "数据库未打开");
throw dbErr;
}
void TestFunc3()
{
int *p = new int[0x7fffffff / 4];
//
delete[] p;
}
int main()
{
try
{
TestFunc1();
TestFunc2();
TestFunc3();
}
catch (Exception& e)//通过自己的基类捕获异常
{
e.What();
}
catch (const exception& e)//标准库里面的异常
{
cout << typeid(e).name() << endl;
e.what();
}
catch (...)//第三方库(别人写的库中的异常)【别人写的库一般不知道有些什么异常】
{
cout << "未知异常" << endl;
}
system("pause");
return 0;
}
下述代码的复杂程度较高,遇到中途退出的情况都需要关闭文件描述符、释放资源,使代码的结构更复杂。
bool TestFunc1()
{
return false;
}
void TestFunc2()
{
throw 1;//非法情况没有直接返回而是抛出异常
}
void TestFunc()
{
int *p = new int[10];
FILE* pf = fopen("1.txt", "r");
if (NULL == pf)
{
delete[] p;
return;
}
if (!TestFunc1())
{
delete[] p;
fclose(pf);
return;
}
try
{
TestFunc2();//此处抛出异常是为了释放空间,没有必要捕获异常
}
catch (...)
{
delete[] p;
fclose(pf);
throw;
}
//出了函数的作用域释放资源
delete[] p;
fclose(pf);
}
此时我们可以使用资源分配的初始化来让程序自己去控制在不需要资源时,自动将资源归还给系统。
下一篇博客会详细描述。