C++异常处理

  • 在C++发展过程中,有的C++编译系统根据实际需要,增加了一些功能,作为工具来使用,其中主要有模板(包括函数模板和类模板)、异常处理命名空间运行时类型识别,以帮助程序设计人员更方便地进行程序的设计和调试工作。
  • 函数模板:所谓函数模板,实际上是建立一个通用函数,它所用到的数据的类型(包括返回值类型、形参类型、局部变量类型)可以不具体指定,而是用一个虚拟的类型来代替(实际上是用一个标识符来占位),等发生函数调用时再根据传入的实参来逆推出真正的类型。这个通用函数就称为函数模板(Function Template)。
  • C++除了支持函数模板,还支持类模板(Class Template)。函数模板中定义的类型参数可以用在函数声明和函数定义中,类模板中定义的类型参数可以用在类声明和类实现中。类模板的目的同样是将数据的类型参数化。

异常处理

  • 程序中常见的错误有两大类:语法错误运行错误
  • 语法错误如:关键字拼写错误,变量名未定义,语句末尾缺分号,括号不匹配等,由于是在编译阶段发现的错误,因此这类错误又称编译错误
  • 运行错误如:内存空间不够,无法实现指定操作、输入数据时数据类型有错等,这些程序能正常通过编译,也能正常运行。但是在运行过程中会出现异常,得不到正确的运行结果,甚至导致程序不正常终止,或出现死机现象。

异常处理的任务

  • 在设计程序时,应当事先分析程序运行时可能出现的各种意外的情况,并且分别制订出相应的处理方法,这就是程序的异常处理的任务
  • 在一般情况下,异常指的是出错(差错),但是异常处理并不完全等同于对出错的处理。只要出现与人们期望的情况不同,都可以认为是异常,并对它进行异常处理。因此,所谓异常处理指的是对运行时出现的差错以及其他例外情况的处理

异常处理的方法

  • C++采取的办法是:如果在执行一个函数过程中出现异常,可以不在本函数中立即处理,而是发出一个信息,传给它的上一级(即调用它的函数),它的上级捕捉到这个信息后进行处理。如果上一级的函数也不能处理,就再传给其上一级,由其上一级处理。如此逐级上送,如果到最高一级还无法处理,最后只好异常终止程序的执行。
  • 这样做使异常的发现与处理不由同一函数来完成。好处是使底层的函数专门用于解决实际任务,而不必再承担处理异常的任务,以减轻底层函数的负担,而把处理异常的任务上移到某一层去处理。这样可以提高效率。
  • C++处理异常的机制是由3个部分组成的,即检查(try)、抛出(throw)和捕捉(catch)。把需要检查的语句放在try块中,throw用来当出现异常时发出一个异常信息,而catch则用来捕捉异常信息,如果捕捉到了异常信息,就处理它。

例1:给出三角形的三边a,b,c,求三角形的面积。只有a+b>c,b+c>a,c+a>b时才能构成三角形。设置异常处理,对不符合三角形条件的输出警告信息,不予计算

#include <iostream>
#include <cmath>
using namespace std;
int main()
{
	double triangle(double, double, double); //函数声明
	double a, b, c;
	cout << "输入a,b,c的值:";
	cin >> a >> b >> c; //输入3个边
	try //在try块中包括要检查的函数
	{
		while (a > 0 && b > 0 && c > 0)
		{
			cout << triangle(a, b, c) << endl; //输出三角形的面积
			cout << endl << "输入a,b,c的值:";
			cin >> a >> b >> c; //输入3个边
		}
	}
	catch(double) //用catch捕捉异常信息并作相应处理
	{
		cout << "a=" << a << ",b=" << b << ",c=" << c << ",that is not a triangle!" << endl;
	}
	cout << "end" << endl; //最后输出"end"
	return 0; //返回0,程序正常结束
}

double triangle(double a, double b, double c) //定义计算三角形的面积的函数
{
	double area;
	double s = (a + b + c) / 2;
	if (a + b <= c || b + c <= a || c + a <= b) throw a; //当不符合三角形条件抛出异常信息a
	cout << "S=" ;
	return sqrt(s * (s - a) * (s - b) * (s - c));
}

