第25课 - 异常处理
一.异常处理初探
1.1 所有的代码都有可能不按照预定义的方式运行
a.典型问题一:
Source Example 1.1: #include <iostream> /* run this program using the console pauser or add your own getch, system("pause") or input loop */ using namespace std; /* 当b等于0时会发生不可预知的错误 */ double Div(double a, double b) { return a / b; } double Add(double a, double b) { return a + b; } double Sub(double a, double b) { return a - b; } double Multiplication(double a, double b) { return a * b; } int main(int argc, char** argv) { /* 会调用Div(3,0),产生不可预知的错误 */ cout<<Div(Add(1,2), Sub(3,3))<<endl; return 0; }
C方式解决方案:
/* 在调用时传入一个正确错误的标志 */ double Div(double a, double b, bool* valid) { /* 引入了问题,无法判断valid这个指针是否合法 */ *valid = true; if ((-0.00000001 < b) && (b < 0.00000001)) { *valid = false; return 0; } return a / b; }
1.2 程序中会出现大量的处理异常的代码
b.典型问题二:
Source Example1.2: #include <iostream> /* run this program using the console pauser or add your own getch, system("pause") or input loop */ using namespace std; int MemSet(void* dest, unsigned int length, unsigned char v) { if (dest == NULL) { return -1; } if (length < 4) { return -2; } if ((v < 0) || (v > 9)) { return -3; } unsigned char *p = (unsigned char *)dest; for (int i = 0; i < length; i++) { p[i] = v; } return 0; } int main(int argc, char** argv) { int ai[5]; double ad[4]; int ac[3]; int ret; /* 代码过多,维护及其困难!!! */ ret = MemSet(ai, sizeof (ai) / sizeof (int), 0); if (ret == 0) { } else if (ret == -1) { } else if (ret == -2) { } else if (ret == -3) { } return 0; }
1.3 其它C方式的异常处理方案
1.3.1 goto语句
Source Example 1.3.1: #include <iostream> /* run this program using the console pauser or add your own getch, system("pause") or input loop */ using namespace std; int MemSet(void* dest, unsigned int length, unsigned char v) { if (dest == NULL) { return -1; } if (length < 4) { return -2; } if ((v < 0) || (v > 9)) { return -3; } unsigned char *p = (unsigned char *)dest; for (int i = 0; i < length; i++) { p[i] = v; } return 0; } int main(int argc, char** argv) { int ai[5]; double ad[4]; char ac[3]; int ret = 0; ret = MemSet(ai, sizeof (ai) / sizeof (int), 0); if (ret != 0) { goto ERROR; } ERROR: return 0; }
1.3.2 setjmp()和longjmp()
小总结: 1. 在C语言可以使用上面两种解决方案将异常处理代码放到统一的地方,与正常逻辑分开
2. 但在C++中,这两种方法可能导致对象的构造函数或者析构函数得不到调用而引发错误
二.C++中的异常处理
2.1 C++中提供了try和catch语句块对可能产生异常的代码进行分开处理
2.1.1 try语句块处理正常逻辑
2.1.2 catch语句块处理异常
2.2 C++语言中通过throw语句引发一个异常
Source Example 2: #include <iostream> /* run this program using the console pauser or add your own getch, system("pause") or input loop */ using namespace std; #define DIV_ZERO_ERROR -1 double Div(double a, double b) { if ((-0.00000001 < b) && (b < 0.00000001)) { /* 由于当前函数没有try..catch语句 */ /* 函数立即返回 */ throw DIV_ZERO_ERROR; } return a / b; } double Add(double a, double b) { return a + b; } double Sub(double a, double b) { return a - b; } double Multiplication(double a, double b) { return a * b; } int main(int argc, char** argv) { /* 会调用Div(3,0),产生不可预知的错误 */ try { cout<<Div(1,3)<<endl; cout<<Div(2,0)<<endl; cout<<Div(3,1)<<endl; } catch(int error) { cout<<"Error No: "<<error<<endl; } return 0; }
输出结果如下:
2.3 throw语句用于将异常"对象"抛出
2.3.1 throw语句将异常抛出,如果在当前函数中没有try...catch语句能够处理该异常,则当前函数立即返回。
a.异常被传递到上层调用函数仍然需要try...catch语句进行处理,如果上层函数也没有能力处理该异常, 则异常继续向更上层函数的函数传递
2.3.2 如果函数调用栈中的所有函数都无法处理抛出的一次,则程序异常中止
异常顺着函数调用栈"向上"传播,直到有相应的catch块处理
Source Example 2.3: #include <iostream> /* run this program using the console pauser or add your own getch, system("pause") or input loop */ using namespace std; void MemSet(void* dest, unsigned int length, unsigned char v) { if (dest == NULL) { throw -1; } if (length < 4) { throw -2; } if ((v < 0) || (v > 9)) { throw -3; } unsigned char *p = (unsigned char *)dest; for (int i = 0; i < length; i++) { p[i] = v; } } int main(int argc, char** argv) { int ai[5]; double ad[4]; char ac[3]; int ret = 0; try { MemSet(ai, sizeof (ai) / sizeof (int), 0); /* 长度小于3,会产生异常2 */ MemSet(ai, sizeof (ac) / sizeof (char), 0); MemSet(ai, sizeof (ad) / sizeof (double), 0); } catch (int error) { cout<<"Error No:"<< error <<endl; } return 0; }
2.4 同一个try语句块可以跟上多个catch语句块
2.4.1 同一个try语句块可以抛出多种不同的异常
2.4.2 不同类型的异常由不同的catch语句块负责处理
2.5 异常被抛出后会自上而下注意匹配catch语句块
2.5.1 异常匹配时,不会进行默认类型转换
Source Example 2.5: #include <iostream> /* run this program using the console pauser or add your own getch, system("pause") or input loop */ using namespace std; int test(int i) { if(i == 1) { throw -1; } if (i == 2) { throw "Error"; } if (i == 3) { throw 0.5; } /* 无法处理字符型的异常,会异常退出 */ //if (i == 4) //{ // throw 'c'; //} return i; } int main(int argc, char** argv) { for (int i = 0; i < 5; i++) { try { cout<<test(i)<<endl; } catch (int error) { cout<<"Error No:"<< error <<endl; } catch (double error) { cout<<"Error No:"<< error <<endl; } catch (const char *s) { cout<<"Error No:"<< s <<endl; } } return 0; }输出结果如下: