咱们在上节博客中讲了 C 语言中的异常处理,今天咱们就来说下 C++ 中的异常处理。在 C++ 中内置异常处理的语法元素 try ... catch ...,try 语句处理正常代码逻辑,catch 语句处理异常状况,try 语句中的异常由相对应的 catch 语句处理。C++ 经过 throw 语句抛出异常信息,throw 抛出的异常必须被 catch 处理,当前函数可以处理异常,程序继续往下执行;当前函数没法处理异常,则函数中止执行并返回。未被处理的异常会顺着函数调用栈向上传播,直到被处理为止,不然程序将中止执行。以下ios
下来咱们就以代码为例来进行分析ide
#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)一次。下来咱们来看看异常处理的匹配规则,以下spa
下来咱们仍是以代码为例来进行说明对象
#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 会打印出什么ip
由于字符串是字面量,因此它会匹配到 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 经常使用于程序中没法避免的恶性错误。下图是标准库中的异常类关系
经过对异常的学习,总结以下:一、C++ 中直接支持异常处理的概念;二、try...catch..是 C++ 中异常处理的专用语句;三、try 语句处理正常代码逻辑,catch 语句处理异常状况,同一个 try 语句能够跟上多个 catch 语句;四、异常处理必须严格匹配,不进行任何的转换;五、catch 语句块中能够抛出异常,异常的类型能够是自定义类类型;六、赋值兼容性原则在异常匹配中依然适用;七、标准库中的异常都是从 exception 类派生的。
欢迎你们一块儿来学习 C++ 语言,能够加我QQ:243343083。