程序有时会遇到运行阶段错误,导致程序无法正常地运行下去。例如,程序可能试图打开一个不可用的文件,请求过多的内存,或者遭遇不能容忍的值。通常,程序员都会试图预防这种意外情况。C++异常为处理这种情况提供了一种功能强大而灵活的工具。异常是相对较新的C++功能,有些老式编译器可能没有实现。另外,有些编译器默认关闭这种特性,您可能需要使用编译器选项来启用它。
讨论异常之前,先来肴肴程序员可使用的一些基本方法。作为试验,以一个计算两个数的调和平均数 的函数为例。两个数的调和平均数的定义是:这两个数字倒数的平均值的倒数,因此表达式为:
2.0 * x *y / (x + y)
如果y是x的负值,则上述公式将导致被零除——一种不允许的运算。对于被零除的情况,很多新式编译器通过生成一个表示无穷人的特殊浮点值来处理,cout将这种值显示为Inf、inf、INF或类似的东西;而其他的编译器可能生成在发生被零除时崩溃的程序。最好编写在所有系统上都以相同的受控方式运行的代码。
返回错误码
一种比异常终止更灵活的方法是,使用函数的返回值来指出问题。例如,ostream类的get (void) 成员通常返回下一个输入字符的ASCII码,但到达文件尾对,将返回特殊值EOF。下面程序来说, 这种方法不管用。任何数值都是有效的返回值,因此不存在可用于指出问题的特殊值。在这种愔况下,可使用指针参数或引用参数来将值返回给调用程序,并使用函数的返回值來指出成功还是失败。 istream族重载>>运算符使用了这种技术的变体。通过告知调用程序是成功了还是失败了,使得程序可以采取除异常终止程序之外的其他措施。程序淸单是一个采用这种方式的示例,它将nmean()的返时值定义为bool,让返回值指出成功还是失败了。
#include<iostream>
#include<cfloat>
bool nmean(double a,double b,double *ans);
int main()
{
double x,y,z;
std::cout<<"enter two numbers:";
while(std::cin>>x>>y)
{
if(nmean(x,y,&z))
{
std::cout<<"harmonic mean of "<<x<<" and "<<y<<" is "<<z<<std::endl;
}
else
std::cout<<z<<"one value should not be the negative of other -try again.\n";
std::cout<<"enter next set of of numbers (q to quit):";
}
std::cout<<"Bye!\n";
return 0;
}
bool nmean(double a,double b,double *ans)
{
if(a==-b)
{
*ans=DBL_MAX;
return false;
}
*ans=2.0*a*b/(a+b);
return true;
}
运行结果:
异常机制
下面介绍如何使用异常机制来处理错误。C++异常对程序运行过程中发生的异常情况(例如被0除)的一种响应。异常提供了将控制权从程序的一个部分传递到另一部分的途径。对异常的处理有3个组成部分:
• 引发异常:
•使用处理程序捕获好常;
• 使用try块。
程序在出现问题时将引发异常。例如,可以修改上面程序淸中的nmean(),使之引发异常,而不是凋用abort()函数。throw语句实际上是跳转,即命令程序跳到另一条语句。throw关键字表示引发异常,紧随其后的值(例如字符串或对象)指出了异常的特征。
程序使用异常处理程序(exception handler)來捕获异常,异常处理程序位于要处理问题的程序中。catch 关键字表示捕获异常。处理程序以关键字catch开头,随后是位于括号中的类型声明,它指出了异常处理程序要响应的异常类型;然后是一个用花括兮括起的代码块,指出要采取的措施。catch关键宇和异常类型用作标签,指出当异常被引发时,程序应跳到这个位置执行。异常处理程序也被称为catch块。
try块标识其中特定的异常可能被激活的代码块,它后面跟一个或多个catch块。 try块是由关键字try指示的,关键字try的后面进一个由花括号括起的代码块,表明需要注意这些代码引发的异常。
#include<iostream>
#include<cstdlib>
double nmean(double a,double b);
int main()
{
double x,y,z;
std::cout<<"enter two numbers:";
while(std::cin>>x>>y)
{
try{
z=nmean(x,y);
}
catch(const char *s)
{
std::cout<<s<<std::endl;
std::cout<<"enter a new pair of number:";
continue;
}
std::cout<<"harmonic mean of "<<x<<" and "<<y<<" is "<<z<<std::endl;
std::cout<<"enter next set of of numbers (q to quit):";
}
std::cout<<"Bye!\n";
return 0;
}
double nmean(double a,double b)
{
if(a==-b)
{
throw"bad nomean() arguments: a=-b not allowed";
}
return 2.0*a*b/(a+b);
}
运行结果:
在程序中,try块与下面类似:
try { // start of try block
z=nmean(x,y);
} // end of try block
如果其中的某条语句导致异常被引发,则后面的catch块将对异常进行处理。如果程序在try块的外面调用 nmean(),将无法处理异常。
引发异常的代码4下面类似:
if (a == -b)
throw "bad nmean() arguments: a = -b not allowed";
其中被引发的异常是字符串“bad nmean()arguments:a = -b not allowed”。异常类型可以是字符串(就像这个例子中那样)或其他C++类型:通常为类类型。
执行throw语句类似于执行返回语句,因为它也将终止函数的执行;但throw不是将控制权返回给调叫程序,而是导致程序沿函数调用序列后退,直到找到包含try块的函数。在程序中,该函数是调用函数。另外,在这个例了中,throw将程序控制权返回给main()。程序将在main()中寻找与引发的异常类型匹配的异常处理程序(位于try块的后面)。
处理程序(或catch块)与下面类似:
catch(const char *s)
{
std::cout<<s<<std::endl;
std::cout<<"enter a new pair of number:";
continue;
}
catch块点类似于函数定义,但并不是函数定义。关键字catch表明这是一个处理程序,而char *s则表明该处理程序与字符串异常匹配。s与函数参数定义极其类似,因为匹配的引发将被賦给s。另外当异常与该处理程序匹配时,程序将执行括号中的代码。
执行完try块中的语句后,如果没有引发任何异常,则程序跳过try块后面的catch块,直接执行处理程序后面的第一条语句。因此处理值3和6时,程序中程序执行报告结果的输出语句。