定义:
异常,让一个函数可以在发现自己无法处理的错误时抛出一个异常,希望它的调用者可以直接或者间接处理这个问题。
之前写的一些小程序,几乎没有用到过异常处理。因为规模比较小,一般的问题在函数内就加上一些判断条件解决了,一般的做法就是返回一个表示错误的值(比如返回NULL指针),在调用的时候判断一下返回的值,虽然简单,但是功能并不强大,只适合小型项目。而大型的项目,如果这么搞就乱套了,所以就要用到异常处理这一套系统。
一个最简单的异常处理:
// ConsoleApplication3.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <iostream> #include <vector> #include <string> using namespace std; //异常类 class NumberException{}; void NumOption(const int& a, const int& b) { try { if (b == 0) throw NumberException(); cout<<a / b<<endl; } catch(const NumberException& e) { cout<<"Exception!"<<endl; } } int _tmain(int argc, _TCHAR* argv[]) { int a,b; cin>>a; cin>>b; NumOption(a, b); system("pause"); return 0; }
结果:
//输入
1
0
//输出
Exception!
关于异常处理有下面几点要注意:
1.在抛出一个异常之后,代码会转到异常处理的地方执行,throw后面的代码就不会再执行。如果没有找到相关catch,会跳出一级继续寻找。这个过程成为栈展开。所以要注意这样一种情况,在栈展开的过程中,程序块中的各种临时对象都会被销毁。
一个例子:
// ConsoleApplication3.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <iostream> #include <vector> #include <string> using namespace std; //异常类 class NumberException{}; //测试的类 class Test { public: ~Test() { cout<<"Test is destructed!"<<endl; } }; void NumOption(const int& a, const int& b) { try { Test test; if (b == 0) throw NumberException(); cout<<a / b<<endl; } catch(const NumberException& e) { cout<<"Exception!"<<endl; } } int _tmain(int argc, _TCHAR* argv[]) { int a,b; cin>>a; cin>>b; NumOption(a, b); system("pause"); return 0; }
正常情况:
//输入
1
1
//输出
1
Test is destructed!
异常情况:
//输入
1
0
//输出
Test is destructed!
Exception!
从上面的结果我们可以看出,正常情况下,程序执行完,先输出1,再销毁Test对象。但是抛出异常时,显然还没有出块的作用域,但是异常发生了,throw后面的内容不执行,也相当于声明周期结束,所有临时对象都会被销毁。
2.通常情况下,接受异常的catch块最好设定为引用类型。如果设为值传递,那么会发生基类参数接受子类异常时削去子类特有部分的情况。
并且我们可以设定是否为const引用,决定是否可以在catch块中对异常进行修改。
3.catch的匹配原则:越专门的匹配越靠前,换句话说,如果同时有子类和基类异常处理的catch,而抛出的是子类异常,我们为了能够处理子类,需要把子类异常处理放在前面。
例子:
// ConsoleApplication3.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <iostream> #include <vector> #include <string> using namespace std; //异常类 class NumberException{}; //派生的异常类 class ZeroException : public NumberException {}; void NumOption(const int& a, const int& b) { try { if (b == 0) throw ZeroException(); cout<<a / b<<endl; } catch(const ZeroException& e) { cout<<"ZeroException!"<<endl; } catch(const NumberException& e) { cout<<"NumberException!"<<endl; } } int _tmain(int argc, _TCHAR* argv[]) { int a,b; cin>>a; cin>>b; NumOption(a, b); system("pause"); return 0; }
结果:
1
0
//输出
ZeroException!
但是如果我们把catch(cost NumberException& e)和catch(const ZeroException& e)这两个换一下位置:结果就变成了
NumberException!
可见,要想处理更特例的异常,就要越放在前面。否则父类匹配了之后,不会再匹配子类。
4.重新抛出,当我们处理了一个异常但是没处理全时,可以将异常继续抛出。使用throw;关键字可以继续抛出。但是如果没有相应的catch继续接受异常的话,程序会terminate!
一个嵌套处理异常的例子:
// ConsoleApplication3.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <iostream> #include <vector> #include <string> using namespace std; //异常类 class NumberException{}; //派生的异常类 class ZeroException : public NumberException {}; void NumOption(const int& a, const int& b) { try { if (b == 0) throw ZeroException(); cout<<a / b<<endl; } catch(const ZeroException& e) { cout<<"ZeroException!"<<endl; throw; } catch (...) { cout<<"I can solve all Exception!"<<endl; } } int _tmain(int argc, _TCHAR* argv[]) { int a,b; cin>>a; cin>>b; //嵌套异常处理 try { NumOption(a, b); } catch(const NumberException& e) { cout<<"NumberException!"<<endl; } system("pause"); return 0; }
结果:
1
0
//输出
ZeroException!
NumberException!
抛出异常时,先通过ZeroException异常处理,处理后继续抛出,外面的NumberException接收。
5.使用catch(...)可以捕获所有的异常:
catch (...) { cout<<"I can solve all Exceptions!"<<endl; }
6.为了加强程序的可读性,使函数的用户能够方便地知道所使用的函数会抛出哪些异常,可以在函数的声明中列出这个函数可能抛出的所有异常类型,例如:
void fun() throw( A,B,C,D);
这表明函数fun()可能并且只可能抛出类型(A,B,C,D)及其子类型的异常。
如果在函数的声明中没有包括异常的接口声明,则此函数可以抛出任何类型的异常,例如:
void fun();
一个不会抛出任何类型异常的函数可以进行如下形式的声明:
void fun() thow();