程序分析:

  • 把可能出现异常的、需要检查的语句或程序段放在try后面的花括号中。
  • 程序开始运行后,按正常的顺序执行到try块,开始执行try块中花括号内的语句。如果在执行try块内的语句过程中没有发生异常,则catch子句不起作用,流程转到catch子句后面的语句继续执行。
  • 如果在执行try块内的语句(包括其所调用的函数)过程中发生异常,则throw语句抛出一个异常信息。throw抛出double类型的异常信息a。执行了throw语句后,流程立即离开本函数,转到其上一级的函数(main函数)。因此不会执行triangle函数中if语句之后的return语句。
  • 这个异常信息提供给try-catch结构,系统会寻找与之匹配的catch子句。现在a是double型,而catch子句的括号内指定的信息的类型也是double型,二者匹配,即catch捕获了该异常信息,这时就执行catch子句的语句。
  • 在进行异常处理后,程序并不会自动终止,继续执行catch子句后面的语句。注意:处理异常后,并不是从出现异常点继续执行while循环。如果在try块的花括号内有10个语句,在执行第3个语句时出现异常,则在处理完该异常后,其余7个语句不再执行,而转到catch子句后面的语句去继续执行。
  • 由于catch子句是用来处理异常信息的,往往被称
    catch异常处理块catch异常处理器

运行结果:

throw语句的形式为:throw 表达式;
try-catch的结构为:

try
	{被检查的语句}
catch(异常信息类型[变量名])
	{进行异常处理的语句}

说明:

  1. 被检测的函数必须放在try块中,否则不起作用
  2. try块和catch块作为一个整体出现,catch块是try-catch结构中的一部分,必须紧跟在try块之后,不能单独使用,在二者之间也不能插入其他语句。但是在一个try-catch结构中, 可以只有try块而无catch块。即在本函数中只检查而不处理,把catch
    处理块放在其他函数中。
  3. try和catch块中必须有用花括号括起来的复合语句,即使花括号内只有一个语句,也不能省略花括号
  4. 一个try-catch结构中只能有一个try块, 但可以有多个catch块,以便与不同的异常信息匹配
  5. catch后面的圆括号中,一般只写异常信息的类型名,如:catch(double)。
  • catch只检查所捕获异常信息的类型,而不检查它们的值。因此如果需要检测多个不同的异常信息,应当由throw抛出不同类型的异常信息。
  • 异常信息可以是C++系统预定义的标准类型,也可以是用户自定义的类型(如结构体或类)。如果由throw抛出的信息属于该类型或其子类型,则catch与throw二者匹配,catch捕获该异常信息。
  • catch还可以有另外一种写法,即除了指定类型名外,还指定变量名,如:catch(double d)。此时如果throw抛出的异常信息是double型的变量a,则catch在捕获异常信息a的同时,还使d获得a的值,或者说d得到a的一个拷贝。什么时候需要这样做呢?有时希望在捕获异常信息时,还能利用throw抛出的值,如:catch(double d) {cout<<"throw "<<d;},这时会输出d的值(也就是a值)。当抛出的是类对象时,有时希望在catch块中显示该对象中的某些信息。这时就需要在catch的参数中写出变量名(类对象名)。
  1. 如果在catch子句中没有指定异常信息的类型,而用了删节号"…",则表示它可以捕捉任何类型的异常信息,如:catch(...) {cout<<"ERROR"<<endl;}。它能捕捉所有类型的异常信息,并输出"ERROR"。这种catch子句应放在try-catch结构中的最后,相当于"其他"。如果把它作为第一个catch子句,则后面的catch子句都不起作用。
  2. try-catch结构可以与throw出现在同一个函数中,也可以不在同一函数中。当throw抛出异常信息后,首先在本函数中寻找与之匹配的catch,如果在本函数中无try-catch结构或找不到与之匹配的catch,就转到离开出现异常最近的try-catch结构去处理。
  3. 在某些情况下,在throw语句中可以不包括表达式,如"throw":
catch(int)
{ //其他语句
	throw; //将已捕获的异常信息再次原样抛出
}

表示"我不处理这个异常,请上级处理"。此时catch块把当前的异常信息再次抛出,给其上一层的catch块处理。

  1. 如果throw抛出的异常信息找不到与之匹配的catch块,那么系统就会调用一个系统函数terminate,使程序终止运行。

例:在函数嵌套的情况下检测异常处理

#include <iostream>
using namespace std;
int main()
{
	void f1();
	try
	{
		f1(); //调用f1() 
	} 
	catch (double)
	{
		cout << "ERROR0!" << endl;
	}
	cout << "end0" << endl; return 0;
}
void f1()
{
	void f2();
	try
	{
		f2(); //调用f2()
	} 
	catch (char)
	{
		cout << "ERROR1!";
	}
	cout << "end1" << endl;
}
void f2()
{
	void f3();
	try
	{
		f3(); //调用f3()
	}
	catch (int)
	{
		cout << "ERROR2!" << endl;
	}
	cout << "end2" << endl;
}
void f3()
{
	double a = 0;
	try
	{
		throw a; //抛出double类型异常信息
	}
	catch (float)
	{
		cout << "ERROR3!" << endl;
	}
	cout << "end3" << endl;
}

