C++程序设计——九

导引

程序运行时,时常会碰到一些问题,比如:

  • 做除法,除数为0
  • 用户输入年龄时输入了一个负数
  • 使用new运算符动态分配空间时,空间不够导致无法分配
  • 访问数组元素时,下标越界
  • 打开文件读取时,文件不存在

这些问题会导致程序无法正常运行,甚至会导致崩溃。
这些程序在执行期间产生的问题就是异常
出现了异常,我们就需要及时地对其反应并处理。
所谓“处理”,可以是给出错误提示信息,然后让程序沿一条不会出错的路径继续执行;也可能是不得不结束程序,但在结束前做一些必要的工作,如将内存中的数据写入文件、关闭打开的文件、释放动态分配的内存空间等。

一发现异常情况就立即处理未必妥当,因为在一个函数执行过程中发生的异常,在有的情况下由该函数的调用者决定如何处理更加合适。尤其像库函数这类提供给程序员调用,用以完成与具体应用无关的通用功能的函数,执行过程中贸然对异常进行处理,未必符合调用它的程序的需要。

此外,将异常分散在各处进行处理不利于代码的维护,尤其是对于在不同地方发生的同一种异常,都要编写相同的处理代码也是一种不必要的重复和冗余。如果能在发生各种异常时让程序都执行到同一个地方,这个地方能够对异常进行集中处理,则程序就会更容易编写、维护。

这就引出了异常处理机制。其基本思想是:函数 A 在执行过程中发现异常时可以不加处理,而只是“拋出一个异常”给 A 的调用者,假定为函数 B。

拋出异常而不加处理会导致函数 A 立即中止,在这种情况下,函数 B 可以选择捕获 A 拋出的异常进行处理,也可以选择置之不理。如果置之不理,这个异常就会被拋给 B 的调用者,以此类推。

如果一层层的函数都不处理异常,异常最终会被拋给最外层的 main 函数。main 函数应该处理异常。如果main函数也不处理异常,那么程序就会立即异常地中止。

C++异常处理涉及到三个关键字:try,catch,throw。

  • throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
  • catch: 在您想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获异常。
  • try: try 块中的代码标识将被激活的特定异常。它后面通常跟着一个或多个 catch 块。

C++异常处理基本语法

C++ 通过 throw 语句和 try…catch 语句实现对异常的处理。throw 语句的语法如下:

throw 表达式;

该语句拋出一个异常。异常是一个表达式,其值的类型可以是基本类型,也可以是类。

try…catch 语句的语法如下:

try {
    语句组
}
catch(异常类型) {
    异常处理代码
}
...
catch(异常类型) {
    异常处理代码
}

catch可以有多个,但至少要有一个。
不妨把 try 和其后{}中的内容称作“try块”,把 catch 和其后{}中的内容称作“catch块”。

try…catch 语句的执行过程是:

  • 执行 try 块中的语句,如果执行的过程中没有异常拋出,那么执行完后就执行最后一个 catch 块后面的语句,所有 catch 块中的语句都不会被执行;
  • 如果 try 块执行的过程中拋出了异常,那么拋出异常后立即跳转到第一个“异常类型”和拋出的异常类型匹配的 catch 块中执行(称作异常被该 catch 块“捕获”),执行完后再跳转到最后一个 catch 块后面继续执行。
    如下:
int main()
{
    double m ,n;
    cout<<"输入两个整数进行整除:"<<endl;
    cout<<"输入m:";
    cin >> m;
    cout<<endl<<"输入n:";
    cin>>n;
    try {
        cout << "进行整除操作,判断n是否为0" << endl;
        if( n == 0)
            throw -1; //抛出int类型异常
        else
            cout << "n不为0,整除结果为"<<m / n << endl;
        cout << "整除完成。" << endl;
    }
    catch(int e) {
        cout << "捕获到异常,输入的n为0,错误!抛出的异常为整型对象,即" << e << endl;
    }
    cout << "程序结束。" << endl;
    return 0;
}

