C语言异常处理
程序难免会出错,在有些我们能预测到错误可能会发生的关键点点,我们需要采取措施,一旦错误发生就按照我们的措施执行。比如在C语言里我们采用assert()的方式,采用return返回的方式。看个代码
int Reverse(char* src,char*dst)
{
assert(src && dst);//对可能发生的异常,采取的措施
//处理程序
return 0;
}
int Dev(int a,int b)
{
if (b==0)
{
return -1; //对可能发生的异常,采取的措施
}
else
{
//处理程序
}
}
可以看出以上是C语言的错误处理方式,但是这种方式有几点不足之处。
- 代码不简洁。
- 采用断言会直接使程序崩掉,用户体验太差。
- 异常处理不彻底。
C++异常处理方式
C++引入了异常处理机制。先简单看个示例。
int Dev(int a,int b)
{
if (b==0)
{
throw string ("分母为零"); //抛出异常
}
else
{
return a/b;
}
}
int main(void)
{
try //当该代码块中出现异常就激活特定异常处理措施
{
int d = Dev(10,0);
cout<<d<<endl;
}
catch(const string s)//捕获异常情况,并且把异常反馈给用户
{
cout<<s<<endl;
}
return 0;
}
上面的程序是不是看着感觉瞬间高大上多了。这就是c++的异常处理机制。
C++ 异常处理涉及到三个关键字:try、catch、throw
- throw:当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
- try: try 块中的代码标识将被激活的特定异常。它后面通常跟着一个或多个 catch 块。
- catch: 在你想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获异常。
当我们预测到那些代码可能会发生错误时我们采用try块保护该代码,一旦异常发生便抛出异常 ,再用catch捕获异常反馈给用户。
try
{
//保护代码
}
catch(exception e)
{
//捕获异常,并反馈出来
}
异常抛出和捕获规则
- 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个处理代码。
- 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。
- 抛出异常后会释放局部存储对象,所以被抛出的对象也就还给系统了,throw表达式会初始化一个抛出特殊的异常对象副本(匿名对象),异常对象由编译管理,异常对象在传给对应的catch处理之后撤销。
- 抛出异常的时候,将暂停当前函数的执行,开始查找对应的匹配catch子句。首先检查throw本身是否在catch块内部,如果是再查找匹配的catch语句。如果有匹配的,则处理。没有则退出当前函数栈,继续在调用函数的栈中进行查找。
不断重复上述过程。若到达main函数的栈,依旧没有匹配的,则终止程序。上述这个沿着调用链查找匹配的catch子句的过程称为栈展开。找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行。
异常捕获注意事项
异常对象的类型与catch说明符的类型必须完全匹配。
只有以下几种情况例外
1. 允许从非const对象到const的转换。
2. 允许从派生类型到基类类型的转换。
3. 将数组转换为指向数组类型的指针,将函数转换为指向函数类型的指针。
C++标准异常
C++有一系列的标准异常,有一个异常exception的类,我们可以使用标准的异常,也可以自己重新定义异常类,也可以继承标准异常。看看标准版异常类。
对上图中异常解释:
异常优缺点
优点:
1. 可以清晰的展示出错误原因,不像返回错误码那么模糊
2. 许多第三方库使用异常,因此容易与这些结合使用
3. 在测试框架里使用比较方便
缺点:
1. 会打断执行流,函数有可能不在该返回的地方返回,这样使得代码的管理和调试困难
2. 异常安全要使用RAII和不同编码实践。加大了代码量,需要大量的支持。