现在的位置: 首页 > 综合 > 正文

C++学习笔记27 异常处理

2016年02月04日 ⁄ 综合 ⁄ 共 2976字 ⁄ 字号 评论关闭


#include <iostream>


using namespace std;

double Div(double a, double b)
{
	return a/b;
}
//除数为0的时候 发生异常

main()
{
   cout<<Div(1,0)<<endl;
 
   return 0;
}

1:c++异常处理机制


C++
异常处理机制是一个用来有效地处理运行错误的非常强大且灵活的工具,它提供了更多的弹性、安全性和稳固性,克服了传统方法所带来的问题.

异常的抛出和处理主要使用了以下三个关键字: try throw  catch 

抛出异常即检测是否产生异常,在C++中,其采用throw语句来实现,如果检测到产生异常,则抛出异常。该语句的格式为:
throw 
表达式;

如果在try语句块的程序段中(包括在其中调用的函数)发现了异常,且抛弃了该异常,则这个异常就可以被try语句块后的某个catch语句所捕获并处
理,捕获和处理的条件是被抛弃的异常的类型与
catch语句的异常类型相匹配。由于C++使用数据类型来区分不同的异常,因此在判断异常时,throw
句中的表达式的值就没有实际意义,而表达式的类型就特别重要


hrow语句用于将异常“ 对象 ”” 抛出
throw语句将异常抛出, 如果在当前函数中没有 try… catch 语句能够处理该异常, 则当前函数将立即返回 
异常被传递到上层调用函数,  仍然需要try …catch语句进行处理, 如果上层函数也没有能力处理该异常 ,则异常继续向更上层函数的函数传递。。。
如果在函数调用栈中的所有函数都无法处理抛出的异常,  则程序异常中止


一个try语句块可以跟上多个catch语句块
同一个try语句块可以抛出多种不同类型的异常
不同类型的异常由不同的catch语句块负责处理
异常被抛出后会自上而下逐一匹配catch语句块
异常匹配时, ,不会进行默认类型转换


异常处理是程序中随处可见的情况
在工程实践中, 大多数的代码都是用于处理异常的质量直接决定最终产品的质量
C++提供了  try…  catch语句用于将正常逻辑代码与异常语句处理代码进行分开处理


catch(… ) 可以捕获所有异常但却无法得到异常信息 
catch(… )一般作为最后一个异常处理块出现

在  catch(…)语句块中 ,可以通过不带参数的throw语句抛出捕获的异常

try
	{
		for(int i = 0; i < 10; i++)
		{
			try
			{
				cout<<test(i)<<endl;
			}
			catch(...)
			{
				cout<<"Exception Occur"<<endl;
				throw;//在  catch(…)语句块中 ,可以通过不带参数的throw语句抛出捕获的异常
			}
		}
	}
	catch(int e)
	{
		cout<<"Catch()..."<<e<<endl;
	}

不要在构造函数中抛出异常
在构造函数可能申请系统资源,  而在构造函数中抛出异常会导致对象构造不完全
不完全对象的析构函数是不会被调用的,可 因此可能造成资源泄漏

#include <iostream>
using namespace std;
class Test
{
	int* p;
public:
	Test()
	{
		cout<<"Test()"<<endl;
		p = new int[5];
		throw 1;
	}
	~Test()
	{
		cout<<"~Test()"<<endl;
		delete[] p;
	}
};

int main()
{
	try
	{
		Test t;
	}
	catch(int e)
	{
		cout<<"Catch:"<<e<<endl;
	}
	return 0;
}
/*

Test()
Catch:1
由输出结果可以看到,没有调用析构函数,会造成内存泄露
*/

2:异常的接口声明:

为了加强程序的可读性,使函数的用户能够方便地知道所使用的函数会抛出哪些异常,可以在函数的声明中列出这个函数可能抛出的所有异常类型,例如:

void fun() throw( A,B,C,D);

这表明函数fun()可能并且只可能抛出类型(A,B,C,D)及其子类型的异常。
如果在函数的声明中没有包括异常的接口声明,则此函数可以抛出任何类型的异常,例如:

void fun();

一个不会抛出任何类型异常的函数可以进行如下形式的声明:

void fun() thow();

3:工程中的异常应用:

在工程中会定义一系列的异常类

通过继承,可以得到一个异常类族

每个类代表工程当中可能出现的一种异常类型

由于对象构造与拷贝的开销,在定义catch语句块时使用引用作为参数。

在工程中可以使用标准库中的异常类
可将标准库中的异常类作为基类派生新的异常类
标准库中的异常都是从exception类派生的
exception类有两个主要的分支
logic_error用于描述程序中出现的逻辑错误
如  :传递无效参数 
runtime_error用于描述无法预料的事件所造成的错误
如  : 内存耗尽,

ogic_error和runtime_error都提供了一个参数为字符串的构造函数, ,这样就可以保持错误信息 
通过what()成员函数就可以得到错误的信息

4:函数级别try语法 秀操作:

第一种:

int func(int i)

{

try

{return i;}

        catch(...)

        {return -1;}

}

第二种:(秀代码)

int func(int i) try

{

return i;

}

catch(...)

{return -1;}

5:总结:

<1>catch(…  )可以捕获所有异常 可以捕获所有异常

<2>catch(… )经常作为最后一个 经常作为最后一个catch语句出现 

<3>不要在构造函数中抛出异常, 这样可能造成资源泄露

<4>工程中经常以标准库中的异常类作为项目异常的基础

<5>函数级try 语句块能够更好的提高代码的维护性

 <6>如果抛出的异常一直没有函数捕获(catch),则会一直上传到c++运行系统那里,导致整个程序的终止。

<7>一般在异常抛出后资源可以正常被释放,但注意如果在类的构造函数中抛出异常,系统是不会调用它的析构函数的,处理方法是:如果在构造函数中要抛出异常,则在抛出前要记得删除申请的资源。

<8>异常处理仅仅通过类型而不是通过值来匹配的,所以catch块的参数可以没有参数名称,只需要参数类型。

<9>函数原型中的异常说明要与实现中的异常说明一致,否则容易引起异常冲突。

<10>应该在throw语句后写上异常对象时,throw先通过Copy构造函数构造一个新对象,再把该新对象传递给 catch. 那么当异常抛出后新对象如何释放?

异常处理机制保证:异常抛出的新对象并非创建在函数栈上,而是创建在专用的异常栈上,因此它才可以跨接多个函数而传递到上层,否则在栈清空的过程中就会被 销毁。所有从try到throw语句之间构造起来的对象的析构函数将被自动调用。但如果一直上溯到main函数后还没有找到匹配的catch块,那么系统 调用terminate()终止整个程序,这种情况下不能保证所有局部对象会被正确地销毁。

<11>catch块的参数推荐采用地址传递而不是值传递,不仅可以提高效率,还可以利用对象的多态性。另外,派生类的异常扑获要放到父类异常扑获的前面,否则,派生类的异常无法被扑获。

<12>写异常说明时,要确保派生类成员函数的异常说明和基类成员函数的异常说明一致,即派生类改写的虚函数的异常说明至少要和对应的基类虚函数的异常说明相同,甚至更加严格,更特殊。

抱歉!评论已关闭.