C语言异常
- 程序在运行过程中可能产生异常
- 异常( Exception ) 与 Bug 的区别
异常是程序运行时可预料的执行分支
Bug 是程序中的错误,不是希望的,是不被预期的运行方式
异常(Exception)和Bug的对比
- 异 常
- 运行时产生除0的情况
- 需要打开的外部文件不存在
- 数组访问时越界
- Bug
- 使用野指针
- 堆数组使用结束后未释放
- 选择排序无法处理长度为 0 的数组
C语言经典处理方式if..else
1 2 3 4 5
| void func (... ) if ( 判断是否产生异常 ) 正常情况代码逻辑; else 异常情况代码逻辑;
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| #include <iostream> #include <string>
using namespace std;
double divide(double a, double b, int* valid) { const double delta = 0.000000000000001; double ret = 0; if( !((-delta < b) && (b < delta)) ) { ret = a / b; *valid = 1; } else { *valid = 0; } return ret; }
int main(int argc, char *argv[]) { int valid = 0; double r = divide(1, 0, &valid); if( valid ) { cout << "r = " << r << endl; } else { cout << "Divided by zero..." << endl; } return 0; }
|
我们用C语言方式来处理,if…else判断,并且额外携带第三个参数用于判断是否有异常。
缺 陷
- divide 函数有 3 个参数 , 难以理解其用法
- divide 函数调用后必须判断 valid 代表的结果
当 valid 为 true 时 , 运算结果正常
当 valid 为 false 时 , 运算过程出现异常
通过 setjmp() 和 longjmp() 进行优化
- int setjmp(jmp_buf env) 将当前上下文保存在 jmp_buf 结构体中
- void longjmp(jmp_buf env, int val) 从 jmp_buf 结构体中恢复 setjmp()保存的上下文, 最终从setjmp 函数调用点返回 , 返回值为val
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| #include <iostream> #include <string> #include <csetjmp>
using namespace std;
static jmp_buf env;
double divide(double a, double b) { const double delta = 0.000000000000001; double ret = 0; if( !((-delta < b) && (b < delta)) ) { ret = a / b; } else { longjmp(env, 1); } return ret; }
int main(int argc, char *argv[]) { if( setjmp(env) == 0 ) { double r = divide(1, 1); cout << "r = " << r << endl; } else { cout << "Divided by zero..." << endl; } return 0; }
|
缺 陷
- setjmp() 和 longjmp()的引入
- 必然涉及到使用全局变量
- 暴力跳导致代码可读性降低
- 本质还是if …else …异常处理方式
C++异常处理
try…catch
C++内置了异常处理的语法元素 try … catch …
try 语句处理正常代码逻辑
- - catch 语句处理异常情况
- - try 语句中的异常甶对应的 catch 语句处理
1 2 3 4
| try double r = divide(1, 0); catch (.....) cout << "Divided by zero..." << endl;
|
C++ 通过throw 语句抛出异常信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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; }
|
C+ + 异常处理分析
- throw 拋出的异常必须被 catch 处理
• 当前函数能够处理异常 , 程序继续往下执行
• 当前函数无法处理异常 , 则函数停止执行 , 并返回
未被处理的异常会顺着函数调用栈向上传播 ,直到被处理为止 , 否则程序将停止执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| #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(int argc, char *argv[]) { try { double r = divide(1, 0); cout << "r = " << r << endl; } catch(...) { cout << "Divied by zero..." << endl; } return 0; }
|
同 一 个 try 语句可以跟上多个 catch 语句
- - catch 语句可以定义具体处理的异常类型
- - 不同类型的异常甶不同的 catch 语句负责处理
- - try 语句中可以抛出任何类型的异常
- - catch(…) 用于处理所有类型的异常
- - 任何异常都只能被捕获( catch ) —次
注意是严格匹配,不会进行任何的类型转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| #include <iostream> #include <string>
using namespace std;
void Demo1() { try { throw 1; } catch(char c) { cout << "catch(char c)" << endl; } catch(short c) { cout << "catch(short c)" << endl; } catch(int c) { cout << "catch(int c)" << endl; } catch(double c) { cout << "catch(double c)" << endl; } catch(...) { cout << "catch(...)" << endl; } }
void Demo2() { throw string("fengyun"); }
int main(int argc, char *argv[]) { Demo1(); try { 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; }
|
- C+ + 中直接支持异常处理的概念
- try … catch … 是 C+ + 中异常处理的专用语句
- try 语句处理正常代码逻辑 , catch 语句处理异常廣况
- 同一个try语句可以跟上多个catch语句
- 异常处理必须严格匹配,不进行任何的类型转换
catch语句块可以抛出异常
为什么要在 catch 中重新拋出异常 ?
catch 中捕获的异常可以被重新解释后拋出
工程开发中使用这样的方式统一异常类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
| #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) { 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(int argc, char *argv[]) { try { MyFunc(11); } catch(const char* cs) { cout << "Exception Info: " << cs << endl; } return 0; }
|
- 在工程中会定义一系列的异常类
- 每个类代表工程中可能出现的一种异常类型
- 代码复用时可能需要重解释不同的异常类
- 在 定 义 catch 语句块时推荐使用引用作为参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
| #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) { 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(int argc, char *argv[]) { 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; }
|
C+ + 标准库中提供了实用异常类族
- 标准库中的异常都是从 exception 类派生的
- exception 类有两个主要的分支
- logic_error 常用于程序中的可避免逻辑错误
- runtime_error 常用于程序中无法避免的恶性错误
迷惑的写法
- try … catch 用于分隔正常功能代码与异常处理代码
- try … catch 可以直接将函数实现分隔为 2 部分
- 函数声明和定义时可以直接指定可能抛出的异常类型
- 异常声明成为函数的一部分可以提高代码可读性
函数异常声明的注意事项:
- 函数异常声明是一种与编译器之间的契约
- 函数声明异常后就只能抛出声明的异常
拋出其它异常将导致程序运行终止
可以直接通过异常声明定义无异常函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| #include <iostream> #include <string>
using namespace std;
int func(int i, int j) throw(int, char) { if( (0 < j) && (j < 10) ) { return (i + j); } else { throw '0'; } }
void test(int i) try { cout << "func(i, i) = " << func(i, i) << endl; } catch(int i) { cout << "Exception: " << i << endl; } catch(...) { cout << "Exception..." << endl; }
int main(int argc, char *argv[]) { test(5); test(10); return 0; }
|