1,C 语言崇尚简洁高效,因此语言本身并没有异常处理的相关语法规则,但是异常处理在 C 语言中 是存在的,我们有必要从 C 语言开始先看一看 C 语言中的异常处理是怎样, 然后对比 C++ 里面的异常处理是怎样;
2,异常的概念:
1,程序在运行过程中可能产生异常;
1,异常是我们在程序开发中必须考虑的一些特殊情况;
2,异常(Exception)与 Bug 的区别:
1,异常是程序运行时可预料的执行分支;
1,例如计算器相关的程序,涉及到除法操作的时候,就应该全面的想想有没有会产生意外的情况,比如除数为 0,这是一种意外情况,但是我们已经预料到这种意外情况,并且这种意外情况在计算器相关的程序里面是不可避免的,因此我们在做开发的时候,就必须有一些代码来处理这样的情况,这些代码就叫做异常处理的代码,而异常是程序运行时可以预料的情况(这就是异常和 Bug 的区别);
2,Bug 是程序中的错误,是不被预期的运行方式;
3,异常和 Bug 的对比:
1,异常:
1,运行时产生除 0 的情况;
2,需要打开的外部文件不存在;
3,数组访问的越界;
2,Bug:
1,使用野指针;
2,堆数组使用结束后未释放;
1,内存泄漏一定是 Bug;
3,选择排序无法处理长度为 0 的数组;
4,异常处理的方式:
1,C 语言经典处理方式:if ... else ...
1,代码示例:
1 void func(...) 2 { 3 if( 判断是否产生异常 ) 4 { 5 // 正常情况代码逻辑; 6 } 7 else 8 { 9 // 异常情况代码逻辑; 10 } 11 }
2,除法操作异常处理编程实验:
1 #include <iostream> 2 #include <string> 3 4 using namespace std; 5 6 double divide(double a, double b, int* valid) 7 { 8 const double delta = 0.000000000000001; 9 double ret = 0; 10 11 if( !((-delta < b) && (b < delta)) ) 12 { 13 ret = a / b; 14 15 *valid = 1; // 为了处理除数为 0 和本身结果就是 0 之间的区别; 16 } 17 else 18 { 19 *valid = 0; // 异常了; 20 } 21 22 return ret; 23 } 24 25 int main(int argc, char *argv[]) 26 { 27 int valid = 0; 28 double r = divide(1, 0, &valid); 29 30 if( valid ) 31 { 32 cout << "r = " << r << endl; 33 } 34 else 35 { 36 cout << "Divided by zero..." << endl; 37 } 38 39 return 0; 40 }
1,工程上 if 处理正常分支,else 处理异常分支;
2,除法操作需要三个参数,而加减乘法都只要两个参数,这样不优雅;
3,如果第三个参数没有指向合法的对象(NULL),就会出现段错误;
3,缺陷:
1,divide 函数有 3 个参数,难以理解其用法;
1,降低了代码的可读性,后期的维护成本就会增加;
2,改进的方向就是统一除法和加减乘法的调用方式,将三个参数改变为两个参数,并且还有异常处理的代码;
2,divide 函数调用后必须判断 valid 代表的结果;
1,当 valid 为 true 时,运行结果正常;
2,当 valid 为 false 时,运行过程出现异常;
4,通过 setjmp() 和 longjmp() 进行优化:
1,int setjmp(jmp_buf env)
1,将当前上下文保存在 jmp_buf 结构体中;
2,jmp_buf 一般的是全局变量;
2,void longjmp(jmp_buf env, int val)
1,从 jmp_buf 结构体中恢复 setjmp() 保存的上下文;
2,最终从 setjmp 函数调用点返回,返回值为 val;
3,这两个函数不要随便调用,因为它们简单粗暴,它们将破坏结构化程序设计的特性,结构化程序设计三大特性有顺序执行、循环执行、分支执行,如果调用这两个函数,将会有其他的执行方式出来;
5,除法操作异常处理优化编程实验:
1 #include <iostream> 2 #include <string> 3 #include <csetjmp> 4 5 using namespace std; 6 7 static jmp_buf env; // 保存程序执行的上下文; 8 9 double divide(double a, double b) 10 { 11 const double delta = 0.000000000000001; 12 double ret = 0; 13 14 if( !((-delta < b) && (b < delta)) ) 15 { 16 ret = a / b; 17 } 18 else 19 { 20 longjmp(env, 1); // 当 b = 0 时,跳转回 env 这个全局变量所保存的程序上下文的地方,也就是 main() 中的 if() 语句;此时 divide() 程序的执行立即停止,并从 setjmp() 函数返回,返回值为 longjmp() 中的参数 1; 21 } 22 23 return ret; 24 } 25 26 int main(int argc, char *argv[]) 27 { 28 if( setjmp(env) == 0 ) //调用 setjmp(env) 将程序的上下文保存在 env 里面; 29 { 30 double r = divide(1, 0); // Divided by zero...; 31 double r = divide(1, 1); // 1; 32 33 cout << "r = " << r << endl; 34 } 35 else 36 { 37 cout << "Divided by zero..." << endl; 38 } 39 40 return 0; 41 }
1,程序执行过程:
1,首先执行到 main() 中的 if() 语句;
2,直接调用 setjmp(),这个函数内部就会将当前程序执行的上下文全部保存在这个全局变量里面,因为是直接调用的,所以保存完了返回值就是 0;
3,运行到 divide() 后,判断到 b 为 0,则执行 longjum(),它根据 env 这个全局变量里面保存的信息来恢复之前的程序上下文;
4,之前的程序上下文是在 if() 中调用 setjmp() 保存的,所以说 longjmp() 函数一旦被调用,则 divide() 函数立即停止,出口在 setjmp(),此时返回值是 longjmp() 中第二个参数 1;
5,执行到 if...else 语句的 else 语句中;
2,这两个函数配合起来破坏了结构化程序设计的三大特性,所以简单的程序理解起来很复杂;
3,优雅但理解难,工程应用不多,工程中推荐使用第一种异常处理方式;
6,改进后缺陷:
1,setjmp() 和 longjmp() 的引入:
1,必然涉及到使用全局变量;
2,暴力跳转导致代码可读性降低;
3,本质还是 if ... else ... 异常处理方式;
7,C 语言中的经典异常处理方式问题:
1,会使得编程中逻辑中混入大量的处理异常代码;
2,正常逻辑代码和异常处理代码混合在一起,导致代码迅速膨胀,难以维护;
1,工程中约定,if 处理正常情况,else 处理异常情况,好歹代码中能够看出正常异常处理代码放在什么地方;
5,异常处理代码分析实例分析:
1 #include <iostream> 2 #include <string> 3 4 using namespace std; 5 6 #define SUCCESS 0 7 #define INVALID_POINTER -1 8 #define INVALID_LENGTH -2 9 #define INVALID_PARAMETER -3 10 11 int MemSet(void* dest, unsigned int length, unsigned char v) 12 { 13 if( dest == NULL ) 14 { 15 return INVALID_POINTER; 16 } 17 18 if( length < 4 ) 19 { 20 return INVALID_LENGTH; 21 } 22 23 if( (v < 0) || (v > 9) ) 24 { 25 return INVALID_PARAMETER; 26 } 27 28 unsigned char* p = (unsigned char*)dest; 29 30 for(int i=0; i<length; i++) 31 { 32 p[i] = v; 33 } 34 35 return SUCCESS; 36 } 37 38 int main(int argc, char *argv[]) 39 { 40 int ai[5]; 41 int ret = MemSet(ai, sizeof(ai), 0); 42 43 if( ret == SUCCESS ) // 处理异常代码逻辑; 44 { 45 } 46 else if( ret == INVALID_POINTER ) // 三个地方处理异常代码逻辑; 47 { 48 } 49 else if( ret == INVALID_LENGTH ) 50 { 51 } 52 else if( ret == INVALID_PARAMETER ) 53 { 54 } 55 56 return ret; 57 }
1,这对于职场新人很痛苦,因为代码逻辑的模块不清晰;
6,C++ 中有没有更好的异常处理方式?
1,直接通过关键字知道哪些代码处理正常情况的代码,哪些是异常情况的代码;
7,小结:
1,程序中不可避免的会发生异常;
2,异常是在开发阶段就可以预见的运行时问题;
3,C 语言中通过经典的 if ... else ... 方式处理异常;
4,C++ 中存在更好的异常处理方式;