C程序设计语言(李雁妮)第7章.ppt
- 文档编号:1759198
- 上传时间:2022-10-23
- 格式:PPT
- 页数:64
- 大小:702.50KB
C程序设计语言(李雁妮)第7章.ppt
《C程序设计语言(李雁妮)第7章.ppt》由会员分享,可在线阅读,更多相关《C程序设计语言(李雁妮)第7章.ppt(64页珍藏版)》请在冰豆网上搜索。
第7章名字空间与异常处理,7.1模块与接口的基本概念7.2名字空间7.3异常处理7.4综合示例小结练习题,7.1模块与接口的基本概念任何一个设计、构造良好的实用程序均是由若干模块(Module)/构件(Component)组成的,即使它是一个相对简单的程序。
这意味着:
(1)可以将程序划分成一组部件。
(2)程序模块的划分可以从多个角度来进行:
一是按自己写的程序与它所调用的系统支撑功能(如标准库函数或类库)来划分;二是将自己写的程序本身按某种原则(例如按功能)来进行划分,等等。
程序划分是有目的的。
将程序划分成模块,不仅能使其程序结构更加清晰,而且其模块更易于被替换与重用。
C+的名字空间(Namespace)和异常处理机制(ExceptionHanding)就是支持程序模块化组织的强有力机制。
本章我们首先以小型桌面计算器为例,来具体阐述C+用于支持模块化程序设计范型的主要机制名字空间及相关问题。
逻辑上,我们可将小型桌面计算器按功能划分为以下五大模块。
(1)语法分析器(Parser):
完成语言的语法分析;
(2)词法分析器(Lexer):
完成语言的词法分析;(3)符号表(SymbolTable):
存储用户标识符的名-值对;,(4)驱动程序(Driver):
main函数;(5)错误处理器(ErrorHandler):
进行程序的错误处理。
其中,语法分析器、词法分析器和符号表是该程序的三大核心部件。
程序中各模块之间的划分及模块间的相互关系如图7.1和图7.2所示。
图中的箭头表示模块之间的调用关系。
通过前面的学习我们已经知道,仅需了解一个函数接口的具体定义,而无需了解函数的具体实现,我们就能够很好地使用函数。
图7.1桌面计算器的程序逻辑划分,图7.2桌面计算器的模块关系图,类似地,即使程序的一个部件是由多个函数组成的,或者其中既有自定义类型也有全局变量、还有函数,我们都可以这样来设想:
如果这样的部件也像函数那样有一个起包装作用的接口,也同样可以只需要了解接口而不需要了解实现,就能够很好地使用它,这正是信息隐藏原理的实质与宗旨。
以此理念,图7.2中程序各模块之间调用的依赖关系我们可用图7.3描述。
图7.3程序各模块调用依赖图,图7.3中各个模块均调用错误处理模块,考虑图的清晰性,各模块对错误处理模块接口的调用我们暂未画出。
从图7.3可看出,各模块调用时直接依赖的仅仅是所调用模块的接口,而与其调用的实现无关。
确切地说,若程序中的一个部件具有明确的边界,能够实现接口与实现的分离,并对它的用户而言在使用时只需关心其接口而不管其实现,该部件就叫做模块(Module)。
实现模块的接口与实现的分离,需要程序设计语言提供相应的支持机制。
C+提供的支持机制是Namespace和Class。
模块用接口隐蔽了其中的数据和函数的处理细节(这也称做封装,Encapsulation),使得模块可以在保持接口不变的前提下,可改变其数据结构和函数的处理细节。
7.2名字空间7.2.1名字空间的基本概念C+中的名字空间(Namespace)是一种表现逻辑聚集关系的机制。
换句话说,如果一些声明(定义声明与非定义声明)在逻辑上都与某个划分准则有关,就可以把这些声明放入一个共同的名字空间中,以表现这一事实。
同一名字空间中的声明在概念上属于同一个逻辑实体。
由于程序中的模块就属于这种逻辑实体,因而我们可用C+的名字空间来封装模块,将程序进行模块化组织。
C+的名字空间的语法形式为namespace名字空间名/逻辑相关的数据、函数、类或其它等/注意:
无“;”号参照图7.1和第6章桌面计算器的代码,若以模块化的组织形式重构桌面计算器,则其语法分析(parser)模块为namespaceParserdoubleexpr(bool);/非定义声明doubleprim(boolget)/*/,上面,我们仅仅是把一些逻辑相关的部分组织到一个名字空间中(模块),并未实现模块接口与其实现的分离。
如何实现这种接口与实现的分离呢?
关键是在其模块实现部分中出现的成员被该名字空间的名字所约束(Qualified),这样的约束通过约束符(Qualifier:
,C+的作用域解析符)来表示。
以桌面计算器的Parser模块为例,进行界面/接口与实现分离的程序代码如下:
从上述代码可看出,接口与实现分离的要点是在每个模块接口中仅需非定义声明其中成员的接口部分,其模块成员的实现(如数据、类的定义、函数的定义等)均应以名字空间名:
成员/的形式进行名字空间外的定义声明。
C+语法规定,利用此形式只能进行名字空间内成员的定义声明,而不能利用此语法新定义声明一个名字空间成员。
7.2.2名字空间中的名字解析名字空间既是一个封装体,也是一种作用域。
一个名字空间中的名字(符号名)只能在其所定义的名字空间中可见、可用。
名字空间的另一主要用途是将不同的符号名组织到不同的名字空间中,以避免名字冲突。
名字冲突在大型软件的开发过程中是一个突出问题。
为了避免这个问题,在C+软件开发过程中,我们应有效地利用名字空间机制对软件中的名字进行有效的组织与管理。
例如,开发中,我们可将自己的代码组织到一个命名的名字空间中,以避免和系统类库、其他程序员编制的程序中的名字发生冲突。
在以上两个名字空间mfc和owl中,虽然都有同名的变量名inflag,但由于它们属于不同的名字空间,因此不产生名字冲突。
在另一个名字空间中解析其他名字空间的名字,可采取名字空间名:
名字的方式进行。
例如:
mfc:
inflag=3;/mfc中的inflagowl:
inflag=-256;/owl中的infalg当一个应用程序中有多个模块(或名字空间)时,由于各个namespace之间经常会出现互相使用对方成员的情况,如果每次使用就需用名字空间名进行约束,将会导致程序书写既繁琐又容易出错。
例如,在Parser模块的term实现中,就用了一系列的名字解析(约束):
为了避免这种繁琐易错的名字解析,C+提供了几种“有限的统一”约束机制。
1using声明语句语法:
using名字空间名:
名字;语义:
将某一名字空间中的名字引入(Introduce)到一个局部范围内,使其名字在该范围内无需名字空间的约束便可见、可用。
例如,在Parser模块中,由于使用了若干using声明语句,则在其namespace中我们就可直接采用其他namespace的名字。
示例代码如下:
需要注意的是,若using声明语句处于某一namespace的界面/接口中,则其using声明语句有效于(作用于)该namespace的所有实现。
例如,若我们在Parser名字空间中采用了如下所示的using声明语句:
则在parser模块中的所有实现(prim、term和expr)中,get_token、curr_tok和error无需名字空间名约束便直接可见。
我们还是以上述的prim实现为例:
我们可看出,在上述代码中所出现的其他namespace中的名字一律无需约束(解析),便能直接采用。
2using指令语句语法:
usingnamespace名字空间名;语义:
将特定名字空间中的所有名字引入到一作用域内。
我们已在前面各章节的示例程序中,多处见到了usingnamespacestd;语句。
该语句的功用即将系统std名字空间的所有名字引入(展现)到程序中。
值得注意的是,在一个namespace接口中使用using指令语句,其作用域为其namespace的所有实现中;若using指令语句用于某一个namespace的实现中,则其作用域为该实现内。
若我们在namespace的Parser中采用了如下using指令语句:
namespaceParserdoubleprim(bool);doubleterm(bool);doubleexpr(bool);usingnamespaceLexer;/using指令语句usingnamespaceError;/using指令语句仍以Parser:
prim为例,则在prim中出现的所有Lexer、Error模块中的名字均无需其名字空间名的约束而直接呈现(显露)给prim。
请看其代码:
实际应用中,是采用using声明语句只将声明的名字引入另一区域,还是用using指令语句将其名字空间中的所有名字引入到另一区域,应具体情况具体分析。
需要切记的是:
C+中的namespace是一个范围。
采用namespace,我们可以对程序进行逻辑上的组织与划分,程序越大,namespace的功能将愈强。
理想情况下,一个名字空间应该具有以下特性:
(1)是一个描述了具有逻辑统一性特征的相关成员的集合;
(2)实现了接口与实现的分离,对用户隐藏了namespace中的细节;,(3)采用恰当的using声明语句或using指令语句,让用户免去任何明显的名字描述负担。
7.2.3模块的多重接口采用C+的名字空间机制,我们可将程序以模块化的形式组织起来。
每一个模块应向调用者只暴露接口,而隐藏其实现。
一个模块通常有不同类别的用户。
对不同的用户而言,一个模块应向他们提供不同的接口。
例如,某模块的开发者能够涉及模块的全部成员,而这个模块的普通用户则只应当涉及对应于对外功能的那些成员。
因此,如果能够为不同的用户提供不同的接口,则该程序既友好又容易控制。
又由于面向开发者的接口变动的可能性通常会比面向普通用户的接口更大,从这个意义上来讲,为他们提供不同的接口,有利于隔离内部变动对外界的影响。
因此,软件开发中,一个模块应为不同的用户提供不同的模块接口。
我们仍以桌面计算器为例来说明此问题。
参看7.1节的图7.1,对于Parser模块而言,面对开发者,我们应提供该模块内所有成员的接口,而对于Driver模块而言,则只需提供Parser模块中的expr函数的接口即可,因Driver只需看到expr的接口。
C+语法上允许为一个名字空间定义多个同空间名的接口。
例如,对桌面计算器的Parser模块而言,为Driver可定义如下接口:
namespaceParserdoubleexpr(bool);对Parser的实现者(开发者)而言,亦可定义如下同空间名的接口:
namespaceParserdoubleprim(bool);doubleterm(bool);doubleexpr(bool);usingLexer:
get_token;/using声明语句usingLexer:
curr_tok;/using声明语句usingError:
error;/using声明语句,但实际的软件开发中,若需要一个模块向外提供多个接口,最好的实现方法是为每一个接口起一个namespace名,实际上采用这种实现方法,可以减少不必要的依赖和名字冲突。
因此,就上述问题而言,为Driver和Parser的开发者所提供的两个接口定义分别如下:
namespaceParser_interface/Driver的接口doubleexpr(bool);,C+的名字空间机制还有一些相对复杂、高级的用法,在此我们不作赘述,有兴趣的读者可参阅C+标准和相关书籍。
7.3异常处理对于一个实用的程序而言,没有错误处理是不可能的。
实际的(大型)程序是由许多人在不同的时间内开发出来的,许多出错处理任务并不一定能在(或者不应当在)发现出错的地方完成。
例如:
intgrandchild(inti)/if(出错了)returnerror_no;/不知如何处理,返回错误号,上述函数调用中多处出现了函数出错后对其错误不知如何(或不能够)处理的问题。
一个欠缺良好结构的程序(有时也可能是由于没有语言支持机制造成的),对其错误通常采用判断语句(如C+中的if和switch语句)进行判断与处理。
这种判断处理方式带来两个问题:
(1)大量的错误处理代码与程序的功能代码交织在一起,这不仅造成了程序结构的混乱,而且往往错误处理代码远大于程序的功能代码。
这使程序不仅很难进行正确的错误处理,而且程序
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 程序设计语言 李雁妮