在C++发展后期,加了一些功能,作为工具来使用,其中主要有模板(包括函数模板和类模板)、异常处理、命名空间和运行时类型识别,以帮助程序设计人员更方便地进行程序设计和调试工作。
程序中常见的错误有两大类:语法错误和运行错误。在编译时,编译系统能发现程序中的语法错误(如关键字拼写错误,变量名未定义,语句末尾缺分号,括号不配对等),编译系统会告知用户在第几行出错。由于是在编译阶段发现的错误,因此这类也称为编译错误。
有的程序虽然能通过编译,也能投入运行。但是在运行过程中会出现异常,得不到正确的运行结果,甚至导致程序不正常终止,或出现死机现象。例如:
- 在一系列计算过程中,出现除数为0的情况。
- 内存空间不够,无地实现指定的操作。
- 无法打开输入文件,因而无法读取数据。
- 输入数据时数据类型有错。
一、异常处理的方法
C++面对程序过于复杂和庞大的异常处理时,采取办法是:如果在执行一个函数过程中出现异常,可以不在本函数立即处理,而是发出一个信息,传给它的上一级(即调用它的函数),它的上级捕捉以这个信息后进行处理。如果上一级函数也不能处理,就再传给其上一级,由其上一级处理。如此逐级上送,如果到最高一级还无法处理,最后只好异常终止程序的执行。
C++处理异常的机制是由3个部分组成的,即检查(try)、抛出(throw)和捕捉(catch)。把需要检查的语句放在try块中,throw用来当出现异常时发出(形象称为抛出,throw的意思是抛出)一个异常信息,而catch则用来捕捉异常信息,旭果捕捉到了异常信息,就处理它。
这里将前面讲的计算三角形面积程序修改一下,使用try...catch来抛出异常信息。
示例代码如下:
#include <iostream>
#include <cmath>
using namespace std;
// 计算三角形面积
double triange(double a, double b, double c){double s = (a + b + c) / 2;if(!((a + b >= c) && (b + c >= a) && (c + a >= b))) throw a;return sqrt(s * (s - a)*(s - b)*(s - c));
}int main(){double a, b, c; //定义变量cout <<"Please enter a value for the three sides of the triangle:" <<endl;try{do{cin >>a >>b >>c;cout <<"Area of a triangle is " <<triange(a, b, c) <<endl;} while(a > 0 && b > 0 && c >0);} catch(double){cout <<"a=" <<a <<", b=" <<b <<", c=" <<c <<", that is not a triangle!" <<endl;cout <<"end" <<endl;}return 0;
}
运行结果如下图:
二、语法
throw语句一般是由throw运算符和一个数据组成的,其形式为
throw 表达式;
try-catch的结构为
try{
// 被检查的语句
} catch( 异常信息类型 [变量名] ) {
// 进行异常处理的语句
}
说明:
(1)被检测的函数必须放在try块中,否则不起作用。
(2)try块和catch块作为一个整体出现,catch块是try-catch结构是的一部分,必须紧跟在try块之后,不能单独使用,二者之间不能插入其他语句。
(3)try和catch块中必须有用花括号括起来的复合语句,即使花括号内只有一个语句,也不能省略花括号。
(4)一个try-catch结构中只能有一个try块,但却可以有多个catch块,以便与不同的异常信息匹配。代码如下:
try{//...
} catch(double){//...
} catch(int){//...
} catch(char){//...
}
(5)catch后面的圆括号中,一般只写异常信息的类型名,代码如下:
try{//...
} catch(double){//...
}
(6)catch还可以有另外一个写法,即除了指定类型名外,还可以指定变量名,代码如下:
try{//...
} catch(double a){cout <<"Error Value:" <<a <<endl;
}
(7)如果在catch子句中没有指定异常信息的类型,而用了删节号“...”,则表示它可以捕捉任何类型的异常信息,代码如下:
try{//...
} catch(...){cout <<"All type error" <<endl;
}
(8)try-catch结构可以与throw出现在同一个函数中,也可以不在同一函数中。当throw抛出异常信息后,首先在本函数中寻找与之匹配的catch,如果在本函数中无try-catch结构或找不到与之匹配的catch,就转到其上一层去处理,如果其上一层也无try-catch结构或找不到与之匹配的catch,则再转到更上一层的try-catch结构去处理,也就是说转到离开出现异常最近的try-catch结构去处理。
(9)在某些情况下,在throw语句中可以不包括表达式,代码如下:
throw;
(10)如果throw抛出的异常信息找不到与之匹配的catch块,那么系统就会调用一个系统函数terminate,使程序终止运行。
三、在函数声明中进行异常情况处理
为了便于阅读程序,使用户在看程序时能够知道所用的函数是否会抛出异常信息以及异常信息可能的类型,C++允许在声明函数时列出可能抛出的异常类型。如上面的三角形面积计算函数,可以修改为:
// 已废弃(不推荐使用)
double triange(double a, double b, double c) throw(double)
或者可以抛出多类型int,double,float或char类型的异常信息。代码如下:
// 已废弃(不推荐使用)
double triange(double a, double b, double c) throw(int, double, float, char)
不过,在 C++11 及以后的版本中,函数声明中的异常规格(exception specifications)已经被弃用,并建议使用 noexcept 关键字来替代。noexcept 关键字用于指示函数是否保证不抛出任何异常。代码修改后如下:
// 使用 noexcept(false)(与 throw() 类似,推荐使用)
double triange(double a, double b, double c) noexcept{ double s = (a + b + c) / 2;if(!((a + b >= c) && (b + c >= a) && (c + a >= b))) terminate();return sqrt(s * (s - a)*(s - b)*(s - c));
}
运行结果如下图:
请注意,如上图可见,如果 noexcept 函数确实抛出了异常,那么程序会调用 std::terminate 来终止,而不是通过常规的异常处理机制(如 catch 块)来处理。
四、在异常处理中处理析构函数
如果try块中定义了类对象,在建立该对象时需要调用构造函数。在执行try块的过程中如果发生了异常,此时流程立即离开try块。这样流程就有可能离开该对象的作用域转到其他函数,因而应当事先做好结束对象前的清理工作。在C++的异常处理机制会在throw抛出异常信息被catch捕获时,对有关的局部对象进行析构,析构对象的顺序与构造的顺序相反,然后执行与异常信息匹配的catch块中的语句。
示例代码如下:
#include <iostream>
#include <string>
using namespace std;
// 定义学生类
class Student{private:int num;string name;public:Student(int num, string name): num(num), name(name){}~Student(){cout <<"destructor Student ~" <<num <<endl; }// 打印学员信息void print(){if(num==0) throw num; //如果学号为0抛出异常cout <<"num:" <<num <<", name:" <<name <<endl;}
};int main(){cout <<"main start" <<endl;try{Student s1(1001, "Tom"), s2(0, "John");// 获取两位学员信息s1.print();s2.print();} catch(int n){cout <<"num=" <<n <<", error!" <<endl;}cout <<"main end" <<endl;return 0;
}
运行后结果如下图: