1.异常的概念及其使用
1.1 异常的概念
异常是一种用于处理错误的机制,它允许程序在检查到错误条件时,能够从一个代码块转到另一个代码块,以处理改错误,而不是直接崩溃返回不确定的结果。
C++的异常处理机制依赖于三个关键字:try(),throw(抛出),catch(捕获)
1.2 异常的抛出和捕获
1.程序出现问题的时候我们会抛出一个对象来引发一个异常,该对象的类型以及当前的调用链决定了应该由哪个catch的处理代码来处理该异常
2.被选中的处理代码是调用链中与该对象类型匹配,并且离抛出异常位置最近的那一个,根据抛出对象的类型和内容,程序抛出异常的那部分会告知异常处理部分到底发生了什么错误。
3.当throw执行后,throw后面的语句将不再执行,程序从throw执行的位置跳到与之匹配的catch位置,catch可能是同一个函数局部的catch,也可能是调用链中另一个函数的catch,控制权从throw位置转移到了catch位置。
这里有两种重要的含义:
(1)沿着调用链的函数会提前退出。
(调用链就是多重函数的调用,例如函数1调用函数2,函数2调用函数3,提前退出的意思是如果函数3发生异常抛出后,函数3没有catch,但函数2有,就交给函数2catch)
(2)一旦程序开始执行异常处理程序,沿着调用链创建的函数都会销毁。
4.抛出异常对象后,会生成一个异常对象的拷贝(因为抛出的异常对象可能是一个局部对象,根据上面可知,这样就可以把函数3的异常传给函数2,从而达到让函数2catch的作用)
1.3 栈展开
1.抛出异常后,程序暂停当前函数的执行,开始寻找与之匹配的catch子句,首先检查throw本身是否在try块内部,如果在则查找匹配的catch语句,如果在则查找匹配的catch语句,如果有匹配的,则跳到catch的地方进行处理。
2.如果当前函数没有try/catch子句,或有try/catch子句但类型不匹配,则退出当前函数,继续在函数的调用链寻找catch的过程称为栈展开。
3.如果到达main函数,依旧没有找到匹配的catch子句后,程序会调用terminate函数终止程序。
4.如果找到匹配的catch子句处理后,catch子句代码会继续执行
以下为代码的示例:
double Divide(int a,int b)
{
try
{
if(b==0)
{
string s("Divide by zer0 condition") ;
throw s;
}
else
{
return((double)a/(double)b);
}
}
catch(int errid)
{
cout << errid << endl;
}
}void Func()
{
int len,time;
cin>>len>>time;
try
{
cout << Divide(len,time)<<endl;
}
catch(const char*errmsg)
{
cout<<errmsg<<endl;
}
cout <<__FUNCTION__<<":" << __LINE__ << "⾏执⾏" << endl;
}
int main()
{
while(1){
try{
Func();
}
catch(const string& errmsg)
{
cout << errmsg << endl;
}
}
return 0;
}
上面代码从divide到func()最后到main()就是一个调用链
1.4 查找匹配的处理代码
1.如果抛出对象和多个catch的类型是匹配的,选择离抛出对象位置最近的那一个。
2.但是也有一些例外,就是权限缩小,例如:允许从非常量向常量的类型转换,允许数组转换成指向数组元素类型的指针等
3.如果到了main函数时依然没有找到合适的catch函数此时又不希望程序终止,此时在main函数中会使用catch(...),它可以捕捉任意类型的异常从而避免程序的终止,但有一个缺点就是不知道异常错误是什么。
1.5 异常重新抛出
有时catch到一个异常对象后,需要对错误进行分类,其中的某种错误需要进行特殊的处理,其他错误则重新抛出异常给外层调用链处理。捕获异常后需要重新抛出,直接throw,就可以把捕获的对象直接抛出。
1.6 异常安全问题
异常抛出后,后面的代码就不再执行了,这就会导致有些前面申请了的资源到后面就无法被释放从而导致了资源资源的泄漏,而此时我们要做到捕获异常,释放资源后再重新抛出。
下面的代码为一个示例:
double Divide(int a,int b)
{
if(b==0){
throw "Division by zero condition!";
}
return (double)a/(double)b;
}
void Func()
{
int* array = new int[10];try
{
int len,time;
cin>>len>>time;
cout<<Divide(len,time)<<endl;
}
catch(...)
{
cout <<"delete []" <<endl;
delete[] array;
throw;
}
cout<<"delete []" << array <<endl;
delete[] array;
}
int main()
{
try
{
Func();}
catch(const char*errmsg)
{
cout<< errmsg <<endl;}
catch(const exception&e)
{
cout<< e.what()<<endl;
}
catch(...)
{
cout << "Unkown Exception" << endl;}
return 0;
}
1.7 异常规范
1.对于用户和编译器而言,预先知道程序是否会抛出会助于简化调用函数的代码
2.在C++ 11中函数列表后面加noexcept表示不会抛出异常吗,啥都不加就可能会抛出异常
3.加了noexcept保证不抛出异常,如果抛出异常了程序就会调用terminate终止程序
double Divide(int a,int b)noexcept
{
if(b==0)
{
throw"Diviaion by zero condition!";
}
return(double)a/(double)b;
}
2.标准库的异常
C++标准库定义了自己的一套异常继承体系库,基类是exception,所以我们日常写程序时,需要在主函数捕获exception即可,要获取异常信息,调用what函数,what是一个虚函数。
以下代码为C++标准库的异常的一套例子:
#include<iostream>
#include<exception>
using namespace std;
class arg_error :public exception
{
public:
arg_error()
:exception("第二个参数错误 不能为0", 1)
{}
};
int add(int a, int b)
{
if (b == 0)
{
arg_error arg;
throw arg;
}
return a / b;
}
int main(void)
{
int a, b, res;
cout << "请输入要除的两个数" << endl;
cin >> a >> b;
try{
res = add(a, b);
cout << res<< endl;
}
catch (exception & p)
{
cout << p.what();
}
return 0;
}