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


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

#include<iostream>#include<string>usingnamespacestd;doubledivide(doublea,doubleb){constdoubledelta=0.000000000000001;doubleret=0;if(!((-delta<b)&&(b<delta))){ret=a/b;}else{throw0;}returnret;}intmain(){try{doubler=divide(1,0);cout<<"r="<<r<<endl;}catch(...){cout<<"Dividedbyzero..."<<endl;}return0;}

我们来看看编译结果

我们再来试试 1/1 呢

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

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

#include<iostream>#include<string>usingnamespacestd;voidDemo1(){try{throw0;}catch(charc){cout<<"catch(charc)"<<endl;}catch(shortc){cout<<"catch(shortc)"<<endl;}catch(doublec){cout<<"catch(doublec)"<<endl;}catch(intc){cout<<"catch(intc)"<<endl;}}voidDemo2(){throw"D.T.Software";}intmain(){try{Demo1();//Demo2();}catch(char*s){cout<<"catch(char*s)"<<endl;}catch(constchar*cs){cout<<"catch(constchar*cs)"<<endl;}catch(stringss){cout<<"catch(stringss)"<<endl;}return0;}

我们来看看会打印出什么

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

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

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

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

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

#include<iostream>#include<string>usingnamespacestd;voidDemo(){try{try{throw'c';}catch(inti){cout<<"Inner:catch(inti)"<<endl;throwi;}catch(...){cout<<"Inner:catch(...)"<<endl;throw;}}catch(...){cout<<"Outer:catch(...)"<<endl;}}/*假设:当前的函数式第三方库中的函数,因此,我们无法修改源代码函数名:voidfunc(inti)抛出异常的类型:int-1==>参数异常-2==>运行异常-3==>超时异常*/voidfunc(inti){if(i<0){throw-1;}if(i>100){throw-2;}if(i==11){throw-3;}cout<<"Runfunc..."<<endl;}voidMyFunc(inti){try{func(i);}catch(inti){switch(i){case-1:throw"InvalidParameter";break;case-2:throw"RuntimeException";break;case-3:throw"TimeoutException";break;}}}intmain(){Demo();/*try{MyFunc(11);}catch(constchar*cs){cout<<"ExceptionInfo:"<<cs<<endl;}*/return0;}

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

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

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

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

#include<iostream>#include<string>usingnamespacestd;classBase{};classException:publicBase{intm_id;stringm_desc;public:Exception(intid,stringdesc){m_id=id;m_desc=desc;}intid()const{returnm_id;}stringdescription()const{returnm_desc;}};/*假设:当前的函数式第三方库中的函数,因此,我们无法修改源代码函数名:voidfunc(inti)抛出异常的类型:int-1==>参数异常-2==>运行异常-3==>超时异常*/voidfunc(inti){if(i<0){throw-1;}if(i>100){throw-2;}if(i==11){throw-3;}cout<<"Runfunc..."<<endl;}voidMyFunc(inti){try{func(i);}catch(inti){switch(i){case-1:throwException(-1,"InvalidParameter");break;case-2:throwException(-2,"RuntimeException");break;case-3:throwException(-3,"TimeoutException");break;}}}intmain(){try{MyFunc(11);}catch(constException&e){cout<<"ExceptionInfo:"<<endl;cout<<"ID:"<<e.id()<<endl;cout<<"Description:"<<e.description()<<endl;}catch(constBase&e){cout<<"catch(constBase&e)"<<endl;}return0;}

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

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

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

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


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