在这个代码中,当 n 为 0 时,try 块中会拋出一个整型异常。拋出异常后,try 块立即停止执行。该整型异常会被类型匹配(这里为int类型)的catch 块捕获,即进入catch(int e)块执行,该 catch 块执行完毕后,程序继续往后执行,直到正常结束。运行结果如下

输入两个整数进行整除:
输入m:15

输入n:0
进行整除操作,判断n是否为0
捕获到异常,输入的n为0,错误!抛出的异常为整型对象,即-1
程序结束。

当n不为0时,try 块中不会拋出异常。因此程序在 try 块正常执行完后,越过所有的 catch 块继续执行,catch 块一个也不会执行。如下:

输入两个整数进行整除:
输入m:15

输入n:3
进行整除操作,判断n是否为0
n不为0,整除结果为5
整除完成。
程序结束。

如果将catch(int e)改为catch(char e),那么在输入的n为0时,抛出的整型异常就没有catch块能捕获,这个异常也就得不到处理,那么程序就会立即中止,try…catch 后面的内容都不会被执行。

能够捕获任何异常的catch语句

如果我们不需要针对特定类型的异常做出处理,而是希望对任何异常都能捕获,则可以编写如下catch块:

catch(...){
	//处理语句
}

这样的catch能够捕获任何还没有被捕获的异常。如下:

#include <iostream>
using namespace std;

int main()
{
    double m ,n;
    cout<<"输入两个整数进行整除:"<<endl;
    cout<<"输入m:";
    cin >> m;
    cout<<endl<<"输入n:";
    cin>>n;
    try {
        cout << "进行整除操作,判断n是否为0" << endl;
        if( n == 0)
            throw -1; //抛出int类型异常
        else if(m==0)
        	throw -1.0;//抛出double类型的异常
        else
            cout << "n不为0,整除结果为"<<m / n << endl;
        cout << "整除完成。" << endl;
    }
    catch(double d){
    	cout<<"catch(double d)捕获到异常,输入的m为0。"<<endl;
    }
    catch(...) {
        cout << "catch(...)捕获到异常,输入的n为0,错误!" << endl;
    }
    cout << "程序结束。" << endl;
    return 0;
}

结果如下:

输入两个整数进行整除:
输入m:15

输入n:0
进行整除操作,判断n是否为0
catch(...)捕获到异常,输入的n为0,错误!
程序结束。

但在运行catch(…)前会先匹配catch(double d)块,如果不匹配才会转到catch(…)块。由于增加了一条语句

else if (m == 0)
            throw - 1.0;//抛出double类型的异常

所以当m为0时,就会抛出double类型的异常。
虽然catch (double)和catch(…)都能匹配该异常,但是catch(double)是第一个能匹配的 catch 块,因此会执行它,而不会执行catch(…)块。
如下:

输入两个整数进行整除:
输入m:0

输入n:15
进行整除操作,判断n是否为0
catch(double d)捕获到异常,输入的m为0。
程序结束。

综上,由于catch(...)能够匹配任何类型的异常,他后面的catch实际上不会起到任何作用,所以要将他放在最后。

异常的再抛出

如果一个函数在执行过程中拋出的异常在本函数内就被 catch 块捕获并处理,那么该异常就不会拋给这个函数的调用者(也称为“上一层的函数”);如果异常在本函数中没有被处理,则它就会被拋给上一层的函数。例如下面的程序:

#include <iostream>
#include <string>
using namespace std;
class CException
{
public:
    string msg;
    CException(string s) : msg(s) {}
};


double Devide(double x, double y)
{
    if (y == 0)
        throw CException("除数为0,错误!");
    cout << "in Devide" << endl;
    return x / y;
}
int CountTax(int salary)
{
    try {
        if (salary < 0)
            throw - 1;
        cout << "counting tax" << endl;
    }
    catch (int) {
        cout << "salary不能小于0!" << endl;
    }
    cout << "tax counted" << endl;
    return salary * 0.15;
}
int main()
{
    double f = 1.2;
    try {
        CountTax(-1);
        f = Devide(3, 0);
        cout << "end of try block" << endl;
    }
    catch (CException e) {
        cout << e.msg << endl;
    }
    cout << "f = " << f << endl;
    cout << "finished" << endl;
    return 0;
}

