1, abort()异常处理,对于调和平均数来说,表示2.08*x*y / (x + y); 来说,调用异常进行处理:
#include "iostream"
#include "cstdlib" //向标准错误流发送程序异常终止消息
using namespace std;
double hmean(double a, double b);
int main()
{
double x, y, z;
cout << "enter two numbers: ";
while(cin >> x >> y)
{
z = hmean(x, y);
cout << "the mean is: " << z << endl;
cout << "enter the next set of numbers:";
}
cout << "bye! \n";
return 0;
}
double hmean(double a, double b)
{
if(a == -b)
{
cout << "untenable arguments to hmean() \n";
abort();
}
return 2.0*a*b/(a+b);
}
在hmean()中调用abort()函数将直接终止程序,而不是首先返回到main()。一般而言,现实的程序异常中断消息随机而定。
2.返回错误码
#include "iostream"
#include "cstdlib" //向标准错误流发送程序异常终止消息
#include "cfloat"
using namespace std;
bool hmean(double a, double b, double *ans);
int main()
{
double x, y, z;
cout << "enter two numbers: ";
while(cin >> x >> y)
{
if(hmean(x, y, &z))
{
cout << "the mean is: " << z << endl;
}
else
{
cout << "one value should not be the negtive of the other -try again.\n";
}
cout << "enter the next set of numbers:";
}
cout << "bye! \n";
return 0;
}
bool hmean(double a, double b, double *ans)
{
if(a == -b)
{
*ans = DBL_MAX;
cout << *ans <<endl;
return false;
}
else
{
*ans = 2.0*a*b/(a+b);
return true;
}
}
返回错误码DBL_MAX这样返回的是给ans一个数码,这时就进行错误返回
3. try and catch异常机制
catch用花括号,指出异常处理程序要响应的异常类型,花括号中为采取的措施。
try加花括号是可能被激活的特定的异常,其后可跟一个或多个catch块。
#include "iostream"
#include "cstdlib" //向标准错误流发送程序异常终止消息
#include "cfloat"
using namespace std;
double hmean(double a, double b);
int main()
{
double x, y, z;
cout << "enter two numbers: ";
while(cin >> x >> y)
{
z = hmean(x, y);
try
{
//z = hmean(x, y);
}
catch(const char *s) //start of exception handler
{
cout << s << endl;
cout << "enter a new pair of numbers: ";
continue;
} //end of handler
cout << "the mean is: " << z << endl;
cout << "enter the next set of numbers:";
}
cout << "bye! \n";
return 0;
}
double hmean(double a, double b)
{
if(a == -b)
{
throw"bad hmean() argument: a = -b not allowed";
}
return 2.0*a*b/(a+b);
}
执行throw语句是导致程序沿函数调用序列后退,直到找到包含try块的函数。catch是一个处理程序,而char*s表明该处理异常与字符串异常匹配。匹配的引发将被赋值给s。
执行过程:if语句导致hmean()引发异常,这将终止hmean()的执行。程序向后搜索时发现,hmean()函数是try调用的,找到异常匹配的catch,对其引发异常匹配,将字符串“”bad hmean() 。。。。"赋给变量s, 然后执行continue。跳过while剩余的部分。
注意:若引发异常,没有try或无匹配的处理程序时。此时为默认情况下,程序最终将调用abort()函数。
4. try and catch 将对象用作异常类型
try后面跟着两个catch块,一个获取hmean的异常,一个捕获gmean引发的异常,并且这个异常将逃过第一个catch
#include "iostream"
#include "cstdlib" //向标准错误流发送程序异常终止消息
#include "cfloat"
#include "cmath"
using namespace std;
class bad_hmean
{
private:
double v1;
double v2;
public:
bad_hmean(double a = 0, double b = 0) :v1(a), v2(b) {}
void mesg();
};
inline void bad_hmean::mesg()
{
cout << "hmean(" << v1 << ", " << v2 << "): "
<< "invalid argument: a = -b\n";
}
class bad_gmean
{
public:
double v1;
double v2;
bad_gmean(double a = 0, double b = 0) :v1(a), v2(b) {}
const char* mesg();
};
inline const char * bad_gmean::mesg()
{
return "gmean() argument should be >= 0\n";
}
double hmean(double a, double b) throw(bad_hmean);
double gmean(double a, double b) throw(bad_gmean);
int main()
{
double x, y, z;
cout << "enter two numbers: ";
while(cin >> x >> y)
{
try
{
z = hmean(x, y);
cout << "the hmean is: " << z << endl;
cout << "the gmean is: " << gmean(x, y) << endl;
cout << "enter the next set of numbers:";
}
catch(bad_hmean &bg)
{
bg.mesg();
cout << "try again.\n";
continue;
}
catch(bad_gmean &hg) //start of exception handler
{
cout << hg.mesg();
cout << "value used: " << hg.v1 << ", " << hg.v2 << endl ;
cout << "sorry, you don't get to play any more.\n";
break;
} //end of handler
}
cout << "bye! \n";
cin.get();
cin.get();
return 0;
}
double hmean(double a, double b) throw(bad_hmean)
{
if(a == -b)
{
throw bad_hmean(a, b);
}
return 2.0*a*b/(a+b);
}
double gmean(double a, double b) throw(bad_gmean)
{
if(a < 0 || b < 0)
throw bad_gmean(a, b);
return sqrt(a * b);
}
其中异常类bad_hmean和bad_gmean使用的技术不同,bad_gmean使用的是公有数据和一个公有方法。
5. 堆栈解退:
c++通常通过将信息放在堆栈中来处理函数调用。具体地说,程序将调用函数的指令的地址(返回地址)放到堆栈中。当被调用的函数执行完毕后,程序将使用该地址来确定从哪里开始继续执行。另外,函数调用将函数参数放到堆栈中。在堆栈中,这些函数参数被视为自动变量。如果被调用的函数创建了新的自动变量,则这些变量也将被添加到堆栈中。如果被调用的函数调用了另一个函数,则后者的信息将被添加到堆栈中,依次类推。当函数结束时,程序流程将跳到该函数被调用时存储的地址处,同时堆栈顶端的元素被释放。因此,函数通常都返回到调用它的函数,依次类推,同时每个函数都在结束时释放其自动变量。如果自动变量是类对象,则类的析构函数(如果有的话)将被调用。
堆栈解退:发生异常时,控制权将转到块尾的异常处理程序,而不是函数调用后面的第一条语句。异常时,释放堆栈中的内存,但不会在释放堆栈的第一个返回地址后停止,而是继续释放堆栈,直到找到一个位于try块中的返回地址。
如果没有堆栈解退这种特性,则引发异常后,对于中间函数调用放在堆栈中的自动类对象,其析构函数将不会被调用。
#include "iostream"
#include "cstdlib" //向标准错误流发送程序异常终止消息
#include "cfloat"
#include "cmath"
using namespace std;
class bad_hmean
{
private:
double v1;
double v2;
public:
bad_hmean(double a = 0, double b = 0) :v1(a), v2(b) {}
void mesg();
};
inline void bad_hmean::mesg()
{
cout << "hmean(" << v1 << ", " << v2 << "): "
<< "invalid argument: a = -b\n";
}
class bad_gmean
{
public:
double v1;
double v2;
bad_gmean(double a = 0, double b = 0) :v1(a), v2(b) {}
const char* mesg();
};
inline const char * bad_gmean::mesg()
{
return "gmean() argument should be >= 0\n";
}
class demo
{
private:
char word[40];
public:
demo(const char* str)
{
strcpy(word, str);
cout << "demo" << word << "created\n";
}
~demo()
{
cout << "demo" << word << "destroyed\n";
}
void show() const
{
cout << "demo" << word << "lives!\n";
}
};
double hmean(double a, double b) throw(bad_hmean);
double gmean(double a, double b) throw(bad_gmean);
double means(double a, double b) throw(bad_hmean, bad_gmean);
int main()
{
double x, y, z;
demo d1("found in main()");
cout << "enter two numbers: ";
while(cin >> x >> y)
{
try //start of try block
{
z = means(x, y);
cout << "the hmean is: " << z << endl;
cout << "enter the next set of numbers:";
} // end of try block
catch(bad_hmean &bg) //strart of catch block
{
bg.mesg();
cout << "try again.\n";
continue;
}
catch(bad_gmean &hg) //start of exception handler
{
cout << hg.mesg();
cout << "value used: " << hg.v1 << ", " << hg.v2 << endl ;
cout << "sorry, you don't get to play any more.\n";
break;
} //end of handler
}
d1.show();
cout << "bye! \n";
cin.get();
cin.get();
return 0;
}
double hmean(double a, double b) throw(bad_hmean)
{
if(a == -b)
{
throw bad_hmean(a, b);
}
return 2.0*a*b/(a+b);
}
double gmean(double a, double b) throw(bad_gmean)
{
if(a < 0 || b < 0)
throw bad_gmean(a, b);
return sqrt(a * b);
}
double means(double a, double b) throw(bad_hmean, bad_gmean)
{
double am, hm, gm;
demo d2("found in means()");
am = (a + b) / 2.0;
try
{
hm = hmean(a, b);
gm = gmean(a, b);
}
catch(bad_hmean &bg)
{
bg.mesg();
cout << "Caught in means()\n";
throw;
}
d2.show();
return (am + hm + gm) / 3.0;
}
执行结果:
其中送入2 -2 时, 在means()中throw将导致means()函数终止执行,并将异常传递给main()函数,d2.show()没有被执行表明means()函数被提前终止。d2调用了析构函数:演示了异常极其重要的一点:程序进行堆栈解退以回到能够捕获异常的地方,将释放堆栈中的自动存储变量。如果变量是类对象,将为该对象调用析构函数。。。。。。
其中输入为2 -4时,由于means()不能捕获bad_gmean异常,因此异常被传递给main(),同时不再执行means()中的其他代码。同样,当程序进行堆栈解退时, 将释放局部的动态变量。
6. 其中exit(EXIT_FAILURE)程序将立刻终止。
引发异常时编译器总是创建一个临时拷贝,即使异常规范和catch块中指定的是引用。
class problem{...};
...
void super() throw (problem)
{
...
if(oh_no)
{
problem oops; //construct object
throw oops;
...
}
}
...
try
{
super();
}
catch(problem &p)
{
//statements
}
p指向oops的拷贝不是oops本身,因为函数super()执行完毕后oops将不再存在。
另外:既然throw语句将生成拷贝,为何代码中使用引用那??
在这里:引用的另一个重要特性:基类引用可以执行派生类对象。假如有一组通过继承关联起来的异常类型,则在异常规范中只需列出一个基类引用,他将与任何派生类对象匹配。
effective c++:
而且,通过指针捕获异常也不符合C++语言本身的规范。四个标准的异常――bad_alloc(当operator new(参见条款8)不能分配足够的内存时,被抛出),bad_cast(当dynamic_cast针对一个引用(reference)操作失败时,被抛出),bad_typeid(当dynamic_cast对空指针进行操作时,被抛出)和bad_exception(用于unexpected异常;参见条款14)――都不是指向对象的指针,所以你必须通过值或引用来捕获它们。
通过值捕获异常(catch-by-value)可以解决上述的问题,例如异常对象删除的问题和使用标准异常类型的问题。但是当它们被抛出时系统将对异常对象拷贝两次(参见条款12)。而且它会产生slicing problem,即派生类的异常对象被做为基类异常对象捕获时,那它的派生类行为就被切掉了(sliced off)。这样的sliced对象实际上是一个基类对象:它们没有派生类的数据成员,而且当调用它们的虚拟函数时,系统解析后调用的是基类对象的函数。(当一个对象通过传值方式传递给函数,也会发生一样的情况――参见Effective C++ 条款22)。例如下面这个程序采用了扩展自标准异常类的异常类层次体系:
class exception { // 如上,这是
public: // 一个标准异常类
virtual const char * what() throw();
// 返回异常的简短描述.
... // (在函数声明的结尾处
// 的"throw()",
}; //有关它的信息
class runtime_error: //也来自标准C++异常类
public exception { ... };
class Validation_error: // 客户自己加入个类
public runtime_error {
public:
virtual const char * what() throw();
// 重新定义在异常类中
... //虚拟函数
}; //
void someFunction() // 抛出一个 validation
{ // 异常
...
if (a validation 测试失败) {
throw Validation_error();
}
...
}
void doSomething()
{
try {
someFunction(); // 抛出 validation
} //异常
catch (exception ex) { //捕获所有标准异常类
// 或它的派生类
cerr << ex.what(); // 调用 exception::what(),
... // 而不是Validation_error::what()
}
}
调用的是基类的what函数,即使被抛出的异常对象是Validation_error和 Validation_error类型,它们已经重新定义的虚拟函数。这种slicing行为绝不是你所期望的。
最后剩下方法就是通过引用捕获异常(catch-by-reference)。通过引用捕获异常能使你避开上述所有问题。不象通过指针捕获异常,这种方法不会有对象删除的问题而且也能捕获标准异常类型。也不象通过值捕获异常,这种方法没有slicing problem,而且异常对象只被拷贝一次。
我们采用通过引用捕获异常的方法重写最后那个例子,如下所示:
void someFunction() //这个函数没有改变 { ... if (a validation 测试失败) { throw Validation_error(); } ... } void doSomething() { try { someFunction(); // 没有改变 } catch (exception& ex) { // 这里,我们通过引用捕获异常 // 以替代原来的通过值捕获 cerr << ex.what(); // 现在调用的是 // Validation_error::what(), ... // 而不是 exception::what() } } |
这里没有对throw进行任何改变,仅仅改变了catch子句,给它加了一个&符号。然而这个微小的改变能造成了巨大的变化,因为catch块中的虚拟函数能够如我们所愿那样工作了:调用的Validation_erro函数是我们重新定义过的函数。
如果你通过引用捕获异常(catch by reference),你就能避开上述所有问题,不会为是否删除异常对象而烦恼;能够避开slicing异常对象;能够捕获标准异常类型;减少异常对象需要被拷贝的数目。所以你还在等什么?通过引用捕获异常吧(Catch exceptions by reference)!
#include "iostream"
#include "new"
#include "cstdlib"
using namespace std;
struct Big
{
double stuff[20000];
};
int main()
{
Big *pb;
try
{
cout << "try to get a big block of memory:\n";
pb = new Big[1000]; // 1,600,000,000 bytes
cout << "got past the new request:\n";
}
catch(bad_alloc &ba)
{
cout << "caught the exception!" << endl;
cout << ba.what() << endl;
cin.get();
cin.get();
exit(EXIT_FAILURE);
}
if(pb != 0)
{
pb[0].stuff[0] = 4;
cout << pb[0].stuff[0] << endl;
}
else
cout << "pb is null pointer" << endl;
delete []pb;
cin.get();
cin.get();
return 0;
}
如果捕获到异常,将what()退出,如果没有捕获到,将检查是否为空指针。