程序中的错误分为编译时的错误和运行时的错误。编译时的错误主要是语法错误,比如:句尾没有加分号,括号不匹配,关键字错误等,这类错误比较容易修改,因为编译系统会指出错误在第几行,什么错误。而运行时的错误则不容易修改,因为其中的错误是不可预料的,或者可以预料但无法避免的,比如内存空间不够,或者在调用函数时,出现数组越界等错误。如果对于这些错误没有采取有效的防范措施,那么往往会得不到正确的运行结果,程序不正常终止或严重的会出现死机现象。我们把程序运行时的错误统称为异常,对异常处理称为异常处理。C++中所提供的异常处理机制结构清晰,在一定程度上可以保证程序的健壮性。
C++中处理异常的过程是这样的:在执行程序发生异常,可以不在本函数中处理,而是抛出一个错误信息,把它传递给上一级的函数来解决,上一级解决不了,再传给其上一级,由其上一级处理。如此逐级上传,直到最高一级还无法处理的话,运行系统会自动调用系统函数terminate,由它调用abort终止程序。这样的异常处理方法使得异常引发和处理机制分离,而不在同一个函数中处理。这使得底层函数只需要解决实际的任务,而不必过多考虑对异常的处理,而把异常处理的任务交给上一层函数去处理。
C++的异常处理机制有3部分组成:try(检查),throw(抛出),catch(捕获)。把需要检查的语句放在try模块中,检查语句发生错误,throw抛出异常,发出错误信息,由catch来捕获异常信息,并加以处理。一般throw抛出的异常要和catch所捕获的异常类型所匹配。异常处理的一般格式为:
try
{
被检查语句
throw 异常
}
catch(异常类型1)
{
进行异常处理的语句1
}
catch(异常类型2)
{
进行异常处理的语句2
}
...
下面我们用示例演示一下异常处理:
#include "stdafx.h"
#include <iostream>
template <typename T>
T Div(T x,T y)
{
if(y==0)
throw y;//抛出异常
return x/y;
}
int main()
{
int x=5,y=0;
double x1=5.5,y1=0.0;
try
{
//被检查的语句
std::cout<<x<<"/"<<y<<"="<<Div(x,y)<<std::endl;
std::cout<<x1<<"/"<<y1<<"="<<Div(x1,y1)<<std::endl;
}
catch(int)//异常类型
{
std::cout<<"除数为0,计算错误!"<<std::endl;//异常处理语句
}
catch(double)//异常类型
{
std::cout<<"除数为0.0,计算错误!"<<std::endl;//异常处理语句
}
return0;
}
结果:
看了上述的示例代码,也许有人会问,第二个双精度类型的除法计算也应该抛出异常才对啊,在实际的运行过程中并非如此,其实该双精度类型除法函数根本没有被执行过。以上程序的执行规程为:调用函数Div(x,y)时发生异常,由函数Div中的语句"throw y"抛出异常,并不在往下执行return x/y,接着catch捕获int类型的异常并处理异常,最后直接执行"return 0"。因此函数Div(x1,y1)和catch(double){}模块根本没有被执行。如果,我们把y的值改为1,则结果就变成为:
如果在执行try语句模块时,没有发生异常,则catch语句块不起作用,流程转到其后的语句继续执行。从上述两个结果中可知第一次throw抛出的int类型所以找到处理该类型的catch,而第二次是抛出double类型所找到的是处理double类型的catch。
下面对异常处理补充几点:(1)try和catch块中必须要用花括号括起来,即使花括号内只有一个语句也不能省略花括号;(2)try和catch必须成对出现,一个try_catch结果中只能有一个try块,但可以有多个catch块,以便与不同的异常信息匹配;(3)如果在catch块中没有指定异常信息的类型,而用删节号"...",则表示它可以捕获任何类型的异常信息;(4)如果throw不包括任何表达式,表示它把当前正在处理的异常信息再次抛出,传给其上一层的catch来处理;(5)C++中一旦抛出一个异常,如果程序没有任何的捕获,那么系统将会自动调用一个系统函数terminate,由它调用abort终止程序;
最后还是一样,我将用一个示例来总结一下今天所讲的内容(开发工具:vs2010):
#include "stdafx.h"
#include <iostream>
template <typename T>
T Div(T x,T y)
{
if(y==0)
throw y;//抛出异常
return x/y;
}
int main()
{
int x=5,y=1;
double x1=5.5,y1=0.0;
try
{
//被检查的语句
std::cout<<x<<"/"<<y<<"="<<Div(x,y)<<std::endl;
std::cout<<x1<<"/"<<y1<<"="<<Div(x1,y1)<<std::endl;
}
catch(...)//捕获任意类型异常
{
try
{
std::cout<<"任意类型异常!"<<std::endl;
throw;//抛出当前处理异常信息给上一层catch
}
catch(int)//异常类型
{
std::cout<<"除数为0,计算错误!"<<std::endl;//异常处理语句
}
catch(double)//异常类型
{
std::cout<<"除数为0.0,计算错误!"<<std::endl;//异常处理语句
}
}
return0;
}
结果:
http://www.cnblogs.com/CaiNiaoZJ/archive/2011/08/19/2145349.html
附:
google 的c++异常的观点
不要使用C++异常。
优点: 1) 异常允许上局应用决定如何处理在底局嵌套函数中収生的“不可能发生”的失败,不像出错代码的记彔那么模糊费解;
2) 应用亍其他徆多现代语言中,引入异常使得C++与Python、Java及其他与C++相近的语言更加兼容;
3) 许多C++第三方库使用异常,关闭异常将导致难以与之结合;
4) 异常是解决构造函数失败的唯一方案,虽然可以通过工厂函数(factory function)或Init()方法模拟异常,但他们分别需要堆分配新的“非法”状态;
5) 在测试框架(testing framework)中,异常确实很好用。
缺点: 1) 在现有函数中添加throw语句时,必须检查所有调用处,即使它们至少具有基本的异常安全保护,或者程序正常结束,永迖不可能捕获该异常。
例如:如果f()依次调用了g()和h(),h抛出被f捕获的异常,g就要当心了,避免没有完全清理;
2) 通俗一点说,异常会导致程序控制流(control flow)通过查看代码无法确定:函数有可能在不确定的地方返回,从而导致代码管理和调试困难,当然,你可以通过规定何时何地如何使用异常来最小化的降低开销,却给开发人员带来掌撑这些规定的负担;
3) 异常安全需要RAII和不同编码实践。轻松、正确编写异常安全代码需要大量支撑。允许使用异常;
4) 加入异常使二进制执行代码体积变大,增加了编译时长(或许影响不大),还可能增加地址空间压力;
5) 异常的实用性可能会刺激开发人员在不恰当的时候抛出异常,或者在不安全的地方从异常中恢复,例如,非法用户输入可能导致抛出异常。如果允许使用异常会使得返样一篇编程风格指南长出很多(译者注,返个理由有点牵强)
结论: 从表面上看,使用异常利大亍弊,尤其是在新项目中,然而,对亍现有代码,引入异常会牵还到所有依赖代码。如果允许异常在新项目中使用,在跟以前没有使用异常的代码整合时也是一个麻烦。因为Google现有的大多数C++代码都没有异常处理,引入带有异常处理的新代码相当困难。 鉴于Google现有代码不接受异常,在现有代码中使用异常比在新项目中使用的代价多少要大一点,迁移过程会比较慢,也容易出错。我们也不相信异常的有效替代方案,如错误代码、断言等,都是严重负担。 我们并不是基于哲学或道德局面反对使用异常,而是在实践的基础上。因为我们希望使用Google上的开源项目,但项目中使用异常会为此带来不便,因为我们也建议不要在Google上的开源项目中使用异常,如果我们需要把返些项目推倒重来显然不太现实。 对于Windows代码来说,这一点有个例外(等到最后一篇吧:D)。 注:对于异常处理,显然不是短短几句话能够说清楚的,以构造函数为例,很多C++书籍上都提到当构造失败时只有异常可以处理,Google禁止使用异常返一点,仅仅是为了自身的方便,说大了,无非是基亍软件管理成本上,实际使用中还是自己决定。
四、一些额外的c++与java的比较
- C++可以抛出所有类型的对象,而java只能抛出Throwable的子类
- java中若要抛出异常必须在函数头部声明,而C++中只是推荐采用此方式
- java在catch模块中必须显式的制定一个异常参数对象,而C++不是必须的
五、C与C++的堆栈解退比较
假设都是在main函数中递归调用函数f(int),f(3)会导致异常
此为C:
此为C++: