异常处理

       我们开发的软件一般按照正常的流程操作时运行不会出问题,但是用户不一定会根据软件工程师的想法来操作软件,而且往往随机性很大,另外,软件的运行环境也会改变,例如硬盘空间不足、文件被移走,这些都可能会导致软件出现异常,甚至崩溃。所以我们进行软件开发时要充分考虑异常的捕捉和处理。

       一.异常处理的基本思想

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

       软件开发中往往每个函数都有自己的分工,很多出现错误的函数都不会处理错误,而是产生一个异常让调用者捕捉和处理。如果调用者也不能处理此异常,则异常就会被继续向上级调用者传递,这个传递过程会一直持续到异常能被处理为止。如果程序最终没能处理这个异常,那么它就会被传递给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);
       }
      程序运行结果为:

       9.0的平方根是 3
       发生对负数-1求平方根的异常。
       程序继续运行完毕。

       根据结果可以看出,程序在运行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();

==============================================================================================================================

以上转自http://www.jizhuomi.com/software/127.html。

==============================================================================================================================

    四、标准异常

C++标准库定义了一组类,用于报告在标准库中的函数遇到的问题。标准库异常类定义在四个头文件中:

(1)exception头文件定义了最常见的异常类,它的类名是exception。这个类只通知异常的产生,但不会提供更多的信息。

(2)stdexcept头文件定义了几种常见的异常类。这些类型将在下边列出。

(3)new头文件定义了bad_alloc异常类型,提供因无法分配内存而由new抛出的异常。

(4)type_info头文件定义了bad_cast异常类型。

在头文件中定义的标准异常类
exception最常见的问题
runtime_error运行时错误:仅在运行时才能检测到问题
range_error运行时错误:生成的结果超出了有意义的值域范围
overflow_error运行是错误:计算上溢
underflow_error运行时错误:计算下溢
logic_error逻辑错误:可在运行前检测到的问题
domain_error逻辑错误:参数的结果值不存在
invalid_argument逻辑错误:不合适的参数
length_error逻辑错误:试图生成一个超出该类型最大长度的对象
out_of_range逻辑错误:使用一个超出有效范围的值
标准库异常类只提供很少的操作,包括创建、复制异常类型对象以及异常类型对象的赋值。

exception、bad_alloc以及bad_cast类型只定义了默认构造函数,无法在创建这些类型的对象时为它们提供初值。其他的异常类型则只定义了一个使用string初始化式的构造函数。当需要定义这些异常类型的对象时,必须提供一个string参数。

异常类型只定义了一个名为what的操作,不需要任何参数,并且返回const char*类型值,指向一个C风格字符串数组。这个数组的内容依赖于异常对象的类型。对于接受string初始化式的异常类型,what函数将返回该string作为C风格字符数组。对于其他异常类型,返回的值则根据编译器的变化而不同。

实例如下:

#include<iostream>
#include<string>
#include<stdexcept>

using std::string;
using std::cout;
using std::cin;
using std::endl;
using std::runtime_error;

int main()
{
	int a,b;
	while(cin>>a>>b){
		try{
			if(a>b)
				throw runtime_error("a < b!!");
			cout<<a<<" < "<<b<<endl;
		}catch(runtime_error err){
			cout<<err.what()
				<<"\nTry Again? Enter y or n"<<endl;
			char c;
			cin>>c;
			if(cin && c=='n')
				break;
		}
	}
	cout<<"the end"<<endl;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值