第1章 命名空间与异常处理由刀豆文库小编整理,希望给你工作、学习、生活带来方便,猜你可能喜欢“第11章模板与异常处理”。
版本控制页
版本控制表
序号 版本号 V0.00 版本性质 初稿 一校 二校 10.16.2008
2008.10.30
孔祥萍 赵元
王文丛
创建时间
建议人
修订人
修改日期
修改内容简述
备注
第1章 命名空间与异常处理
本章主要内容:
命名空间的定义 命名空间的访问 异常处理的概念 异常处理的机制 多层级间的异常处理 异常处理的堆栈操作
本章重点:
命名空间的定义与命名空间的访问 异常处理的机制和实现 多层级间的异常处理
本章难点:
命名空间的访问
多层级间的异常处理 异常处理的堆栈操作
学完本章您将能够:
了解命名空间概念 掌握命名空间定义 了解异常处理的机制 掌握异常处理的方法
掌握多层级间的异常处理方法 了解异常处理中的堆栈操作
引言
在一个给定作用域中定义的每个名字在该作用域中必须是惟一的,对庞大、复杂的应用程序而言,这个要求可能难以满足。这样的应用程序在全局作用域中,一般有许多名字定义。由独立开发的库构成的复杂程序更有可能遇到名字冲突,本章用命名空间的技术来解决此类问题。
C++中的异常处理是一种灵活并且精巧的工具。它克服了C语言的传统错误处理方法的缺点,并且能够被用来解决一系列运行期的错误。但是,异常处理也像其他语言特性一样,很容易被误用。为了能够有效的使用这一特性,了解运行期机制如何工作以及相关的性能花费是非常重要的。
1.1 命名空间
1.1.1 命名空间的定义
命名空间定义以关键字namespace开始,后接命名空间的名字。命名空间名字后面由大括号括住的一块声明和定义区域构成,可以在命名空间中放入可以出现在全局作用域的任意声明:类、变量(以及它们的初始化)、函数(以及它们的定义)、模板以及其他命名空间。
namespace GameCollege {
cla Matrix { /*...*/ };
void Inverse(matrix &);
const double PI = 3.1416;} 这段代码定义了名为 GameCollege的命名空间,它有3个成员:一个类、一个函数、一个常量。
像其他名字一样,命名空间的名字在定义该命名空间的作用域中必须是惟一的。命名空间可以在全局作用域或其他作用域内部定义,但不能在函数或类内部定义。命名空间作用域不能以分号结束。
定义在命名空间中的实体称为命名空间成员。像任意作用域的情况一样,命名空间中的每个名字必须引用该命名空间中的惟一实体。因为不同命名空间引入不同作用域,所以不同命名空间可以具有同名成员。在命名空间中定义的名字可以被命名空间中的其他成员直接访问,命名空间外部的代码必须指出名字定义在哪个命名空间中。在命名空间之外使用空间的成员必须用空间名字进行修饰。
#include int main(){ } std::cout
与其他作用域不同,命名空间可以在几个部分中定义。命名空间由它的分离定义部分的总和构成,命名空间是累积的。一个命名空间的分离部分可以分散在多个文件中,在不同文本文件中的命名空间定义也是累积的。当然,名字只在声明名字的文件中可见,这一常规限制继续应用,所以,如果命名空间的一个部分需要使用定义在另一文件中的名字,仍然必须声明该名字。
1.1.2 嵌套命名空间
一个嵌套命名空间,即是一个嵌套作用域,其作用域嵌套在包含它的命名空间内部。嵌套命名空间中的名字遵循常规规则:外围的命名空间中声明的名字被嵌套命名空间中同一名字的声明所屏蔽。嵌套命名空间内部定义的名字局部于该命名空间。外围的命名空间之外的代码只能通过限定名访问嵌套命名空间中的名字。
嵌套命名空间可以改进库中代码的组织:
namespace GameCollege
// 外围的命名空间 {
namespace QueryLib
// GameCollege的嵌套命名空间QuerLib
{
cla Query { /*...*/ };
Query operator&(const Query&, const Query&);
//...}
namespace Bookstore
// GameCollege的嵌套命名空间Bookstore {
cla Item_base { /*...*/ };
cla Bulk_item : public Item_base { /*...*/ };
//...} } 命名空间GameCollege现在包含两个嵌套命名空间:名为QueryLib的命名空间和名为Bookstore的命名空间。
当程序库提供者为了防止库中每个部分的名字与库中其他部分的名字起冲突的时候,嵌套命名空间是很有用的。
嵌套命名空间中成员的名字由外围命名空间的名字和嵌套命名空间的名字构成。例如,嵌套命名空间QueryLib中声明的类的名字是:
GameCollege::QueryLib::Query 1.1.3 命名空间的访问
如:命名空间名namespace_name::member_name,成员名这样引用命名空间的成员无可否认是很麻烦,特别是命名空间名字很长的时候。可以使用更简洁的方式使用命名空间的成员。
using 声明
【实例1-1】using声明
#include using std::cout;using std::endl;int main(){
cout
return 0;} 使用using声明能使命名空间std中的名字cout、endl在当前作用域中可见,可以无须限定符std::而使用名字cout、endl。using声明中引入的名字遵循常规作用域规则。从using声明点开始,直到包含using声明的作用域的末尾,名字都是可见的。外部作用域中定义的同名实体被屏蔽。简写名字只能在声明它的作用域及其嵌套作用域中使用,一旦该作用域结束了,就必须使用完全限定名。using声明可以出现在全局作用域、局部作用域或者命名空间作用域中。类作用域中的using声明局限于被定义类的基类中定义的名字。
命名空间别名
可用命名空间别名将较短的同义词与命名空间名字相关联。例如:
namespace GameCollegeLib {
} const MyCla cMyCla = MyCla();void PrintfMyCla(const MyCla& myCla);cla MyCla { public:
};void SetValue(int v){ iValue = v;} int GetValue()const { return iValue;} int iValue;MyCla(): iValue(0){}
private: 这样的长命名空间名字,可以像下面这样与较短的同义词相关联:
namespace gc = GameCollegeLib;命名空间别名声明以关键字namespace开头,接(较短的)命名空间别名名字,再接 =,最后接原来的命名空间名字和分号。如果原来的命名空间名字是未定义的,程序将会出错。
【实例1-2】命名空间别名
#include #include “gamecollegelib.h”
namespace gc = GameCollegeLib;
int main(){
std::cout
myCla.SetValue(45);
gc::PrintfMyCla(myCla);
} return 0;上例程序中gc是GameCollegeLib的别名,可以通过gc访问GameCollegeLib命名空间中成员。
一个命名空间可以有许多别名,所有别名以及原来的命名空间名字都可以互换使用。
using 指示的形式
“using指示”以关键字using开头,后接关键字namespace,再接命名空间名字。如果该名字不是已经定义的命名空间名字,程序将会出错。
using namespace std;
“using指示”使得特定命名空间所有名字可见,没有限制。短格式名字可从“using指示”点开始使用,直到出现“using指示”的作用域的末尾。
【实例1-3】using指示的形式访问命名空间
namespace GameCollegeLib {
int i = 16, j = 15, k = 23;
// 命名空间其他成员 } int j = 0;void manip(){
using namespace GameCollegeLib;// using 指示GameCollegeLib空间中名称全部可见
++i;
// 设置GameCollegeLib::i为17
++j;
// 错误:名称冲突,无法确定是全局j还是GameCollegeLib::j
++::j;
// 设置全局变量j为1
++GameCollegeLib::j;// 设置GameCollegeLib::j为16
int k = 97;
// 定义局部变量k将覆盖GameCollegeLib::k
++k;
// 设置局部变量k赋值为98 } manip中的“using指示”使manip能够直接访问GameCollegeLib中的所有名字:使用它们的简化形式,该函数可以引用这些成员的名字。
GameCollegeLib的成员看来好像是在定义GameCollegeLib和manip的作用域中定义的一样。如果在全局作用域中定义GameCollegeLib,则GameCollegeLib的成员看来好像是声明在全局作用域的一样。因为名字在不同的作用域中,manip内部的局部声明可以屏蔽命名
空间的某些成员名字,局部变量k屏蔽命名空间名字GameCollegeLib::k,在manip内部对k的引用没有二义性,它引用局部变量k。
命名空间中的名字可能会与外围作用域中定义的其他名字冲突。例如,对manip而言,GameCollegeLib命名空间的成员j看来好像声明在全局作用域中,但是,全局作用域存在另一名为j的对象。这种冲突是允许的,但为了使用该名字,必须显示指出想要的是哪个版本,因此,在manip内部的j使用是有二义性的:该名字既可引用全局变量又可引用命名空间 GameCollegeLib的成员。
为了使用像j这样的名字,必须使用作用域操作符指出想要的是哪个名字。可以编写 ::j 来获得在全局作用域中定义的变量,要使用GameCollegeLib中定义的j,必须使用它的限定名字GameCollegeLib::j。
“using指示”注入来自一个命名空间的所有名字,它的使用是不可靠的:只用一个语句,命名空间的所有成员名就突然可见了。虽然这种方法看似简单,但也有它自身的问题。如果应用程序使用许多库,并且用“using指示”使得这些库中的名字可见,那么,全局命名空间污染问题就会重新出现。
1.2 异常处理在C++中的实现
1.2.1 异常处理的概念和使用条件
程序运行中的某些错误(或意外情况)不可避免但可以预料。例如,假设y变量是一个除法运算或者模运算的除数,那么当变量y为0时,是一种严重的错误(即出现异常),需要得到处理并将错误反馈给设计者,可以在程序中添加如下形式的测试语句来达到这样的目的:
if(y == 0){
std::cout
1.2.2 异常处理的实现
C++中提供的对异常进行处理的机制,那就是在程序中使用try、catch和throw。
【实例1-4】在函数中使用异常机制
请看如下程序,其中使用了try、catch和throw来对除以0或模0所产生的异常进行处理。程序要求输入两个整数i1、i2以及一个运算符op(“/”或者“%”),而后进行相应运算并对所出现的异常进行处理。
#include
using std::cout;using std::cin;using std::endl;
void main(){
try
{
// try 程序块为“受监控”的块,块中所抛出的异常
// 将被本try块后的某一个catch块所捕获并处理
int i1, i2;
char op;
cout
cin >> i1 >> i2 >>op;// 输入两个整数i1、i2 以及一个运算符op
if(op == '/')
{
// 分母为0 时,抛出一个字符串:char*类型的异常
// 所抛出的异常将被随后的catch块捕获并处理
if(i2 == 0)
throw “Divided by 0!”;
// 正常情况(分母非0)的处理
cout
}
else if(op == '%')// 限制op只能为“/”或者“%”
{ // 分母为0 时,抛出整数i1 — int 型异常 // 所抛出的异常将被随后的catch 块捕获并处理
if(i2==0)
throw i1;
cout
}
else
cout
// 再进行一些其他的处理
cout
}
catch(int i)
{
// 捕获“int”类型的异常并进行处理
// 输出相关的异常信息后结束本catch块
cout
}
catch(char *str)
// 捕获“char *”类型的异常并进行处理
{
// 输出相关的异常信息后结束本catch块
cout
} // 异常处理结束后,均转到此处执行该语句
cout
Input I1 I2 OP:33 5 % 33 % 5=3 22 / 5=4 End main function!再次运行,显示结果为:
Input I1 I2 OP:33 0 / Error occuring--Divided by 0!End main function!注意:通过throw抛出异常后,系统将跳转到与所抛出的实参(表达式)类型完全匹配的catch块去执行,而执行完catch块后将不再返回,继而转到catch块序列的最后一个catch 块的下一语句处去执行。
对实际问题进行处理的程序中,还会碰到多种多样的产生异常(或错误)的情况。例如,磁盘中的文件被移动或缺少所需文件时将导致无法打开,内存不足使得通过new无法获取所需要的动态空间,用户提供了不恰当的输入数据等。为使程序具有更好的容错能力并体现出程序的健壮性,设计程序时,应该充分考虑程序执行过程中可能发生的各种意外情况(即异常),并对它们进行恰当的处理。也就是说,当异常出现后,要尽可能地让用户程序来进行干预,排除错误(至少给出适当的错误提示信息),而后继续运行程序。
下面对try、catch和throw的功能及其使用时要注意的内容做进一步说明:
1)
通过try可构成try块,用于标记运行时可能出现异常的程序代码。也就是说,try程序块在运行时将成为“受监控”的代码块,其中所抛出的任何异常(包括:在try 块中直接抛出的异常,以及通过所调用的“下层”函数而间接抛出的各种异常)都将被捕获并处理。
注意:抛出异常是通过throw 语句来完成的。try 块的使用格式如下: try { //“受监控”的语句序列(通常包含抛出异常的throw 语句)} 2)
通过catch可构成所谓的catch块,它紧跟在try块的后面,用于监视并捕获运行时可能出现的某一种类(类型)的异常并对此种异常进行具体的处理(处理程序代码包含在catch块之中)。即catch程序块将监视并捕获处理程序异常,若没有异常出现的话,catch程序块将不被执行。若准备处理多种类型的异常,要同时给出一个连续的catch块序列,届时系统将通过按序逐一“比对”所抛出的异常类型(既可是系统预定义的基本类型,也可是用户自定义的某种类型)来确定执行该catch块序列中的哪一块。
catch 块的使用格式如下:
catch(/*欲捕获的异常类型及形参名字*/){ //对该类型异常的具体处理语句序列 } 注意:如果catch块的具体处理语句序列中根本不使用形参(对应值)的话,则catch块首括号中可以只给出一个异常类型而不必给出形参名字。另外,还允许在catch块首括号中仅写上3 个点符号, 即“catch(…)”,其使用含义为:该catch块将捕获任何类型的异常。也就是说,它与任何一种异常类型都可以匹配成功。若使用这种catch块的话,它应该是catch块序列的最后一块,否则,其后的所有catch块都将起不到任何作用(永远不会被“匹配”成功。因为系统是按序与catch块序列中的每一块逐一进行“对比”的)。
抛出异常的throw语句的使用格式为:
throw 表达式
通常使用“表达式”的特殊情况,例如,一个变量或对象、一个常量或字符串等。系统将根据表达式的值类型来与各catch块首括号中形参的异常类型进行“比对”,若“匹配”成功(要求类型完全一致,系统并不自动进行类型转换),则跳转到那一catch块去执行,即进行相应的异常处理。若所有匹配都不成功,则将自动去执行一个隐含的系统函数“abort()”来终止整个程序的运行。
更确切地说,throw语句将把运行时遇到的异常事件信息通知给相匹配的异常处理catch块代码(throw后的“”,实际上起着实参的作用,它代表的正是异常事件信息,而catch块首括号中设立的正是所对应的形参。与函数调用类似,通过throw抛出异常后,也同样有一个实参与形参相结合的过程,形参与实参结合后,catch块中处理的实际上是throw语句所提供的实参信息。但与函数调用不同的是,函数调用完成后仍要返回到调用处继续执行,而throw语句所进行的“调用”实际上是带有实参的跳转,跳转到的目标是形参和实参完全匹配的一个catch块,而执行完catch块后将不再返回,继而转到catch块序列的最后一个catch块的下一语句处去执行。另外注意,catch块必须有且仅有一个形参的说明(可以缺少形参名字,但不可以缺少其类型),throw语句后携带的也只是一个相对应的实参(表达式)。
1.3 多级多层捕获与处理机制
1.3.1 多级多层处理的机制
在程序中的层次级别指的是函数间的层层调用关系。在这个调用中被调函数抛出异常,而被主调函数捕获,那么这种情况下的异常处理就称为是多级多层处理。C++的异常处理中是允许处理多级多层情况的。具体如下:
1)允许抛出异常的语句处于捕获与处理异常的catch块所在函数的“下属层次”,例如,可以在主调函数中处理异常,而在被调函数中抛出异常。也就是说,若在本层次的函数中无法处理所抛出的异常的话,系统总会自动沿着“调用链”一直向上传递异常。系统将首先找到异常抛出句throw 所处函数的“父辈”函数,进一步可能又要去找其“祖辈”函数。
【实例1-5】多层间的异常处理
#include unsigned short numMul(unsigned short sNum1, unsigned short sNum2){
int nExc = 0;
unsigned short sProduct = sNum1 * sNum2;
// 当结果超出数据表达范围时就抛出异常
if(sProduct
{
throw nExc;
}
else
{ }
int main(){
unsigned short s1 = 50000, s2 = 40000;
unsigned short sProduct;
try {
sProduct = numMul(s1, s2);
printf(“乘积=%dn”, sProduct);catch(int nExc){
printf(“错误代码为:%dn”, nExc);
}
return 0;}
return sProduct;
}
}
程序运行,结果如下:
错误代码为:0 2)允许在同一程序中使用多个try块,而且这些try块可以处于不同的位置并具有不同的层次级别。抛出异常后,系统将首先在相应的低层次级别的try块后的catch块序列中寻找匹配,若不成功,将自动沿着“调用链”向上传递该异常,并在上一层次的try块后的catch块序列中继续寻找匹配。直到最终匹配成功,或者已到达了最高层次的main函数后仍不成功时,则自动调用系统隐含函数“abort()”来终止整个程序的运行。
改写上例中的main()函数,对于抛出的异常不做处理。
#include int main(){
unsigned short s1 = 50000, s2 = 40000;
unsigned short sProduct;
sProduct = numMul(s1, s2);
printf(“乘积=%dn”, sProduct);
return 0;} 程序运行后,系统报错后结束。
1.3.2 多级多层处理的综合实例
【实例1-6】对用户提供不恰当输入数据的异常进行处理
输入一个限定范围内的整数,若输入数据不处于限定范围之内时,则视为程序运行异常,而后抛出并捕获处理这些异常。若输入正确,则在屏幕上显示出所输入的整数。另外,在程序中自定义一个异常类myExcepCla,以方便对异常的处理。
对输入数据所限定的范围如下:只允许输入-50~50之间除去0、11与22之外的那些整数。具体对异常的处理设定为:
若输入值为0,则抛出一个“char*”类异常 — 显示“main-catch::Exception : value equal 0!”; 若输入值小于-50,则抛出一个自定义类myExcepCla异常 — 显示“main-catch::Exception : value le than-50!”; 若输入值大于50,则抛出一个“char*”类异常
— 显示“InData-catch::Exception : value greater than 50!”; 若输入值为11,则利用输入值抛出一个int 型异常 — 显示“InData-catch::Caught INT exception!i=11”; 若输入值为22,则抛出一个double型异常 — 显示“main-catch::Caught unknown exception!”。
要求程序使用两个try块,一个处于main函数中,而另一个则处于被main函数调用的InData函数之中,它们具有不同的位置并具有不同的层次级别。main函数中try块后的catch块序列捕获并处理“char*”、myExcepCla以及“其他”任何类型的异常;而InData函数中try块后的catch 块序列则捕获并处理int与“char*”类型的异常。
#include using std::cout;using std::cin;using std::endl;cla myExcepCla
{
char meage[100];public:
// 构造函数,实参带来私有字符串数据meage
myExcepCla(char* msg)
{
strcpy(meage, msg);
}
// 成员函数,获取私有数据meage字符串
char* GetErrMsg()
{
return meage;
} };void InData();//函数声明 void main(){ // 处于main中的try程序块,它为“受监控”的程序块
try
{
InData();// 调用自定义函数InData,其中可能抛出异常
cout
}
catch(char * str)// 捕获“char *”类型的异常并进行处理
{
cout
}
catch(myExcepCla ob)
{
// 捕获“myExcepCla”类型的异常并进行处理
cout
}
catch(...)
{
// 捕获“其他”任何异常并进行处理
cout
} // 本层次的某个异常处理结束后,// 均转到此处执行该语句
cout
// 自定义函数f,负责判断val 是否为11 或22,// 输入值为11,抛出一个int 型异常
if(val == 11)
throw val;
// 输入值为22,抛出一个double型异常
if(val == 22)
throw 123.86;} 注意:f中所抛出的int型异常,将首先被自动沿着“调用链”向上传递到其“父辈”函数InData,而在InData中的catch块序列中,该异常恰好被匹配成功而得到处理。f中所抛出的double型异常,首先也被自动沿着“调用链”向上传递到其“父辈”函数InData,而在InData中的catch块序列中无法匹配成功又进一步被传递到其“祖辈”函数main,在main中与“catch(...)”匹配成功而得到处理。
void InData(){
int val;
cout
11、22):“;
cin >> val;
if(val == 0)// 若输入值等于0,抛出“char*”类异常,在main中被捕获处理。
throw ”Exception : value equal 0!“;
try
{
// 处于InData中的try程序块,它为“受监控”的另一个程序块
if(val
{
myExcepCla exc(”Exception : value le than-50!“);
// 该异常将被main中try 后的“catch(myExcepCla ob)”
// 所捕获处理
throw exc;
}
if(val > 50)
throw ”Exception : value greater than 50!“;// 在本try 后的catch 块序列中被捕获处理
else
f(val);// 被调函数f 中可能抛出异常
cout
} // 捕获“int”类型的异常并进行处理
catch(int i)
{
cout
}
// 捕获“char *”类型的异常并进行处理
catch(char * str)
{
cout
} } 注意:InData函数中try块后的某一个catch块被执行后(异常被处理结束后),将结束InData函数,返回到其主调函数main的try程序块的InData函数调用语句的下一语句处,即将接着去执行main中的“cout
显示结果为:
Input a INTEGER(from-50--50, excpt 0、11、22):0 main-catch::Exception : value equal 0!End main function!第二次执行:
Input a INTEGER(from-50--50, excpt 0、11、22):88 InData-catch::Exception : value greater than 50!End try block!End main function!第三次执行:
Input a INTEGER(from-50--50, excpt 0、11、22):50 OK!value=50 End try block!End main function!本实例程序中使用了两个try块,一个处于main函数中,而另一个则处于被main函数调用的InData函数之中,它们具有不同的位置并具有不同的层次级别。抛出异常后,系统总是首先在相应的低层次级别(如本例的InData函数)的try块后的catch块序列中寻找匹配,若不成功,再自动沿着“调用链”向上传递该异常,并在上一层次(如本例的main函数)的try块后的catch块序列中继续寻找匹配。
另一个注意点是:若程序中含有多个不同位置并具有不同层次级别的try块时,一旦异常被某一个层次的catch块所捕获,在执行完那一catch块后,系统的继续运行“点”将是本层次catch块序列之最后一个catch块的下一语句处(若那里不再有其他语句时,则结束那一函数而返回到上一层次的主调函数中)。
本程序还自定义了一个异常类myExcepCla,通过对它的使用,可建立起一种对异常进行处理的统一模式。
1.4 异常处理中的堆栈展开
C++进行异常处理时,总要自动地进行如下所述的一个“堆栈展开”过程:每当抛出一个异常后,系统首先找到能捕获处理该异常的catch块(注意,其中可能自动沿着“调用链”多次向上传递该异常,并假定在某一层次的try块后的catch块序列中找到了相匹配的catch块),紧接着系统要利用所抛出的对象(throw句中的“实参”)对相应catch块的“形参”进行“初始化”,而后将立即检查从抛出异常的那一try块的块首到该异常被抛出之间已构造,但尚未析构的那些处于堆栈中的局部对象(与变量)。若有,系统将自动对这些局部对象进行相应的退栈与析构处理(通过自动调用各对象的析构函数来完成,析构对象的顺序与构造它们的顺序恰好相反),再去执行相应catch块中的代码完成对异常的处理。
系统之所以要这样做,是因为异常处理机制中,通过throw语句抛出异常后,系统实际上是要找到并“跳转”到捕获异常的catch块去执行,而且在执行完那一catch块后将不再返回(到throw语句之后),继而转到catch块序列的最后一个catch块的“下一语句”处去执行。正是由于这种“跳转”后的不再返回,当抛出异常的throw语句处于某一下属层次的局部作用域(如某个局部块作用域或某个函数作用域)之中时,throw的“跳转”实际上相当于跳出了那些作用域,所以系统将自动检查在那些作用域中已经构造但尚未析构的处于堆栈中的局部对象(与变量),并自动进行相应的退栈与析构处理。
【实例1-7】分析以下程序执行后会显示出什么结果
注意系统对局部块作用域中的局部对象ob1和ob2所进行的自动析构处理。程序中说明了一个具有显式构造函数和析构函数的自定义类ClaA,并在其中显示目前已经进入该构造函数或析构函数的提示信息(因为要通过输出信息来观察系统提供的自动析构处理功能)。
编写main函数,并在其中设置局部块作用域,且说明ClaA自定义类的局部对象ob1和ob2,之后通过抛出“char*”类型的异常“跳转”出该局部块作用域(此时系统将对局部对象进行自动析构处理)。
由于要抛出并处理异常,将main中的程序段置于try块的块体之中,并添加捕获“char*”类型异常的catch块。
本实例的重点在于了解异常处理过程中的“堆栈展开”,以及对象的自动析构处理功能。
#include
using std::cout;using std::cin;
using std::endl;
cla ClaA {
};
void main(){
} // 捕获“char *”类型的异常并进行处理 catch(char * str){
} cout
throw ”throwing 'char*' exception in block-statement!“;cout
// try 程序块为“受监控”的程序块 cout
} cout
} cout
}
} cout
cout
Begin main function!Enter try-block!Begin a block-statement!Constructing ClaA obj--ob1 Constructing ClaA obj--ob2 Throwing exception, in block-statement!Destructing ClaA obj--ob2 Destructing ClaA obj--ob1 In catch-block, deal with: throwing 'char*' exception in block-statement!Execution resumes here.End main function!注意:上一程序中,抛出异常的throw语句处于一个局部块作用域内,throw的“跳转”实际上相当于跳出了那个局部块作用域(“跳转”到相对应的那一catch块而不再返回),所以系统将自动检查在那一局部块作用域中已经构造,但尚未析构的处于堆栈中的局部对象。如本例的ob1和ob2,并自动对它们进行相应的退栈与析构处理(析构顺序为ob2而后是ob1)。
实际上,当抛出异常的throw语句处于下属层次的某个函数作用域之中时,throw的“跳转”就相当于跳出了那个函数作用域(而不再返回),那时系统同样也将自动检查在哪一函数作用域中已经构造,但尚未析构的处于堆栈中的局部对象,并自动进行相应的退栈与析构处理。
例如,若将本例程序main函数中的try块改写为:
try { cout
void fun(){ cout
ClaA ob1(”obj1“), obj2(”ob2“);
cout
throw ”throwing 'char*' exception in fun!";
cout
本章介绍了命名空间概念,如何使用命名空间解决名字冲突问题。如何定义命名空间以及如何使用命名空间成员。在进行复杂系统设计过程中,名字冲突问题是不可避免的,命名空间技术可以很好的解决此类问题。嵌套命名空间可以防止库中每个部分的名字与库中其他部分的名字冲突的问题。对于命名空间的访问有“using声明”、“命名空间别名”、“using 指示”等方法。由于“using指示”使得命名空间中的名字暴露在全局区域中,所以要尽量避免使用。本章还介绍了C++中的异常处理,C++中采用异常处理机制的原因和实现方法,以及实际程序设计时会遇到多层级异常处理的问题。此外还介绍了C++中异常处理时的堆栈操作,使大家更能了解异常处理对程序稳定性的影响,并掌握在程序中使用异常处理的方法,从而使大家设计的程序更加健壮。
自测习题
判断试题
1.当程序中产生异常时,程序一定会被强制结束。()2.异常只在try块中产生,在非try块中不会产生。()
3. 在catch块首括号中仅写上3个点符号,即catch(„)时此块不处理任何异常。()4.若try块后找不到相应的catch块来处理异常,则程序将忽略这个异常继续运行。()5.在同一程序中,try块的数量一定和catch块的数量一样多。()问答题
6.解释using声明和using指示符的区别。7.考虑下面的代码例子:
namespace Exercise {
} int ivar = 0;//1 int
ivar = 0;double dvar = 0;const int limit = 1000;
void manip(){
//2 double dvar = 3.1416;int
iobj = limit + 1;++ivar;++::ivar;} 如果将命名空间Exercise成员的using声明放在 //1 处那么会对代码中的声明和表达式有什么影响?如果放在 //2 处呢?当用using指示符代替命名空间Exercise的using声明时,答案又是什么? 课后作业
1.输入一组数,实现一个累加和函数,当累加和超过数据的表示范围时,产生异常。2.在自己项目中定义自己命名空间,并分别用using声明、命名空间别名、using指示对命名空间成员进行引用。