我们在上节博客中讲了 C 语言中的异常处理,今天我们就来讲下 C++ 中的异常处理。在 C++ 中内置异常处理的语法元素  try ... catch ...try 语句处理正常代码逻辑,catch 语句处理异常情况,try 语句中的异常由相对应的 catch 语句处理。C++ 通过 throw 语句抛出异常信息,throw 抛出的异常必须被 catch 处理,当前函数能够处理异常,程序继续往下执行;当前函数无法处理异常,则函数停止执行并返回。未被处理的异常会顺着函数调用栈向上传播,直到被处理为止,否则程序将停止执行。如下

图片.png

        下来我们就以代码为例来进行分析

#include 
#include 
using namespace std;double divide(double a, double b){    const double delta = 0.000000000000001;    double ret = 0;        if( !((-delta < b) && (b < delta)) )    {        ret = a / b;    }    else    {        throw 0;    }        return ret;}int main(){    try    {        double r = divide(1, 0);        cout << "r = " << r << endl;    }    catch(...)    {        cout << "Divided by zero..." << endl;    }    return 0;}

        我们来看看编译结果

图片.png

        我们再来试试 1/1 呢

图片.png

        已经正确实现了哈。C++ 的这个异常处理是不是很方便呢。同一个 try 语句是可以跟上多个 catch 语句的。catch 语句可以定义具体处理的异常类型,不同类型的异常由不同的 catch 语句负责处理;try 语句中可以抛出任何类型的异常,catch(...) 用于处理所有类型的异常,任何异常都只能被捕获(catch)一次。下来我们来看看异常处理的匹配规则,如下

图片.png

         下来我们还是以代码为例来进行说明