程序分析:

  • 在main函数中的try块调用f1函数,在f1函数的try块中调用f2函数,在f2函数的try块中义调用f3函数。在执行f3函数过程中执行了throw语句,抛出double型异常信息a。由于在f3中没有找到和a类型相匹配的catch子句,于是将a抛给f3的调用者f2函数,在f2中还没有找到和a类型相匹配的catch子句,又将a抛给f2的凋用者f1函数,在f1中也没找到和a类型相匹配的catch子句,又将a抛给f1的调用者main函数,在main函数中的try-catch结构中找到和a类型相匹配的catch子句。

运行结果:

如果将f3函数中的catch子句改为catch(double),程序中其它部分不变,则f3函数中的throw抛出的异常信息立即被f3函数的catch子句捕获(因为抛出的是double型异常信息a,而catch要捕捉的也是double型异常信息,二者匹配)。输出“ERROR3!”,再执行catch子句后面的语句,输出“end3”。F3函数执行结束后,流程返回f2函数,继续往下执行。

如果在此基础上再将f3函数中的catch块改为

catch (double)
{
	cout << "ERROR3!" << endl;
	throw;
}

f3函数中的throw抛出的异常信息a,被f3函数的catch子句捕获,输出"ERROR3!",但它即用"throw;"将a再抛出,于是a被main函数中的catch子句捕获。

在函数声明中进行异常情况指定

  • C++允许在声明函数时列出可能抛出的异常类型,如:double triangle(double, double, double) throw(double);,表示triangle函数只能抛出double类型的异常信息。
  • 如果写成double triangle(double, double, double) throw(int, double, float, char);,则表示triangle函数只限于抛出int,double,float或char类型的异常信息。
  • 异常指定是函数声明的一部分,必须同时出现在函数声明和函数定义的首行中,否则在进行函数的另一次声明时,编译系统会报告"类型不匹配"。
  • 如果在声明函数时未列出可能抛出的异常类型,则该函数可以抛出任何类型的异常信息。如果想声明一个不能抛出异常的函数,可以写成以下形式:double triangle(double, double, double) throw(); //throw无参数。这时即使在函数执行过程中出现了throw语句,实际上也并不执行throw语句,并不抛出任何异常信息,程序将非正常终止。

在异常处理中处理析构函数

  • 如果在try块(或try块中调用的函数)中定义了类对象,在建立该对象时要调用构造函数。在执行try块(包括在try块中调用其他函数)的过程中如果发生了异常,此时流程立即离开try块。这样流程就有可能离开该对象的作用域而转到其他函数,因而应当事先做好结束对象前的清理工作,C++的异常处理机制会在throw抛出异常信息被catch捕获时,对有关的局部对象进行析构(调用类对象的析构函数),析构对象的顺序与构造的顺序相反,然后执行与异常信息匹配的catch块中的语句。

例:在异常处理中处理析构函数

#include <iostream>
#include <string>
using namespace std;
class Student
{
public:
	Student(int n, string nam) //定义构造函数
	{
		cout << "constructor-" << n << endl;
		num = n;
		name = nam;
	}
	~Student() { cout << "destructor-" << num << endl; } //定义析构函数
	void get_data(); //成员函数声明
private:
	int num;
	string name;
};

void Student::get_data() //定义成员函数
{
	if (num == 0) throw num; //如num=0, 抛出int型变量num
	else cout << num << " " << name << endl; //若num≠0,输出num, name
	cout << "in get_data( )" << endl; //输出信息,表示目前在get_data函数中
}

void fun()
{
	Student stud1(1101, "Tan"); //建立对象stud1
	stud1.get_data(); //调用stud1的get_data函数
	Student stud2(0, "Li"); //建立对象stud2
	stud2.get_data(); //调用stud2的get_data函数
}

int main()
{
	cout << "main begin" << endl; //表示主函数开始了
	cout << "call fun( )" << endl; //表示调用fun函数
	try
	{
		fun(); //调用fun函数
	}
	catch (int n)
	{
		cout << "num=" << n << ",error!" << endl; //表示num=0出错
		cout << "main end" << endl; //表示主函数结束
	}
	return 0;
}

程序分析:

  • 在main函数中的catch处理器捕获到异常信息num,并将num的值赋给了变量n。此时开始进行析构工作。仔细分析流程,注意从主函数中的try块开始到执行throw语句抛出异常信息这段过程中,有没有已构造而未析构的局部对象。可以看到:在执行try块中的fun函数过程中,先后建立了两个对象stud1和stud2。在fun函数调用 stud2的get_data()函数时,执行throw语句,流程离开get_data()函数,返回fun函数。由于在fun函数中没有catch函数,流程又离开fun函数返回main函数。在结束fun函数之前,需要释放对象stud1和stud2,要调用析构函数进行清理工作。

运行结果:

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值