第15章名称空间(Namespace)•大型程序往往是由团队开发的,即使是个人编写的程序,随着代码量的增多,变量、函数、类的名字冲突的现象时有发生,有的情况下,编译器会指明错误所在,但有时候会发生一些察觉不到的覆盖,让程序员对出现的错误摸不着头脑。•很多厂商也提供了快捷的第3方类库,用户不用关心库中的类是如何实现的,直到如何调用接口使用即可,但多个厂商定义的变量、函数和类的名字可能会发生冲突,同样是初始化操作,甲公司类库提供了initial函数,乙公司可能也提供了initial函数,如果在程序中同时使用了两个公司的类库,initial函数对应哪个版本呢?•为了解决这些问题,新的C++标准提供了名称空间机制。旧标准中(ANSI/ISO1998)并没有该项机制,所以,一些特别老的编译器可能并不支持名称空间特性。15.1什么是名称空间•旧的标准中,仅仅依靠名称在程序中的作用域和可见域来区分同名实体,在第6章中已经介绍了实体的作用域和可见域的概念,由于“屏蔽”等原因,可见域一般是作用域的子集。•旧标准中使用“#includeiostream.h”形式的头文件,不需要using指令指明名称空间;•新标准中使用“#includeiostream”形式的头文件,需要using指令指明名称空间。15.1.1名称空间范例•名称空间通过定义一种新的声明区域来创建命名的名称空间,一个名称空间中的实体不会和另外一个名称空间中的同名实体冲突,来看一个简单形象的例子15-1,其既能说明名称空间是怎么回事,也演示了如何定义全局的名称空间。15.1.2定义名称空间•C++中定义名称空间的基本格式为:namespace名称空间名{变量类型变量;函数返回类型函数原型;}15.2实体的作用域与可见域•名称空间可以如代码15-1中的yaya和abao一样,定义成全局的,也可以将一个名称空间定义在另一个名称空间内,形成名称空间的嵌套,但名称空间的定义不能在代码块内,因此,默认情况下,名称空间中的实体作用域是全局的。15.2.1实体可见域•名称空间中实体的作用域是全局的,并不意味着其可见域也是全局的,如果不使用作用域限定符和using机制,抛开名称空间嵌套和内部屏蔽的情况,实体的可见域是从实体创建到该名称空间结束(如果有名称空间的嵌套,则内部名称空间中的实体可能会屏蔽外部名称空间中的实体,这在稍后会讲到),在名称空间外,该实体是不可见的。•来看一段示例,见代码15-2。15.2.2使用作用域限定符::来访问其他名称空间中的实体•在某个名称空间中定义或创建的程序实体,如果要在其他名称空间中或外部函数中访问,必须如下两种方式之一来使实体可见:–使用作用域限定符::–使用using声明机制•代码15-3使用作用域限定符A::dispA()和B::dispB()对代码15-2进行修改。15.2.3using声明机制•如果不希望在每次使用名称空间中实体时都使用作用域限定符,可使用using声明机制扩展其可见域,using声明的基本格式为:using名称空间::实体名;•如“usingA::dispA()”,至于using声明语句将该实体的可见域扩展到什么程度,这取决于using语句的书写位置,换言之,这取决于using语句的可见域。•1.using声明在全局区域:见代码15-4•2.using声明语句在局部:见代码15-515.2.4using声明带来的多重声明问题(二义性)•如果using声明使用不当,很容易引起多重声明错误,比如,已经定义了全局函数disp,却还使用全局using声明语句“usingA::disp”,假设没有屏蔽发生,那么调用disp()时,编译器不确定是全局函数版本还是A::disp(),引发多重声明错误。•变量名同样存在这种问题,假设有两个名称空间A、B中都定义了int型变量num,在程序的某处需要使用num,如果写出如下代码:usingA::num;usingB::num;num=5;则最后一句对num的赋值操作具有二义性,因此,应合理使用using声明机制。15.2.5空间内的“屏蔽”•在一个空间内,变量可见域也有“屏蔽”规则,如内部变量对外部变量的屏蔽等是和第6章中介绍的内容使一致的,见代码15-6。15.2.6先声明,后使用•和普通变量一样,在使用名称空间中的实体前,必须保证其有效,举例来说,在使用一个变量前,必须对该变量进行声明。例如,如果将代码15-6中名称空间A定义修改为:namespaceA//创建名称空间A{voiddispA(){intnum=3;coutdispA函数中的num:numendl;coutA中的num:A::numendl;coutB中的num:B::numendl;cout外部的num:::numendl;}intnum=1;//A中声明的num}•编译运行,编译器将给出如下出错信息:•errorC2039:'num':isnotamemberof'A'15.3名称空间的作用域与可见性•原则上讲,名称空间的作用域是全局的,但其可见域却并非如此,而且,不论使用限定符还是使用using声明语句,都要求名称空间可见,回头看以下前面给出的代码,就代码15-6来说,如果将namespaceB的定义放在namespaceA定义之后,编译器将指出错误,using语句同样如此,如果在using声明时,namespace尚未定义,或者说namespace已经定义,但声明的实体尚未包含在此namespace中,编译器同样会指出错误,因此,名称空间同样要先定义、后使用。15.3.1名称空间的定义策略•以代码15-6为基础,假设想实现如下功能:A::dispA函数中要访问B::num,这要求B定义在A前,同时,在B中增加dispB函数,其中调用A::dispA()函数,这要求A定义在B之前,如此看来,上述功能似乎不太可能会实现。•实则不然,这取决于名称空间的定义策略,在前面提及,名称空间中函数的定义和实现可以分开进行,这是我们解决问题的突破口,见代码15-7。•另外,函数原型可以多次声明,见代码15-815.3.2推荐用法•为了在程序的多个文件中合理使用名称空间,需要合理组织名称空间的定义,和类定义相似(但又不同,类定义中成员变量不被创建,而名称空间中的变量在声明时被创建),一种推荐的做法是将名称空间的声明接口(仅仅包括函数原型)放在.h头文件中,而将变量声明和函数实现放在单独.cpp文件中,这样在使用头文件的地方,只要包含相应的头文件即可。15.3.3名称空间嵌套•名称空间可以定义在另一个名称空间内,以单层嵌套为例,要访问内部名称空间中的实体,必须采用“外部名称空间::内部名称空间::实体名”的形式,如果是多层嵌套,还要多次使用作用域限定符。•见代码15-1015.3.4using编译指令•前面介绍过通过using声明语句使得某个空间中的特定实体可见,using编译指令比using声明更进一步,通过“usingnamespace名称空间名;”的形式,使得名称空间中的所有实体都可见,不再需要作用域限定符。•如usingnamespacestd;15.3.5匿名的名称空间•也可以通过省略名称空间的名称来创建匿名的名称空间,此时,该无名空间中的实体的可见性无法扩展(既不能采用“名称空间::实体”的形式,也不能采用using声明机制扩展).•未命名的名字空间中定义的名字只在包含该名字空间的文件中可见,但其中的变量的生存期却从程序开始到程序结束。如果有多个文件包含未命名的名字空间,这些名字空间是不相关的,即使这些名字空间中定义了相同的名字,这些名字也代表不同的对象。15.4对名称空间的思考•下面引用当前流行的名称空间使用指导原则:1.使用在已命名的名称空间中声明的变量,而不是使用外部全局变量。2.使用已命名的名称空间中声明的变量,而不是静态全局变量。3.如果开发了一个函数库或者类库,将其放在一个名称空间中。事实上,C++提倡将标准函数库放在名称空间std中。4.仅将编译指令using作为一种将旧代码转换为名称空间的权宜之计。5.对于using声明,首先将其作用域设置为局部而不是全局。6.不要在头文件中使用using编译指令,这样,使得可用名称变得模糊,容易出现二义性,其次,包含头文件的顺序可能会影响程序的行为,如果非要使用using编译指令,建议放在所有#include预编译指令后。•名称空间相当于是对在原有名称层次的基础上又扩展了一层,而且是最外面的一层,原始的名称层次进化规则简单归纳如图15-1所示。15.5小结•本章探讨了名称空间的用法,这是C++新增加的一个特性,其目的是为了减少冲突,这在大型程序组织中十分有效。使用名称空间,关键是掌握空间中实体的作用域与可见域,以及名称空间的作用域与可见域的知识,理解其实质,做到知其然,知其所以然。•作用域限定符::、using声明机制和using编译机制是3种常用的扩展实体可见域的方式,使名称空间中的特定实体或全部实体在声明可见域内可用。名称空间内实体的访问规则和原来介绍的没有名称空间时的情况类似,名称空间还支持嵌套层次结构,在外部使用内层空间时,必须使用多重作用域限定符的形式。