我们在上节博客中讲了 C 语言中的异常处理,今天我们就来讲下 C++ 中的异常处理。在 C++ 中内置异常处理的语法元素 try ... catch ...,try 语句处理正常代码逻辑,catch 语句处理异常情况,try 语句中的异常由相对应的 catch 语句处理。C++ 通过 throw 语句抛出异常信息,throw 抛出的异常必须被 catch 处理,当前函数能够处理异常,程序继续往下执行;当前函数无法处理异常,则函数停止执行并返回。未被处理的异常会顺着函数调用栈向上传播,直到被处理为止,否则程序将停止执行。如下
下来我们就以代码为例来进行分析
#include <iostream> #include <string> 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; }
我们来看看编译结果
我们再来试试 1/1 呢
已经正确实现了哈。C++ 的这个异常处理是不是很方便呢。同一个 try 语句是可以跟上多个 catch 语句的。catch 语句可以定义具体处理的异常类型,不同类型的异常由不同的 catch 语句负责处理;try 语句中可以抛出任何类型的异常,catch(...) 用于处理所有类型的异常,任何异常都只能被捕获(catch)一次。下来我们来看看异常处理的匹配规则,如下
下来我们还是以代码为例来进行说明
#include <iostream> #include <string> 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; }
我们来看看会打印出什么
我们看到直接在最后匹配到了 int,因为抛出的 0 默认类型为 int,它不会进行默认类型的转换。我们再来看看 Demo2 会打印出什么
因为字符串是字面量,所以它会匹配到 const char* cs 上,如果我们在 Demo2 函数中抛出的是 string("D.T.Software");看看会打印出什么
便会打印出字符串了。那么在 catch语句中我们还可以抛出异常,如下
那么我们为什么要在 catch 语句中重新抛出异常呢?catch 中捕获的异常可以被重新解释后抛出,在工程开发中使用这样的方式统一异常类型,如下
那么我们还是以代码为例来进行讲解
#include <iostream> #include <string> 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(...)。我们来看看结果
我们看到和我们的分析是完全一致的。接下来的 func 函数就比如是第三方的源码,我们得根据这个功能写个一个属于我们自己的 func 函数。我们改写完之后是不是就一目了然呢?比如没改写之前,抛出个 11 的异常,对应的便会打印出 -3,我们还得去查这个 -3 代表啥意思。我们来注释掉 Demo 函数,看看下面的编译结果
输出结果一目了然,直接看到是超时异常。那么我们便直接定位到了问题,这样效率便会提高。在 C++ 中,异常的类型可以是自定义类类型,对于类类型异常的匹配依旧是至上而下严格匹配,赋值兼容性原则在异常匹配中依然适用。一般而言,将匹配子类异常的 catch 放在上部,匹配父类异常的 catch 放在下部。在工程中会定义一系列的异常类,每个类代表工程中可能出现的一种异常类型。代码复用时可能需要解释不同的异常类,在定义 catch 语句时需要推荐使用引用作为参数。
接下来我们还是以代码为例来进行分析
#include <iostream> #include <string> 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 用来获取他们的信息,我们来看看编译结果
这样的信息是不是更加直观呢。如果我们将上面的 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。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。