异常处理
异常是指程序在运行过程中因环境条件出现意外或用户操作不当而导致程序不能正常运行。
其中有些异常情况是可以预料但不可避免的,例如在申请堆内存空间时,因内存空间不足使分配空间操作失败;
访问磁盘文件时因文件不存在使打开文件操作失败;执行打印操作时因打印机未打开使打印出错;
执行除法运算时因除数为0而出错等。对于这些可能发生的意外情况,在编写程序时应充分考虑并给予适当的处理,尽可能做到排除错误,使程序继续运行或者给出适当的提示信息后中断程序的运行。
异常Exception是一种不常见或者不可预见的情况,经常导致中断正常的程序流。
常见的可能产生异常的操作:数值越界,文件操作,内存分配,Windows资源,实时生成的对象与窗体,硬件和操作系统产生的冲突等。
异常处理的方法
异常处理的方法有多种,可以在可能发生错误的程序段中直接加上错误处理的代码,这样做的好处是阅读程序时能够直接看到对错误进行处理的情况,不足之处是使程序变得繁琐、可读性差。C++的异常处理机制允许发生异常的代码与处理异常的代码不在同一函数中。
C++异常处理机制中实现异常处理的方法是:
- 监测异常:将可能发生异常的代码包含在try语句块中。
- 抛掷异常:在try代码块中或其调用函数的代码中可能发生异常的地方进行检测,若检测到异常,则用throw语句抛出异常。
- 捕获异常:在try代码块后给出catch代码块,在其中给出处理异常的语句,用于捕获和处理异常。
如果一个异常没有被调用链中的任何函数捕捉到,那么在主函数捕捉该异常失败之后,按照默认,该程序就会自动调用abort()函数来终止
throw语句的一般形式如下:
throw <表达式>;
try catcht 语句的一般形式如下:
try{
//try语句块
} catch(类型1 [变量1]){
//针对类型1的异常处理语句块
}catch(类型2 [变量2]) {
//针对类型2的异常处理语句块
}...catch(类型N [变量N]){
//针对类型N的异常处理语句块
}catch(...){
//处理任何类型异常,放在最后
}
#include <iostream>
using namespace std;
int Div(int x,int y) {
if(y==0) throw y; //如果除数为0,throw语句抛掷整型异常
return x/y;
}
int main() {
try {
//由于除法运算可能出现除0异常,因此放在try语句块中监测
cout<<"7/3="<<Div(7,3)<<endl;
cout<<"7/0="<<Div(7,0)<<endl;
cout<<"7/1="<<Div(7,1)<<endl; //该语句不被执行
} catch(int i) {
//catch语句块捕获整型异常并进行处理
cout<<"exception of dividing zero\n";
}
cout<<"That is ok.\n";
return 0;
}
(1)主函数中调用了计算两个整数相除结果的函数Div,当除数为0时Div函数的运行将发生除0异常,所以在主函数中将调用Div函数的三条语句包含在try语句块中进行监测。
(2)当执行到try块的第二条语句时,调用了函数Div(7,0),由于除数为0,在Div函数中抛掷了一个异常throw y,其中y为整数,该异常为整型异常。这时将退出div函数,从这一点来看throw语句的作用类似于return语句。
(3)与return语句不同的是退出Div函数后,不是返回调用该函数语句的下一条语句:cout<<“7/1=”<<Div(7,1)<<endl;
继续执行,而是在try语句块后寻找能捕获整型异常的catch语句块。
try语句块后只有一个catch语句块且该语句块带一个整型参数i,该catch语句块可以捕获整型异常。
(4)catch语句块的形参i被初始化为throw语句中y的值,因i在catch块中未用到,故可将i省略,但参数类型不能省略,然后进行退出Div函数的退栈处理并释放相关的资源。
(5)执行完catch语句块中处理异常的语句后,则执行catch块后的第一条语句:
cout<<“That is ok.\n”;
#include<iostream>
#include<cmath>
#include<cstdio>
using namespace std;
int Div(int x,int y){
if(y==0) throw y;
return x/y;
}
double Sqrt(double y){
if(y<0) throw y;
return sqrt(y);
}
int main(){
double a,b; cin>>a>>b;
try{
cout<<"div("<<a<<"/"<<b<<")="<<Div(a,b)<<endl;
cout<<"sqrt("<<b<<")="<<Sqrt(b)<<endl;
}catch(int){
cout<<"exception of dividing zero\n";
} catch(...){
//处理任何类型异常
cout<<"caught exception\n";
}
cout<<"That is ok.\n";
return 0;
}
异常处理的规则
编写异常处理程序时应注意遵守以下规则:
(1)应将可能发生异常的程序代码段包括对某个函数的调用包含在try块中,以便对其进行监控,C++异常处理机制只对受监控代码段发生的异常进行处理。
(2)在受监控代码段中所有可能发生异常之处加上检测语句,一旦检测到异常情况,立即用throw语句抛掷这个异常。throw语句抛掷的异常的类型由throw后所跟的表达式值的类型所决定,如代码段中有多处要抛掷异常,应该用不同类型的操作数加以区别,类型相同而仅仅值不相同的操作数不能区别不同的异常。
(3)throw语句抛掷的异常由try语句块后的catch语句块捕获并处理,catch语句块可以有多个,通常每个catch语句块捕获一种类型的异常,捕获异常的类型由catch语句块参数类型所决定,catch块可以捕获其参数类型及其派生类型的异常。当catch语句块中未用到参数时,可只给出参数类型声明而省略参数名。
(4)当catch语句块的参数类型声明是一个省略号(…)时,该catch语句块可以捕获和处理任何类型的异常,该catch语句块应放在所有其它catch块的后面。
(5)如果抛掷异常后未找到能够处理该类异常的catch块,则自动调用运行函数terminate,该函数调用abort函数终止程序的运行。
#include<iostream>
using namespace std;
void f(int x) {
try {
if(x>0) throw 2;
if(x==0) throw 'a';
if(x<0) throw 3.14;
} catch(int n) {
cout<<"输入的为正数:"<<x<<endl;
} catch(char m) {
cout<<"输入的为零:"<<x<<endl;
} catch(double k) {
cout<<"输入的为负数:"<<x<<endl;
} catch(...) {
cout<<"捕获所有类型的异常!"<<"输入的数字是:"<<x<<endl;
}
}
int main() {
f(4); f(0); f(-5);
return 0;
}
#include<iostream>
using namespace std;
void f() {
try {
throw 'a'; //重抛异常
} catch(char x) {
cout<<"内层异常处理!"<<endl;
throw 'b';
}
}
int main() {
try {
f();
} catch(char x) {
cout<<"外层异常处理!"<<endl;
}
return 0;
}
#include<iostream>
using namespace std;
//演示C++中的异常处理对象的构造和析构
class Demo {
public:
int x;
Demo(int y) {
x = y;
cout<<"进入Demo类的构造函数"<<endl;
if(x<0) throw x;
else cout<<"调用Demo类的构造函数,构造对象"<<endl;
}
~Demo() {
cout<<"调用Demo类的析构函数,析构对象"<<endl;
}
};
void func() {
cout<<"进入函数func"<<endl;
Demo d1(4), d2(-8);
throw 'A';
}
int main() {
cout<<"主函数开始执行"<<endl;
try {
cout<<"调用func函数"<<endl;
func();
} catch(int n) {
cout<<"对象"<<n<<"发生错误"<<endl;
} catch(char m) {
cout<<"在函数中抛出异常"<<endl;
}
cout<<"主函数执行完毕"<<endl;
return 0;
}
例:异常处理中类对象的析构
#include <iostream>
using namespace std;
void func();
class Expt {
public:
Expt() {
}
~Expt() {
}
char *showReason()const {
return "Expt类异常。";
}
};
class Demo {
public:
Demo() {
cout<<"构造Demo。"<<endl;
}
~Demo() {
cout<<"析构Demo。"<<endl;
}
};
void func() {
Demo d;
cout<<"在func函数中抛掷Expt类异常。"<<endl;
throw Expt(); //创建Expt类无名对象以抛掷Expt类异常
}
int main() {
cout<<"在主函数中。"<<endl;
try {
cout<<"在try块中调用func函数。"<<endl;
func();
} catch(Expt e) {
//捕获Expt类异常
cout<<"在catch异常处理程序中。"<<endl;
cout<<"捕获到Expt类型异常:"<<e.showReason()<<endl;
} catch(char *str) {
cout<<"捕获到其它类型的异常:"<<str<<endl;
}
cout<<"回到main函数,从这里恢复执行。"<<endl;
return 0;
}
程序运行结果:
在主函数中。
在try块中调用func函数。
构造Demo。
在func函数中抛掷Expt类异常。
析构Demo。
在catch异常处理程序中。
捕获到Expt类型异常:Expt类异常
回到main函数,从这里恢复执行。
例:构造函数中发生异常的处理
#include<iostream>
using namespace std;
class Birthday {
public:
void init(int y,int m,int d) {
if(y<1||m<1||m>12||d<1||d>31) throw y;
}
Birthday(int y,int m, int d):year(y),month(m),day(d) {
init(y,m,d);
cout<<"Birthday\n";
}
void display() {
cout<<"Birthday:"<<year<<"/"<<month<<"/"<<day<<endl;
}
~Birthday() {
cout<<"~Birthday\n"; //构造不成功,析构函数不执行
}
private:
int year, month, day;
};
int main() {
try {
Birthday birth(1996,13,0);
birth.display();
} catch(int) {
cout<<"Object Creation Failed!"<<endl;
}
return 0;
}
程序运行结果:
Object Creation Failed!
例:有子对象的派生类构造函数中发生异常的处理
#include <iostream>
using namespace std;
class Birthday {
public:
Birthday(int y=2000,int m=01,int day=01) {
cout<<"Construct Birthday."<<endl;
}
~Birthday() {
cout<<"Destruct Birthday."<<endl;
}
private:
int year, month, day;
};
class Father {
public:
Father(int i):age(i) {
cout<<"Construct Father."<<endl;
}
~Father() {
cout<<"Destruct Father."<<endl;
}
protected:
int age;
};
class Son: public Father {
public:
Son(int age):Father(age) {
//包含父类对象和子对象的构造函数
cout<<"Construct Son."<<endl;
throw age; //执行子对象类和父类的析构函数,不执行派生类的析构函数
}
~Son() {
cout<<"Destruct Son."<<endl;
}
private:
Birthday bir; //子对象
};
int main() {
try {
Son(19);
} catch(int) {
cout<<"Exception."<<endl;
}
return 0;
}
程序运行结果:
Construct Father.
Construct Birthday.
Construct Son.
Destruct Birthday.
Destruct Father.
Exception.