第 10章 异常处理第 10章 异常处理
10.1 异常处理机制
10.2 异常处理的实现
10.3 异常处理中的构造与析构第 10章 异常处理
10.1 异常处理机制在一个大型软件中,由于函数之间有着明确的分工和复杂的调用关系,发现错误的函数往往不具备处理错误的能力。因此,C++语言异常处理机制的基本思想是将异常的检测与处理分离。当在一个函数体中检测到异常条件存在,但却无法确定相应的处理方法时,该函数将引发一个异常,由函数的直接或间接调用者捕获这个异常并处理这个错误。
第 10章 异常处理如果程序始终没有处理这个异常,最终它会被传到 C++运行系统那里,运行系统捕获异常后,通常只是简单地终止这个程序 。
由于异常处理机制使得异常的引发和处理不必在同一函数中,这样,底层的函数可以着重解决具体问题而不必过多地考虑对异常的处理;上层调用者可以在适当的位置设计对不同类型异常的处理 。
第 10章 异常处理
10.2 异常处理的实现
10.2.1 异常处理的语法
1,throw语法
throw<表达式 >;
当某段程序发现了自己不能处理的异常,就可以使用 throw语句将这个异常抛掷给调用者 。 throw语句的使用与 return语句相似,如果程序中有多处要抛掷异常,
应该用不同的表达式类型来互相区别,表达式的值不能用来区别不同的异常 。
第 10章 异常处理
2,try块语法
try
{
复合语句
}
try语句后的复合语句是代码的保护段。如果预料某段程序代码 (或对某个函数的调用 )有可能发生异常,
就将它放在 try语句之后。如果这段代码 (或被调函数 )
运行时真的遇到异常情况,其中的 throw表达式就会抛掷这个异常。
第 10章 异常处理
3,catch语法
catch(异常类型 1参数 1)
{
//针对异常类型 1的处理语句
}
catch(异常类型 2参数 2)
{
//针对异常类型 2的处理语句
}
...
catch(异常类型 n参数 n)
第 10章 异常处理
{
//针对异常类型 n的处理语句
}
catch语句后的复合语句是异常处理程序,捕获由
throw表达式抛掷的异常。异常类型声明部分指明语句所处理的异常类型,它与函数的形参相类似,可以是某个类型的值,也可以是引用。这里的类型可以是任何有效的数据类型,包括 C++的类。当异常被抛掷以后,catch语句便依次被检查。
第 10章 异常处理
10.2.2 异常处理的执行过程异常处理的执行过程如下,
① 控制通过正常的顺序执行到达 try语句,然后执行 try
块内的保护段 。
② 如果在保护段执行期间没有引起异常,那么跟在 try
块后的 catch语句就不执行,程序从异常被抛掷的 try块后跟随的最后一个 catch语句后面的语句继续执行下去 。
③如果在保护段执行期间或在保护段调用的任何函数中 (直接或间接的调用 )有异常被抛掷,则从通过 throw创建的对象中创建一个异常对象 (这隐含指可能包含一个拷贝构造函数 )。
第 10章 异常处理这一点上,编译器能够处理抛掷类型的异常,在更高执行上下文中寻找一个 catch语句 (或一个能处理任何类型异常的 catch处理程序 )。 catch处理程序按其在 try
块后出现的顺序被检查 。 如果没有找到合适的处理程序,则继续检查下一个动态封闭的 try块 。 此处理继续下去,直到最外层的封闭 try块被检查完 。
第 10章 异常处理
④ 如果匹配的处理器未找到,则 terminate()将被自动调用,而函数 terminate()的默认功能是调用 abort终止程序 。
⑤如果找到了一个匹配的 catch处理程序,且它通过值进行捕获,则其形参通过拷贝异常对象进行初始化。如果它通过引用进行捕获,则参量被初始化为指向异常对象,在形参被初始化之后,“循环展开栈”
的过程开始。这包括对那些在与 catch处理器相对应的
try块开始和异常丢弃地点之间创建的 (但尚未析构的 )
所有自动对象的析构。
第 10章 异常处理
【 例 10-1】 处理除零异常例题 。
#include<iostream.h>
intDiv(intx,inty); //整数除法函数原型声明
voidmain()
{
try
//由于除法运算有可能出现除零异常,因此放在 try块中
{
cout<<"5/2="<<Div(5,2)<<endl;
cout<<"8/0="<<Div(8,0)<<endl;
cout<<"7/1="<<Div(7,1)<<endl;
第 10章 异常处理
}
catch(int) //捕获整数异常
{
cout<<"exceptionofdividingzero."<<endl;
}
cout<<"thatisok."<<endl;
}
intDiv(intx,inty)
{
if(y==0)
throwy; //如果整数为零,抛掷一个整数异常第 10章 异常处理
returnx/y;
}
程序运行结果为
5/2=2
exceptionofdividingzero.
thatisok.
从运行结果可以看出,当执行下列语句时,在函数 Div()中发生除零异常 。
cout<<"8/0="<<Div(8,0)<<endl;
第 10章 异常处理异常被抛掷后,在 main()函数中被捕获,异常处理程序输出有关信息后,程序流程跳转到主函数的最后一条语句,输出,thatisok.”,而函数 Div()中的下列语句没有被执行:
cout<<"7/1="<<Div(7,1)<<endl;
catch处理程序的出现顺序很重要,因为在一个 try
块中,异常处理程序是按照它出现的顺序被检查的。只要找到一个匹配的异常类型,后面的异常处理都将被忽略。例如,在下面的异常处理块中,首先出现的是
catch(...),它可以捕获任何异常,在任何情况下,其它的 catch语句都不被检查。因此,catch(...)应该放在最后。
第 10章 异常处理
//...
try
{
//...
}
catch(...)
{
//只在这里处理所有的异常
}
//错误:后面的两个异常处理程序段不会被检查
catch(constchar*str)
第 10章 异常处理
{
cout<<"Caughtexception:"<<str<<endl;
}
catch(int)
{
//处理整型异常
}
在 VC++6.0环境中,为了使用异常处理机制,需要进行如下设置:打开 ProjectSettings对话框,选择 C/
C++选项卡,在 Category栏中选择 C++Lauguage,然后选择 EnableExceptionHandling。
第 10章 异常处理
10.2.3 异常接口声明为了加强程序的可读性,使用户能够方便地知道所使用的函数会抛掷哪些异常,可以在函数的声明中列出这个函数可能抛掷的所有异常类型 。 例如:
voidfun()throw(A,B,C,D)
这表明函数 throw()能够且只能够抛掷类型 A,B,C、
D的异常 。
如果在函数的声明中没有包括异常接口声明,则此函数可以抛掷任何类型的异常。
第 10章 异常处理例如:
voidfun();
一个不抛掷任何类型异常的函数可以进行如下形式的声明:
voidfun()throw();
第 10章 异常处理
10.3 异常处理中的构造与析构
C++异常处理的真正能力不仅在于它能够处理各种不同类型的异常,还在于它具有在异常抛掷前为构造的所有局部对象自动调用析构函数的能力 。
在程序中,找到一个匹配的 catch异常处理后,如果 catch语句的异常类型声明是一个值参数,则其初始化方式是复制被抛掷的异常对象;如果 catch语句的异常类型声明是一个引用,则其初始化方式是使该引用指向异常对象 。
第 10章 异常处理当 catch语句的异常类型声明参数被初始化后,栈的展开过程便开始了 。 这包括从对应的 try块开始到异常被抛掷处之间对构造 (且尚未析构 )的所有自动对象进行析构 。 析构的顺序与构造的顺序相反 。 然后程序从最后一个 catch处理之后开始恢复执行 。
【 例 10-2】 使用带析构语义的类的 C++异常处理例题 。
#include<iostream.h>
voidMyFunc(void);
classExpt
{
public:
Expt(){};
第 10章 异常处理
~Expt(){};
constchar*ShowReason()const
{
return"Expt类异常 。 ";
}
};
classDemo
{
public:
Demo();
~Demo();
第 10章 异常处理
};
Demo::Demo()
{
cout<<"构造 Demo。 "<<endl;
}
Demo::~Demo()
{
cout<<"析构 Demo。 "<<endl;
}
voidMyFunc()
第 10章 异常处理
{
DemoD;
cout<<"在 MyFunc()中抛掷 Expt类异常 。 "<<endl;
throwExpt();
}
intmain()
{
cout<<"在 main()函数中 。 "<<endl;
try
{
cout<<"在 try块中,调用 MyFunc()。 "<<endl;
第 10章 异常处理
MyFunc();
}
catch(ExptE)
{
cout<<"在 catch异常处理程序中 。 "<<endl;
cout<<"捕获到 Expt类型异常,";
cout<<E.ShowReason()<<endl;
}
catch(char*str)
第 10章 异常处理
{
cout<<"捕获到其它的异常,"<<str<<endl;
}
cout<<"回到 main()函数 。 从这里恢复执行 。 "<<endl;
return0;
}
程序运行结果为在 main()函数中在 try块中,调用 MyFunc()
构造 Demo
在 MyFunc()中抛掷 Expt类异常析构 Demo
第 10章 异常处理在 catch异常处理程序中捕获到 Expt类型异常,Expt类异常回到 main()函数,从这里恢复执行注意,本例中,在两个 catch处理器中都说明了异常参量
(catch语句的参量 ):
catch(ExptE)
{//...}
catch(char*str)
{//...}
第 10章 异常处理其实,也可以不说明这些参量 (E和 str)。 在很多情况下,只要通知处理程序有某个特定类型的异常已经产生就足够了 。 但是在需要访问异常对象时就要说明参量,否则,将无法访问 catch处理程序语句中的那个对象 。 例如:
catch(Expt)
{
//在这里不能访问 Expt异常对象
}
用一个不带操作数的 throw表达式可以将当前正被处理的异常再次抛掷。
第 10章 异常处理这样一个表达式只能出现在一个 catch处理程序中或 catch处理程序内部调用的函数中 。 再次抛掷的异常对象是源异常对象 (不是拷贝 )。 例如:
try
{
throwCSomeOtherException();
}
catch(...) //处理所有异常第 10章 异常处理
{
//对异常作出响应 (也许仅仅是部分的 )
//...
throw; //将异常传给某个其它处理器
}
10.1 异常处理机制
10.2 异常处理的实现
10.3 异常处理中的构造与析构第 10章 异常处理
10.1 异常处理机制在一个大型软件中,由于函数之间有着明确的分工和复杂的调用关系,发现错误的函数往往不具备处理错误的能力。因此,C++语言异常处理机制的基本思想是将异常的检测与处理分离。当在一个函数体中检测到异常条件存在,但却无法确定相应的处理方法时,该函数将引发一个异常,由函数的直接或间接调用者捕获这个异常并处理这个错误。
第 10章 异常处理如果程序始终没有处理这个异常,最终它会被传到 C++运行系统那里,运行系统捕获异常后,通常只是简单地终止这个程序 。
由于异常处理机制使得异常的引发和处理不必在同一函数中,这样,底层的函数可以着重解决具体问题而不必过多地考虑对异常的处理;上层调用者可以在适当的位置设计对不同类型异常的处理 。
第 10章 异常处理
10.2 异常处理的实现
10.2.1 异常处理的语法
1,throw语法
throw<表达式 >;
当某段程序发现了自己不能处理的异常,就可以使用 throw语句将这个异常抛掷给调用者 。 throw语句的使用与 return语句相似,如果程序中有多处要抛掷异常,
应该用不同的表达式类型来互相区别,表达式的值不能用来区别不同的异常 。
第 10章 异常处理
2,try块语法
try
{
复合语句
}
try语句后的复合语句是代码的保护段。如果预料某段程序代码 (或对某个函数的调用 )有可能发生异常,
就将它放在 try语句之后。如果这段代码 (或被调函数 )
运行时真的遇到异常情况,其中的 throw表达式就会抛掷这个异常。
第 10章 异常处理
3,catch语法
catch(异常类型 1参数 1)
{
//针对异常类型 1的处理语句
}
catch(异常类型 2参数 2)
{
//针对异常类型 2的处理语句
}
...
catch(异常类型 n参数 n)
第 10章 异常处理
{
//针对异常类型 n的处理语句
}
catch语句后的复合语句是异常处理程序,捕获由
throw表达式抛掷的异常。异常类型声明部分指明语句所处理的异常类型,它与函数的形参相类似,可以是某个类型的值,也可以是引用。这里的类型可以是任何有效的数据类型,包括 C++的类。当异常被抛掷以后,catch语句便依次被检查。
第 10章 异常处理
10.2.2 异常处理的执行过程异常处理的执行过程如下,
① 控制通过正常的顺序执行到达 try语句,然后执行 try
块内的保护段 。
② 如果在保护段执行期间没有引起异常,那么跟在 try
块后的 catch语句就不执行,程序从异常被抛掷的 try块后跟随的最后一个 catch语句后面的语句继续执行下去 。
③如果在保护段执行期间或在保护段调用的任何函数中 (直接或间接的调用 )有异常被抛掷,则从通过 throw创建的对象中创建一个异常对象 (这隐含指可能包含一个拷贝构造函数 )。
第 10章 异常处理这一点上,编译器能够处理抛掷类型的异常,在更高执行上下文中寻找一个 catch语句 (或一个能处理任何类型异常的 catch处理程序 )。 catch处理程序按其在 try
块后出现的顺序被检查 。 如果没有找到合适的处理程序,则继续检查下一个动态封闭的 try块 。 此处理继续下去,直到最外层的封闭 try块被检查完 。
第 10章 异常处理
④ 如果匹配的处理器未找到,则 terminate()将被自动调用,而函数 terminate()的默认功能是调用 abort终止程序 。
⑤如果找到了一个匹配的 catch处理程序,且它通过值进行捕获,则其形参通过拷贝异常对象进行初始化。如果它通过引用进行捕获,则参量被初始化为指向异常对象,在形参被初始化之后,“循环展开栈”
的过程开始。这包括对那些在与 catch处理器相对应的
try块开始和异常丢弃地点之间创建的 (但尚未析构的 )
所有自动对象的析构。
第 10章 异常处理
【 例 10-1】 处理除零异常例题 。
#include<iostream.h>
intDiv(intx,inty); //整数除法函数原型声明
voidmain()
{
try
//由于除法运算有可能出现除零异常,因此放在 try块中
{
cout<<"5/2="<<Div(5,2)<<endl;
cout<<"8/0="<<Div(8,0)<<endl;
cout<<"7/1="<<Div(7,1)<<endl;
第 10章 异常处理
}
catch(int) //捕获整数异常
{
cout<<"exceptionofdividingzero."<<endl;
}
cout<<"thatisok."<<endl;
}
intDiv(intx,inty)
{
if(y==0)
throwy; //如果整数为零,抛掷一个整数异常第 10章 异常处理
returnx/y;
}
程序运行结果为
5/2=2
exceptionofdividingzero.
thatisok.
从运行结果可以看出,当执行下列语句时,在函数 Div()中发生除零异常 。
cout<<"8/0="<<Div(8,0)<<endl;
第 10章 异常处理异常被抛掷后,在 main()函数中被捕获,异常处理程序输出有关信息后,程序流程跳转到主函数的最后一条语句,输出,thatisok.”,而函数 Div()中的下列语句没有被执行:
cout<<"7/1="<<Div(7,1)<<endl;
catch处理程序的出现顺序很重要,因为在一个 try
块中,异常处理程序是按照它出现的顺序被检查的。只要找到一个匹配的异常类型,后面的异常处理都将被忽略。例如,在下面的异常处理块中,首先出现的是
catch(...),它可以捕获任何异常,在任何情况下,其它的 catch语句都不被检查。因此,catch(...)应该放在最后。
第 10章 异常处理
//...
try
{
//...
}
catch(...)
{
//只在这里处理所有的异常
}
//错误:后面的两个异常处理程序段不会被检查
catch(constchar*str)
第 10章 异常处理
{
cout<<"Caughtexception:"<<str<<endl;
}
catch(int)
{
//处理整型异常
}
在 VC++6.0环境中,为了使用异常处理机制,需要进行如下设置:打开 ProjectSettings对话框,选择 C/
C++选项卡,在 Category栏中选择 C++Lauguage,然后选择 EnableExceptionHandling。
第 10章 异常处理
10.2.3 异常接口声明为了加强程序的可读性,使用户能够方便地知道所使用的函数会抛掷哪些异常,可以在函数的声明中列出这个函数可能抛掷的所有异常类型 。 例如:
voidfun()throw(A,B,C,D)
这表明函数 throw()能够且只能够抛掷类型 A,B,C、
D的异常 。
如果在函数的声明中没有包括异常接口声明,则此函数可以抛掷任何类型的异常。
第 10章 异常处理例如:
voidfun();
一个不抛掷任何类型异常的函数可以进行如下形式的声明:
voidfun()throw();
第 10章 异常处理
10.3 异常处理中的构造与析构
C++异常处理的真正能力不仅在于它能够处理各种不同类型的异常,还在于它具有在异常抛掷前为构造的所有局部对象自动调用析构函数的能力 。
在程序中,找到一个匹配的 catch异常处理后,如果 catch语句的异常类型声明是一个值参数,则其初始化方式是复制被抛掷的异常对象;如果 catch语句的异常类型声明是一个引用,则其初始化方式是使该引用指向异常对象 。
第 10章 异常处理当 catch语句的异常类型声明参数被初始化后,栈的展开过程便开始了 。 这包括从对应的 try块开始到异常被抛掷处之间对构造 (且尚未析构 )的所有自动对象进行析构 。 析构的顺序与构造的顺序相反 。 然后程序从最后一个 catch处理之后开始恢复执行 。
【 例 10-2】 使用带析构语义的类的 C++异常处理例题 。
#include<iostream.h>
voidMyFunc(void);
classExpt
{
public:
Expt(){};
第 10章 异常处理
~Expt(){};
constchar*ShowReason()const
{
return"Expt类异常 。 ";
}
};
classDemo
{
public:
Demo();
~Demo();
第 10章 异常处理
};
Demo::Demo()
{
cout<<"构造 Demo。 "<<endl;
}
Demo::~Demo()
{
cout<<"析构 Demo。 "<<endl;
}
voidMyFunc()
第 10章 异常处理
{
DemoD;
cout<<"在 MyFunc()中抛掷 Expt类异常 。 "<<endl;
throwExpt();
}
intmain()
{
cout<<"在 main()函数中 。 "<<endl;
try
{
cout<<"在 try块中,调用 MyFunc()。 "<<endl;
第 10章 异常处理
MyFunc();
}
catch(ExptE)
{
cout<<"在 catch异常处理程序中 。 "<<endl;
cout<<"捕获到 Expt类型异常,";
cout<<E.ShowReason()<<endl;
}
catch(char*str)
第 10章 异常处理
{
cout<<"捕获到其它的异常,"<<str<<endl;
}
cout<<"回到 main()函数 。 从这里恢复执行 。 "<<endl;
return0;
}
程序运行结果为在 main()函数中在 try块中,调用 MyFunc()
构造 Demo
在 MyFunc()中抛掷 Expt类异常析构 Demo
第 10章 异常处理在 catch异常处理程序中捕获到 Expt类型异常,Expt类异常回到 main()函数,从这里恢复执行注意,本例中,在两个 catch处理器中都说明了异常参量
(catch语句的参量 ):
catch(ExptE)
{//...}
catch(char*str)
{//...}
第 10章 异常处理其实,也可以不说明这些参量 (E和 str)。 在很多情况下,只要通知处理程序有某个特定类型的异常已经产生就足够了 。 但是在需要访问异常对象时就要说明参量,否则,将无法访问 catch处理程序语句中的那个对象 。 例如:
catch(Expt)
{
//在这里不能访问 Expt异常对象
}
用一个不带操作数的 throw表达式可以将当前正被处理的异常再次抛掷。
第 10章 异常处理这样一个表达式只能出现在一个 catch处理程序中或 catch处理程序内部调用的函数中 。 再次抛掷的异常对象是源异常对象 (不是拷贝 )。 例如:
try
{
throwCSomeOtherException();
}
catch(...) //处理所有异常第 10章 异常处理
{
//对异常作出响应 (也许仅仅是部分的 )
//...
throw; //将异常传给某个其它处理器
}