#include 
#include 
using namespace std;void Demo1(){    try    {        throw 0;    }    catch(char c)    {        cout << "catch(char c)" << endl;    }    catch(short c)    {        cout << "catch(short c)" << endl;    }    catch(double c)    {        cout << "catch(double c)" << endl;    }    catch(int c)    {        cout << "catch(int c)" << endl;    }}void Demo2(){    throw "D.T.Software";}int main(){    try    {        Demo1();        //Demo2();    }    catch(char* s)    {        cout << "catch(char* s)" << endl;    }    catch(const char* cs)    {        cout << "catch(const char* cs)" << endl;    }    catch(string ss)    {        cout << "catch(string ss)" << endl;    }        return 0;}

        我们来看看会打印出什么

图片.png

        我们看到直接在最后匹配到了 int,因为抛出的 0 默认类型为 int,它不会进行默认类型的转换。我们再来看看 Demo2 会打印出什么

图片.png

        因为字符串是字面量,所以它会匹配到 const char* cs 上,如果我们在 Demo2 函数中抛出的是 string("D.T.Software");看看会打印出什么

图片.png

        便会打印出字符串了。那么在 catch语句中我们还可以抛出异常,如下

图片.png

        那么我们为什么要在 catch 语句中重新抛出异常呢?catch 中捕获的异常可以被重新解释后抛出,在工程开发中使用这样的方式统一异常类型,如下

图片.png

        那么我们还是以代码为例来进行讲解

#include 
#include 
using namespace std;void Demo(){    try    {        try        {            throw 'c';        }        catch(int i)        {            cout << "Inner: catch(int i)" << endl;            throw i;        }        catch(...)        {            cout << "Inner: catch(...)" << endl;            throw;        }    }    catch(...)    {        cout << "Outer: catch(...)" << endl;    }}/*    假设: 当前的函数式第三方库中的函数,因此,我们无法修改源代码        函数名: void func(int i)    抛出异常的类型: int                        -1 ==> 参数异常                        -2 ==> 运行异常                        -3 ==> 超时异常*/void func(int i){    if( i < 0 )    {        throw -1;    }        if( i > 100 )    {        throw -2;    }        if( i == 11 )    {        throw -3;    }        cout << "Run func..." << endl;}void MyFunc(int i){    try    {        func(i);    }    catch(int i)    {        switch(i)        {            case -1:                throw "Invalid Parameter";                break;            case -2:                throw "Runtime Exception";                break;            case -3:                throw "Timeout Exception";                break;        }    }}int main(){    Demo();/*        try    {        MyFunc(11);    }    catch(const char* cs)    {        cout << "Exception Info: " << cs << endl;    }*/        return 0;}

        我们先以 Demo 函数为例来进行分析,在 try 语句中的 try 语句里抛出 c,匹配到 catch(...) 语句中,先打印出 Inner: catch(...),再次抛出。匹配到外面的 catch(...) 语句中,先打印出 Outer: catch(...)。我们来看看结果

图片.png

        我们看到和我们的分析是完全一致的。接下来的 func 函数就比如是第三方的源码,我们得根据这个功能写个一个属于我们自己的 func 函数。我们改写完之后是不是就一目了然呢?比如没改写之前,抛出个 11 的异常,对应的便会打印出 -3,我们还得去查这个 -3 代表啥意思。我们来注释掉 Demo 函数,看看下面的编译结果

图片.png

        输出结果一目了然,直接看到是超时异常。那么我们便直接定位到了问题,这样效率便会提高。在 C++ 中,异常的类型可以是自定义类类型,对于类类型异常的匹配依旧是至上而下严格匹配,赋值兼容性原则在异常匹配中依然适用。一般而言,将匹配子类异常的 catch 放在上部,匹配父类异常的 catch 放在下部。在工程中会定义一系列的异常类,每个类代表工程中可能出现的一种异常类型。代码复用时可能需要解释不同的异常类,在定义 catch 语句时需要推荐使用引用作为参数。

        接下来我们还是以代码为例来进行分析

#include 
#include 
using namespace std;class Base{};class Exception : public Base{    int m_id;    string m_desc;public:    Exception(int id, string desc)    {        m_id = id;        m_desc = desc;    }        int id() const    {        return m_id;    }        string description() const    {        return m_desc;    }};/*    假设: 当前的函数式第三方库中的函数,因此,我们无法修改源代码        函数名: void func(int i)    抛出异常的类型: int                        -1 ==> 参数异常                        -2 ==> 运行异常                        -3 ==> 超时异常*/void func(int i){    if( i < 0 )    {        throw -1;    }        if( i > 100 )    {        throw -2;    }        if( i == 11 )    {        throw -3;    }        cout << "Run func..." << endl;}void MyFunc(int i){    try    {        func(i);    }    catch(int i)    {        switch(i)        {            case -1:                throw Exception(-1, "Invalid Parameter");                break;            case -2:                throw Exception(-2, "Runtime Exception");                break;            case -3:                throw Exception(-3, "Timeout Exception");                break;        }    }}int main(){    try    {        MyFunc(11);    }    catch(const Exception& e)    {        cout << "Exception Info: " << endl;        cout << "    ID: " << e.id() << endl;        cout << "    Description: " << e.description() << endl;    }    catch(const Base& e)    {        cout << "catch(const Base& e)" << endl;    }    return 0;}

        我们看到定义了两个类,在类 Exception 中定义了 id 和 description 用来描述他们的信息,再在 MyFunc 函数中生成临时对象 Exception 用来获取他们的信息,我们来看看编译结果

图片.png

        这样的信息是不是更加直观呢。如果我们将上面的 catch 语句中的父类放在子类前面呢,看看结果

图片.png

        我们看到编译已经警告了,运行后它打印的是父类的信息,因为它同样遵循赋值兼容性原则。我们在之前说的,将匹配子类异常的 catch 放在上部,匹配父类异常的 catch 放在下部。一定要遵循这个规则。在 C++ 标准库中提供了实用异常类族,标准库中的异常都是从 exception 类派生的,exception 类有两个主要的分支:a> logic_error 常用于程序中可避免逻辑错误;b> runtime_error 常用于程序中无法避免的恶性错误。下图是标准库中的异常类关系

图片.png

        通过对异常的学习,总结如下:1、C++ 中直接支持异常处理的概念;2、try...catch..是 C++ 中异常处理的专用语句;3、try 语句处理正常代码逻辑,catch 语句处理异常情况,同一个 try 语句可以跟上多个 catch 语句;4、异常处理必须严格匹配,不进行任何的转换;5、catch 语句块中可以抛出异常,异常的类型可以是自定义类类型;6、赋值兼容性原则在异常匹配中依然适用;7、标准库中的异常都是从 exception 类派生的。

        欢迎大家一起来学习 C++ 语言,可以加我QQ:243343083