一、引入异常的意义
1、C语言中错误的处理通常采用返回值的方式或者置位全局变量的方式。这就存在两个问题:
1> 如果返回值正是我们需要的数据,且返回数据同出错数据容错差不高。全局变量,在多线程中易引发竞争;
2> 而且,当错误发生时,上级错误要出过处理,层层上报,造成过多的出错处理代码,且传递的效率低下,为此C++提供了异常。
2、C++的异常处理机制使得异常的引发和异常的处理不必再用一个函数中,这样底层的函数可以着重解决具体问题,而不必过多的考虑异常的处理。上层调用者可以在适当的位置设计对不同类型异常的处理。
3、异常是专门针对抽象编程中的一系列错误处理的,C++中不能借助函数机制,因为栈结构的本质是先进后出,依次访问,无法进行跳跃,但错误处理的特征却是遇到错误信息就想要转到若干级之上进行重新尝试。
4、异常超脱于函数机制,决定了其对函数的跨越式回跳,异常跨越函数。
二、示例
#include<iostream>
using namespace std;
double triangle(double x, double y, double z) {
double area = 0;
double s = (x + y + z) / 2;
area = sqrt(s * (s - x) * (s - y) * (s - z));
return area;
}
int main() {
double x, y, z;
cin >> x >> y >> z;
while (x > 0 && y > 0 && z > 0 && (x + y) > z && (x + z) > y && (y + z) > x) {
cout << triangle(x, y, z) << endl;
cin >> x >> y >> z;
}
system("pause");
return 0;
}
对于上面求三角形面积的函数调用,因为不是所有的随机三个数都可以组成一个三角形,这就需要对输入的值做出一个判断。输出结果:
上面是C语言的一种解决办法,下面使用C++异常处理:
#include<iostream>
using namespace std;
double triangle(double x, double y, double z) {
double area = 0;
double s = (x + y + z) / 2;
if (x > 0 && y > 0 && z > 0 && (x + y) > z && (x + z) > y && (y + z) > x)
area = sqrt(s * (s - x) * (s - y) * (s - z));
else
throw - 1;
return area;
}
int main() {
double x, y, z;
try {
while (cin >> x >> y >> z) {
cout << triangle(x, y, z) << endl;
}
}
catch (int e) {
cout << x << "," << y << "," << z << "无法构成三角形" << endl;
}
system("pause");
return 0;
}
输出结果:
<1>
try {
while (cin >> x >> y >> z) {
cout << triangle(x, y, z) << endl;
}
}
这里把会抛出的函数放入到try语句块中.
<2>
if (x > 0 && y > 0 && z > 0 && (x + y) > z && (x + z) > y && (y + z) > x)
area = sqrt(s * (s - x) * (s - y) * (s - z));
else
throw - 1;
底层函数自己解判断构不成三角形这个问题,如果构不成就抛出一个异常,交由上层函数处理。当底层函数抛出异常时,throw后面的语句包括return都不会执行,直接跳回上一级函数。
<3>
catch (int e) {
cout << x << "," << y << "," << z << "无法构成三角形" << endl;
}
如果底层函数抛出了异常,上层函数会通过抛出异常的类型进行捕获,捕获到了就会进行处理。
如果抛出的异常类型与捕获的异常类型无法匹配,像这样:
#include<iostream>
using namespace std;
double triangle(double x, double y, double z) {
double area = 0;
double s = (x + y + z) / 2;
if (x > 0 && y > 0 && z > 0 && (x + y) > z && (x + z) > y && (y + z) > x)
area = sqrt(s * (s - x) * (s - y) * (s - z));
else
throw (double)1;
return area;
}
int main() {
double x, y, z;
try {
while (cin >> x >> y >> z) {
cout << triangle(x, y, z) << endl;
}
}
catch (int e) {
cout << x << "," << y << "," << z << "无法构成三角形" << endl;
}
system("pause");
return 0;
}
程序就会报错,这里就是系统调用了系统函数terminate,终止了程序。
三、语法格式
try{
被检查的语句
}
catch(异常信息类型 [变量名]){
进行异常处理的语句
}
注意:
1、这里的大括号不能省略;
2、被检查的语句必须放在try快中,否则不起作用;
3、一个try-catch结构,只能有一个try块,至少有一个catch块,以便捕获throw的不同类型的信息。
4、throw抛出的类型,既可以是系统预定义的标准类型也可以是自定义类型。从抛出到catch是一次复制拷贝的过程,如果有自定义类型,要考虑自定义类型的拷贝问题。
那是拿刚才的triangle函数举个例子:
#include<iostream>
using namespace std;
class A {
public:
A() {
cout << "A()" << endl;
}
A(const A& another) {
cout << "A(const A& another)" << endl;
}
~A() {
cout << "~A()" << endl;
}
};
double triangle(double x, double y, double z) {
double area = 0;
double s = (x + y + z) / 2;
if (x > 0 && y > 0 && z > 0 && (x + y) > z && (x + z) > y && (y + z) > x) {
area = sqrt(s * (s - x) * (s - y) * (s - z));
}
else
throw A();
return area;
}
int main() {
double x, y, z;
try {
while (cin >> x >> y >> z)
cout << triangle(x, y, z) << endl;
}
catch (A e) {
cout << x << "," << y << "," << z << "构不成三角形" << endl;
}
system("pause");
return 0;
}
这里让throw抛出的是A类型,输出结果:
可以看到发生了一次构造,一个拷贝构造,构造时throw A()这个地方发生的,拷贝构造则是跳回到catch时发生的。最后出了catch块后发生两次析构。throw创建的A类对象一直到上一级的函数中的catch块结束才析构。
5、如果catch语句没有匹配异常类型信息,就可以用(…)表示可以捕获任何异常类型的信息,就相当于switch case里的default。
#include<iostream>
using namespace std;
class A {
public:
A() {
cout << "A()" << endl;
}
A(const A& another) {
cout << "A(const A& another)" << endl;
}
~A() {
cout << "~A()" << endl;
}
};
double triangle(double x, double y, double z) {
double area = 0;
double s = (x + y + z) / 2;
if (x > 0 && y > 0 && z > 0 && (x + y) > z && (x + z) > y && (y + z) > x) {
area = sqrt(s * (s - x) * (s - y) * (s - z));
}
else
throw (double)1;
return area;
}
int main() {
double x, y, z;
try {
while (cin >> x >> y >> z)
cout << triangle(x, y, z) << endl;
}
catch (A e) {
cout << x << "," << y << "," << z << "构不成三角形" << endl;
}
catch (...) {
cout << "catch default" << endl;
}
system("pause");
return 0;
}
输出结果:
6、try-catch结构可以与throw在同一个函数中,也可以在不同的函数中,throw抛出异常后,先在本函数内寻找与之匹配的catch块,找不到与之匹配的就转到上一层,如果上一层也没有,就转到更上一层。如果最终找不到与之匹配的catch块,系统则会调用系统函数terminate,使程序终止。
#include<iostream>
using namespace std;
void h() {
char a = 'a';
throw a;
cout << "h() end" << endl;
}
void g() {
try {
h();
}
catch (char e) {
cout << "g() catch" << endl;
}
cout << "g() end" << endl;
}
void f() {
try {
g();
}
catch(char a){
cout << "f() catch" << endl;
}
cout << "f() end" << endl;
}
int main() {
try {
f();
}
catch (char a) {
cout << "main() catch" << endl;
}
cout << "main() end" << endl;
system("pause");
return 0;
}
输出结果:
如果把h抛出的类型改成double:
#include<iostream>
using namespace std;
void h() {
double a = 1;
throw a;
cout << "h() end" << endl;
}
void g() {
try {
h();
}
catch (char e) {
cout << "g() catch" << endl;
}
cout << "g() end" << endl;
}
void f() {
try {
g();
}
catch(char a){
cout << "f() catch" << endl;
}
cout << "f() end" << endl;
}
int main() {
try {
f();
}
catch (double a) {
cout << "main() catch" << endl;
}
cout << "main() end" << endl;
system("pause");
return 0;
}
输出结果:
可以看到一直传转到main函数中匹配上doule类型才捕获到, 如果main函数中catch的异常类型也无法和throw的类型匹配:
#include<iostream>
using namespace std;
void h() {
double a = 1;
throw a;
cout << "h() end" << endl;
}
void g() {
try {
h();
}
catch (char e) {
cout << "g() catch" << endl;
}
cout << "g() end" << endl;
}
void f() {
try {
g();
}
catch(char a){
cout << "f() catch" << endl;
}
cout << "f() end" << endl;
}
int main() {
try {
f();
}
catch (char a) {
cout << "main() catch" << endl;
}
cout << "main() end" << endl;
system("pause");
return 0;
}
匹配不到这里就报错了,系统给中断了。
四、抛出类型声明
1、为了加强程序的可读性,可以在函数声明中列出可能抛出的所有异常类型。
例如:
void func() throw(A, B, C, D); //表示这个函数func()能够且只能抛出类型A B C D 及其子类型的异常。
#include<iostream>
using namespace std;
class A{};
class B : public A {};
void h() throw(A){
B b ;
throw b;
cout << "h() end" << endl;
}
void g() {
try {
h();
}
catch (char e) {
cout << "g() catch" << endl;
}
cout << "g() end" << endl;
}
void f() {
try {
g();
}
catch(char a){
cout << "f() catch" << endl;
}
cout << "f() end" << endl;
}
int main() {
try {
f();
}
catch (A a) {
cout << "main() catch" << endl;
}
cout << "main() end" << endl;
system("pause");
return 0;
}
输出结果:
可以看到throw声明之后可以抛出A的子类B类型,若抛出AB外的其他类型则报错。
2、如果在函数声明中没有包含一场接口声明,则函数可以抛出任何类型的异常,例如:
void func();
3、一个不抛出任何异常的函数可以声明为:
void func() throw();
4、如果一个函数抛出了它的异常接口声明所不允许抛出的异常,unexpected函数会被调用,该函数默认行为调用terminate函数终止函数。
五、栈自旋
异常被抛出后,从进入try块其,到异常被抛出前,这期间在栈上构造的所有对象都会被自动析构。析构的顺序与构造的顺序相反。这一过程称为栈的解旋(unwinding)。
#include<iostream>
using namespace std;
class A{
public:
A() {
cout << "A()" << endl;
}
~A() {
cout << "~A()" << endl;
}
};
void h() {
A a;
throw 'a';
}
void g() {
A b;
h();
}
int main() {
try {
g();
}
catch (A a) {
cout << "main() catch" << endl;
}
catch (...) {
cout << "catch default" << endl;
}
//cout << "main() end" << endl;
system("pause");
return 0;
}
输出结果:
发生两次构造,两次析构。
如果是堆上的空间,则会造成内存泄漏了:
#include<iostream>
using namespace std;
class A{
public:
A() {
cout << "A()" << endl;
}
~A() {
cout << "~A()" << endl;
}
};
void h() {
A* a = new A;
throw 'a';
}
void g() {
A* b = new A;
h();
}
int main() {
try {
g();
}
catch (A a) {
cout << "main() catch" << endl;
}
catch (...) {
cout << "catch default" << endl;
}
//cout << "main() end" << endl;
system("pause");
return 0;
}
输出结果:
发生了两次构造,但并没有析构,造成内存泄漏。