在这个程序中,try块调用函数CountTax(),因为传入的参数为-1,抛出异常-1,但CountTax函数本身有能够处理该异常的catch块(不会再抛给调用者),被其捕获后输出:salary不能小于0!;然后输出:tax counted,返回一个salary*0.15的值退出程序。
下面再执行Devide()函数,因为传入的y为0,抛出异常,但该函数没有能够处理该异常的catch块,故返回main函数,有main函数的catch块捕获异常并处理。执行结果如下:

salary不能小于0!
tax counted
除数为0,错误!
f = 1.2
finished

注:如果拋出的异常是派生类的对象,而 catch 块的异常类型是基类,那么这两者也能够匹配,因为派生类对象也是基类对象。

虽然函数也可以通过返回值或者传引用的参数通知调用者发生了异常,但采用这种方式的话,每次调用函数时都要判断是否发生了异常,这在函数被多处调用时比较麻烦。有了异常处理机制,可以将多处函数调用都写在一个 try 块中,任何一处调用发生异常都会被匹配的 catch 块捕获并处理,也就不需要每次调用后都判断是否发生了异常。

有时,虽然在函数中对异常进行了处理,但是还是希望能够通知调用者,以便让调用者知道发生了异常,从而可以作进一步的处理。在 catch 块中拋出异常可以满足这种需要。例如:

#include <iostream>
#include <string>
using namespace std;
int CountTax(int salary)
{
    try {
        if( salary < 0 )
            throw string("zero salary");
        cout << "counting tax" << endl;
    }
    catch (string s ) {
        cout << "CountTax error : " << s << endl;
        throw; //继续抛出捕获的异常
    }
    cout << "tax counted" << endl;
    return salary * 0.15;
}
int main()
{
    double f = 1.2;
    try {
        CountTax(-1);
        cout << "end of try block" << endl;
    }
    catch(string s) {
        cout <<"main函数中捕获到异常:"<<s << endl;
    }
    cout << "finished" << endl;
    return 0;
}

执行结果如下:

CountTax error : zero salary
main函数中捕获到异常:zero salary
finished

在上面这个程序中,虽然函数CountTax()已经抛出了异常,但是仍然设置了继续抛出异常,throw;没有指明拋出什么样的异常,因此拋出的就是 catch 块捕获到的异常,即 string(“zero salary”)。这个异常会被 main 函数中的 catch 块捕获。

函数的异常声明列表

为了增强程序的可读性和可维护性,使程序员在使用一个函数时就能看出这个函数可能会拋出哪些异常,C++ 允许在函数声明和定义时,加上它所能拋出的异常的列表,具体写法如下:

void func() throw (int, double, A, B, C);

或者

void func() throw (int, double, A, B, C){...}

上面的写法表明 func 可能拋出 int 型、double 型以及 A、B、C 三种类型的异常。异常声明列表可以在函数声明时写,也可以在函数定义时写。如果两处都写,则两处应一致。

如果异常声明列表如下编写,则说明 func 函数不会拋出任何异常。

void func() throw ();

反之,一个函数如果不交待能拋出哪些类型的异常,就可以拋出任何类型的异常。

C++标准异常类

C++ 标准库中有一些类代表异常,这些类都是从 exception 类派生而来的:
bad_typeid、bad_cast、bad_alloc、ios_base::failure、out_of_range。

C++ 程序在碰到某些异常时,即使程序中没有写 throw 语句,也会自动拋出上述异常类的对象。这些异常类还都有名为 what 的成员函数,返回字符串形式的异常描述信息。使用这些异常类需要包含头文件 stdexcept。

总结

通过这次实验,我认识到C++异常处理的重要性,对于一个程序来讲,异常能否得到很好地处理关系着他本身的鲁棒性,今后在编写程序时必须注意此事。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值