c++学习笔记(18.异常处理)
本节知识点:
1.异常与c语言的异常处理:
#include <cstdlib> #include <iostream> 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; ret = MemSet(ai, sizeof(ai), 0); if( ret == 0 ) { } else if( ret == -1 ) { } else if( ret == -2 ) { } else if( ret == -3 ) { } ret = MemSet(ad, sizeof(ad), 1); if( ret == 0 ) { } else if( ret == -1 ) { } else if( ret == -2 ) { } else if( ret == -3 ) { } ret = MemSet(ac, sizeof(ac), 2); if( ret == 0 ) { } else if( ret == -1 ) { } else if( ret == -2 ) { } else if( ret == -3 ) { } cout << "Press the enter key to continue ..."; cin.get(); return EXIT_SUCCESS; }c.可见条件判断的方法,使得正常逻辑的代码和异常处理的代码混合在一起,导致代码迅速膨胀,难以维护!而goto语句和setjmp()、longjmp()函数则可以将异常处理代码放在统一的地方,与正常逻辑代码分开。
2.c++中的异常处理:
#include <iostream> #define div_zero_error 1 using namespace std; double div(double a, double b) { if((-0.0000001<b)&&(b<0.0000001)) { throw div_zero_error; } return a/b; } int main() { char a = ‘c‘; //try语句块外面依然可以放其他语句 try { cout << div(2,3) << endl; throw a; //throw后面可以直接 放数据 也可以放变量 //throw ‘d‘; cout << div(1,0) << endl; cout << div(5,2) << endl; } catch(int a) { cout << "div_zero_error" << endl; } catch(char) //catch语句后面,可以只有变量类型,没有具体变量 { cout << "test_error" << endl; } return 0; }注意:第一,并不是所有语句都要全部放在try或者catch语句块里面,可以放在try或者catch语句外面!
c.当throw语句抛出一个异常的时候,首先在当前函数中看,throw语句是否在一个try语句块中,如果是则去判断这个try语句块对应的catch语句块中是否能够处理这个异常类型,如果不能处理或者throw语句不在一个try语句块中,则退出当前函数,返回到调用函数的位置看,看调用函数的语句是否在一个try语句块中,对应的catch是否能处理这个异常,不断重复返回到上层函数,直到遇到可以处理这个异常的catch语句。或者返回到了所有函数后,都无法处理抛出的这个异常,则程序异常终止!!!切记,即使异常在对应的catch语句块中完成了异常处理,程序也不能回到异常发生处或上层函数调用处继续执行了,程序只能接着处理异常的catch语句块后面继续执行!!!
#include <cstdlib> #include <iostream> 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]; try { MemSet(ai, sizeof(ai), 0); MemSet(ad, sizeof(ad), 1); MemSet(ac, sizeof(ac), 2); } catch(int e) { cout<<e<<endl; } cout << "Press the enter key to continue ..."; cin.get(); return EXIT_SUCCESS; }注意:上面代码是c++异常处理的常规用法!
#include <cstdlib> #include <iostream> using namespace std; int MemSet(void* dest, unsigned int length, unsigned char v) { int ret = 0; 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]; if(0==MemSet(ai, sizeof(ai), 0)) {} else { if(-1==MemSet(ai, sizeof(ai), 0)) { cout << -1 << endl; } else if(-2==MemSet(ai, sizeof(ai), 0)) { cout << -2 << endl; } else if(-3==MemSet(ai, sizeof(ai), 0)) { cout << -3 << endl; } } if(0==MemSet(ad, sizeof(ad), 1)) {} else { if(-1==MemSet(ad, sizeof(ad), 1)) { cout << -1 << endl; } else if(-2==MemSet(ad, sizeof(ad), 1)) { cout << -2 << endl; } else if(-3==MemSet(ad, sizeof(ad), 1)) { cout << -3 << endl; } } if(0==MemSet(ac, sizeof(ac), 2)) {} else { if(-1==MemSet(ac, sizeof(ac), 2)) { cout << -1 << endl; } else if(-2==MemSet(ac, sizeof(ac), 2)) { cout << -2 << endl; } else if(-3==MemSet(ac, sizeof(ac), 2)) { cout << -3 << endl; } } cout << "Press the enter key to continue ..."; cin.get(); return EXIT_SUCCESS; }注意:上面就是使用条件判断的异常处理方式,可见代码膨胀的样子!但是使用条件判断可以保证每一个正常逻辑的代码都得到执行,而try.....catch语句就保证不了!因为当发生throw异常的时候,代码就会跳转到catch语句块中,然后从catch语句块往后执行了!会丢失throw语句后面部分的正常逻辑代码!
e.同一个try语句块可以跟上多个catch语句块,同一个try语句块可以抛出多种不同类型的异常,不同类型的异常由不同的catch语句块负责处理!异常被抛出后会自上而下逐一匹配catch语句块,异常匹配时,不会进行默认类型转换!
#include <cstdlib> #include <iostream> 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 ‘d‘; } return i; } int main(int argc, char *argv[]) { for(int i=0; i<4; i++) { try { cout<<test(i)<<endl; } catch(int e) { cout<<"Int: "<<e<<endl; } catch(const char* e) { cout<<"const char*: "<<e<<endl; } catch(double e) { cout<<"double: "<<e<<endl; } } cout << "Press the enter key to continue ..."; cin.get(); return EXIT_SUCCESS; }注意:第一,catch后面可以只有类型,没有具体变量,但是当没有变量的时候,catch语句块中就不能获得throw语句抛出的异常参数了!
3.深入异常处理:
#include <cstdlib> #include <iostream> 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 ‘d‘; } return i; } int main(int argc, char *argv[]) { for(int i=0; i<5; i++) { try { try { cout<<test(i)<<endl; } catch(int) { // cout<<"Int: "<<e<<endl; throw; } catch(const char* e) { cout<<"const char*: "<<e<<endl; throw 3; } catch(double e) { cout<<"double: "<<e<<endl; throw; } catch(...) { cout << "catch(...) " << endl; throw; } } catch(double a) { cout << "throw catch(double) " << endl; } catch(int a) { cout << "throw catch(int) " << endl; cout << a << endl; } catch(char a) { cout << "throw catch(char) " << endl; } catch(...) { cout << "throw catch(...) " << endl; } } cout << "Press the enter key to continue ..."; cin.get(); return EXIT_SUCCESS; }
4.异常与对象:
a.切记,千万不要在构造函数中抛出异常,构造函数中可能会申请系统资源(如new int[5]),而在构造函数中抛出异常则会导致对象构造不完全,对于不完全对象的析构函数是不会被调用的,因此可能会造成资源泄漏!(即析构函数不被调用,析构函数中的delet [] 没有执行,造成内存泄漏)#include <iostream> using namespace std; class test { public: test() { cout << "test()....." << endl; throw ‘a‘; } ~test() { cout << "~test()....." << endl; } }; void fun() { try { test t1; } catch(char e) { cout << e << endl; } } int main() { fun(); return 0; }注意:上面的对象因为是构造不完全的对象,所以对象的析构函数不被调用!
5.工程中的异常应用:
#include <iostream> #include <stdexcept> //异常族的头文件 using namespace std; double Div(double a, double b) { //invalid_argument p("Divide by zero..."); if( (-0.00000001 < b) && ( b < 0.00000001) ) { /* invalid_argument是一个异常类,invalid_argument("Divide by zero...") 是直接调用这个类的构造函数(后面是构造函数的参数,也是异常信息), 此时编译器产生一个临时对象,throw就是抛出了这个临时对象 */ throw invalid_argument("Divide by zero..."); //throw p; } return a / b; } int main() { try { cout<<Div(1, 0)<<endl; } //这里使用引用是为了避免由于对象构造与拷贝的开销,使用引用就不会再重新创建一个对象进行拷贝了 catch(invalid_argument& error) { cout << error.what() << endl;//通过what成员函数获得异常信息 } return 0; }注意:在catch语句后,可以使用引用参数,使用引用就避免了对象的构造和拷贝的开销,效率会高些!
使用自己创建的异常对象示例代码:
#include <iostream> #include <stdexcept> //异常族的头文件 using namespace std; class div_zero_error : public logic_error { public: div_zero_error(const char* s) : logic_error(s)//初始化列表~~~ { } }; double Div(double a, double b) { //div_zero_error p("Divide by zero..."); if( (-0.00000001 < b) && ( b < 0.00000001) ) { /* div_zero_error是一个异常类,div_zero_error("Divide by zero...") 是直接调用这个类的构造函数(后面是构造函数的参数,也是异常信息), 此时编译器产生一个临时对象,throw就是抛出了这个临时对象 */ throw div_zero_error("Divide by zero..."); //throw p; } return a / b; } int main() { try { cout<<Div(1, 0)<<endl; } //这里使用引用是为了避免由于对象构造与拷贝的开销,使用引用就不会再重新创建一个对象进行拷贝了 catch(exception& error) //使用exception类型 是为了接受各种类型的异常 { cout << error.what() << endl;//通过what成员函数获得异常信息 } return 0; }注意:第一,当希望通过标准库中的异常类族派生出自己的异常类的时候,首先要自己定义类并继承基类,这样自己的异常类中就继承了logic_error类的构造函数,这个带参的构造函数(即参数是字符串,用来保存异常信息的)是对应what()成员函数的。所以就出现了这样的语句,div_zero_error(const char* s) : logic_error(s) 这条语句很有意思,这条语句使用了构造函数的初始化参数列表!当创建div_zero_error类对象的时候,先接收构造函数的参数,且保存在s中,不管是从初始化列表的角度看,还是从父类构造函数的角度看,都是先调用logic_error类的构造函数,再调用div_zero_error类的构造函数,此时在调用logic_error类的构造函数的时候,就完成了对异常信息的保存!!!等于说把子类div_zero_error的构造函数的参数赋值给了父类logic_error的构造函数的参数,用于传递异常信息,即div_zero_error的构造函数的参数就是用来接收异常信息的!对于那些标准库中的其他子类,原理依然相同!
6.函数级try语法:
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。