异常处理

在程序运行过程中,有些错误是可以预料,但不可回避的,如:

内存空间不足,外存文件被移动到其他驱动,io设备未处理好等系统运行环境造成的错误。

异常存在于程序的正常功能之外,一般要求程序立即处理。因此,需要考虑一种方法做到部分功能可以允许用户排除环境错误,继续运行程序,部分功能可以给出适当的同时信息。c++的异常处理机制,使得异常的引发和处理不必在同一个函数当中,这样底层的函数可以着重于解决具体问题,不需要过多考虑对异常的处理,而上层的调用者可以在适当的位置设置对不同类型异常的处理,即异常引发和处理不在同一个函数,下层解决问题,上层处理各种异常。异常处理机制提供程序中错误检测与错误处理部分之间的通信。

c++中使用throw(抛出)表达式引发(抛出)异常条件,并以try catch语句块处理异常,try catch语句块以try(检测)开始,并以一个或多个catch(捕获)结束。在try块中执行的代码所抛出的异常,通常由其中的一个catch子句进行处理。

抛出异常的基本格式为:throw 异常数据

异常数据可以包含任意的信息,可以是int、float、bool等基本类型,也可以是指针、数组、字符串、结构体、类等聚合类型,异常数据是什么类型,就抛出什么类型的异常。c++标准库当中还定义了一组异常类exception,专门用于在throw和try块之间传递错误信息。在try块中执行的代码所抛出的异常,通常由其中的一个catch子句进行处理。
try catch语句表达式的基本格式如下:

try{要执行的程序;}
catch(异常类型1 表达式1){该类异常对应的处理;}
catch(异常类型2 表达式2){该类异常对应的处理;}

catch(异常类名n 表达式n){该类异常对应的处理;}

异常是一份数据,所以有数据类型。当异常发生后,会将异常数据传递给异常变量,这和函数传参的过程类似,只有跟异常类型匹配的异常数据才会被传递给异常变量,否则catch不会接收这份异常数据,也不会执行catch块中的语句。因此可以将catch看做一个没有返回值的函数,当异常发生后catch会被调用,并且会接收实参(异常数据)。但对于catch,异常是在运行阶段产生的,所以不能在编译阶段判断类型是否正确,只有在运行阶段才能将实参和形参匹配(动态绑定)。发生异常时必须将异常明确地抛出,try才能检测到;如果不抛出,即使有异常try也检无法检测。检测到异常后程序的执行流会发生跳转,从异常点跳转到catch所在的位置,位于异常点之后的、并且在当前try块内的语句都将不再有机会执行。即使catch语句成功地处理了错误,程序的执行流也不会再回退到异常点,程序会继续执行catch块后面的代码,恢复正常的执行流。
常见的标准异常有四类

exception头文件:最常见的异常类,类名为:exception 只通知异常不提供其他信息
stdexcept:定义了集中常见的异常类
new:头文件定义了bad_alloc异常类型,提供因无法分配内存(分配出错)而由new抛出的异常
type_info:定义了bad_cast异常类,(错误的类型转换异常)

异常处理示例程序1:

#include<iostream>
using namespace std;
class Item
{
    int isbn;
    friend istream &operator>>(istream &ii,Item &i)
    {
        ii>>i.isbn;
        return ii;
    }
    friend int &operator+(Item i1,Item i2)
    {
        static int sum;
        sum+=i1.isbn+i2.isbn;
        return sum;
    }
    friend int sameisbn(Item i1,Item i2)
    {
        if(i1.isbn==i2.isbn)return 1;
        return 0;
    }
};
int main()
{
    Item item1,item2;
    while(cin>>item1>>item2)
    {
        try
        {
            if(!sameisbn(item1,item2))
            throw runtime_error("err:必须是同一个isbn\n");            //runtime_error是异常类exception的派生类,表示运行时异常
            else cout<<"两次销售之和:"<<item1+item2;                 //其中传入参数代表异常所描述的异常描述信息,是异常类的数据成员
        }
        catch(const runtime_error err)
        {
            char c;
            cout<<err.what()<<"是否需要重新输入?"<<endl;              //what()是异常类的成员函数,返回字符串形式的异常描述信息
            cin>>c;
            if(c=='\0')break;
        }
    }
}

异常处理2:多层异常处理

#include<iostream>
using namespace std;
void fun1(),fun2(),fun3();
void fun1()
{
    try{
        fun2();
    }
    catch(int){cout<<"第1层处理"<<endl;}                //捕获fun2()中的int异常
    cout<<"fun1结束"<<endl;                            //由于已经没有抛出的异常,因此fun1()中的输出语句会执行
}
void fun2()
{
    int a;
    try{
        fun3();
    }
    catch(int){cout<<"第2层处理"<<endl;throw a;}        //捕获fun3()中的char异常,同理,抛出int类型异常,可以在其上层的函数fun1()中捕获
    cout<<"fun2结束"<<endl;                            //此句不会执行
}
void fun3()
{
    double a;int b;char c;
    try{
        throw a;
        throw b;
        throw c;
    }                                                       //此处虽然抛出三个异常,但只有double类型的a(异常变量)可以在fun3()中捕获
    catch(double){cout<<"第3层处理"<<endl;throw b;throw c;}  //处理完double类型异常后数抛出char类型异常,可以在其上层的函数fun2()中捕获
    cout<<"fun3结束"<<endl;                                 //因为异常c在fun2()中被捕获,因此fun3中的这句输出语句被跳过了
}
int main()                    //输出结果:第3层处理
{                                     //第2层处理
    fun1();                           //第1层处理
    cout<<"执行main()";                //fun1结束
}                                     //执行main()

函数A在执行过程中发现异常时可以不加处理,而只是“拋出一个异常”给A的调用者,假定为函数B。拋出异常而不加处理会导致函数A立即中止,函数B可以选择捕获A拋出的异常进行处理,也可以选择置之不理

  • 如果置之不理,这个异常就会被拋给B的调用者,以此类推
  • 如果一层层的函数都不处理异常,异常最终会被拋给最外层的main函数,如果main函数也不处理异常,那么程序会立即异常地中止