c++ 学习笔记(44) 异常

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)!
7. exception类可以把它用作其他异常类的基类,其中的what()函数,是虚拟成员函数,返回一个字符串,该字符串的特征随实现而异。可以通过它派生出其他的异常类。如class bad_hmean: public exception
{
...
};
另外: stdexcept异常类中定义了其他的异常类logic_error和runtime_error类, 其中logic_error指出了domain_error , invalid_error , length_error ,  out_of_bounds 错误类型。
runtime_error 指出了range_error , overflow_error ,  underflow_error 异常类型。
8 . bad _alloc 异常 和 new 
对于处理使用new时可能出现的内存分配问题, c++ 提供了两种可供选择的方式,第一种是: 让new在无法满足内存请求时返回一个空指针;第二种是;让new引发bad_alloc异常。
#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()退出,如果没有捕获到,将检查是否为空指针。
正常时:

异常时:





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值