异常处理

异常处理的基本思想

进行异常处理的目标是,使软件具有容错能力,在出现运行环境或者异常操作等问题时,程序能够继续往下运行,必要时弹出提示信息。

软件开发中往往每个函数都有自己的分工,很多出现错误的函数都不会处理错误,而是产生一个异常让调用者捕捉和处理。

如果调用者也不能处理此异常,则异常就会被继续向上级调用者传递,这个传递过程会一直持续到异常能被处理为止。如果程序最终没能处理这个异常,那么它就会被传递给C++的库函数terminate,然后terminate会调用abort函数终止程序。

C++异常处理的语法

异常处理机制是靠try、throw和catch语句实现的。

throw语句的形式为:

throw 表达式

try块的语法形式为:

try
{
    复合语句
}
catch(异常类型的声明)
{
    复合语句
}
catch(异常类型的声明)
{
    复合语句
}
...

先说说throw语句,当某段程序有了不能处理的异常时,就可以用“throw 表达式”的方式传递这个异常给调用者。这里throw后的表达式在语法上与return后的表达式类似。

再来看try块的try子句,子句后括号里的复合语句就是被监测的程序段。如果某段程序或者调用的某个函数可能会产生异常,就把它放到try后。当try子句后的程序段发生异常时,程序段中throw就会抛出这个异常。

最后来看try的catch子句,catch子句后括号里的异常类型的声明,在语法上与函数的形参类似,可以是某个类型(包括类)的值也可以是引用,它指明了此catch子句用来处理何种类型的异常。

当try子句中的异常抛出后,每个catch子句会被依次检查,哪个catch子句的异常类型的声明与抛出异常的类型一致,就由哪个catch子句来处理此异常。

catch后异常类型的声明部分可以是一个省略号,形式如:catch(...),这种形式的catch子句可以处理任何类型的异常,它只能放到try块所有其他catch语句之后。

可见,如果try监测的某段程序多个地方需要抛出异常,那么throw后应该跟不同类型的表达式来区分,而不应该只通过不同的值区分。

讲了异常处理的语法形式,据此再来说说异常处理的执行过程:
1.程序正常执行到try块的try子句,然后执行try子句后的复合语句,也就是被监测的程序段。

2.如果try子句后的程序段正常执行了,没有发生任何异常,那么此try块的所有catch子句将不被执行,程序直接跳转到整个try块(包括try子句和所有catch子句)后继续执行。

3.如果try子句后的程序段或者此程序段中的任何调用函数发生了异常,并通过throw抛出了这个异常,则此try块的所有catch子句会按其出现的顺序被检查。

若没有找到匹配的处理程序则继续检查外层的try块。如果一直找不到则此过程会继续到最外层的try块被检查。

这里有两种结果:
a.找到了匹配的处理程序,则相应catch子句捕捉异常,将异常对象拷贝给catch的参数,如果此参数是引用则它指向异常对象。

catch的参数被这样初始化以后,此catch子句对应的try子句后的程序段中,从开头到异常抛出位置之间构造的所有对象进行析构,析构顺序与构造顺序相反。然后catch处理程序被执行,最后程序跳转到try块之后的语句执行。

b.始终没有找到匹配的处理程序,则运行C++库函数terminate,而terminate函数调用abort函数终止程序。

给大家举个异常处理的例子,大家知道我们只能对非负实数求平方根,若是负数就应该处理此异常。例程如下:

#include <iostream>
#include "math.h"
using namespace std;
double GetSqrt(double x);       // 求平方根的函数的原型声明
int main()
{
    try
    {
        // 由于求平方根运算有可能出现对负数运算的异常,所以放到try块中
        cout << "9.0的平方根是 " << GetSqrt(9.0) << endl;
        cout << "-1.0的平方根是 " << GetSqrt(-1.0) << endl;
        cout << "16.0的平方根是 " << GetSqrt(16.0) << endl;
    }
    catch(double y)    // 捕捉double型异常
    {
        cout << "发生对负数" << y << "求平方根的异常。" << endl;
    }
    cout << "程序继续运行完毕。" << endl;
    return 0;
}
double GetSqrt(double x)
{
    if (x < 0)
        throw x;     // 如果x为负数,则抛出一个double型异常
    return sqrt(x);
}

在这里插入图片描述
根据结果可以看出,程序在运行cout << "-1.0的平方根是 " << GetSqrt(-1.0) << endl;时GetSqrt函数抛出异常,异常被main函数中catch子句捕捉,输出信息后,程序跳转到main函数最后一句输出"程序继续运行完毕。"。

而try子句后的cout << "16.0的平方根是 " << GetSqrt(16.0) << endl;没有被执行。这是因为异常抛出后会按照catch子句出现的顺序依次检查,当找到匹配的catch处理程序时后面的所有catch子句就被忽略。

根据这个原理,若catch(…)放到前面则其后的所有catch子句就不会被检查,因此它只能放到try块的最后。

其实很多情况下catch子句的处理程序并不需要访问异常对象,只需要声明异常的类型就够了,例如上面程序中的catch子句就可以改成

catch(double)    // 捕捉double型异常
{
    cout << "发生对负数求平方根的异常。" << endl;
}

当然如果需要访问异常对象就要给出参数名,就像上面程序中的catch(double y)。

异常接口声明

我们可以在函数的声明中给出它可能会抛出的所有异常类型。例如:

void func() throw(X, Y);

上面的语句表明函数func能够抛出也只能抛出X、Y及它们子类型的异常。

若函数的声明中没有给出任何异常接口声明,则此函数可能抛出任何类型的异常。例如:

void func();

如果函数不抛出任何类型的异常,则可以这样声明:

void func()  throw();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阳光开朗男孩

你的鼓励是我最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值