用于大型程序的工具
--命名空间[续1]
二、嵌套命名空间
一个嵌套命名空间即是一个嵌套作用域 —— 其作用域嵌套在包含它的命名空间内部。嵌套命名空间中的名字遵循常规规则:外围命名空间中声明的名字被嵌套命名空间中同一名字的声明所屏蔽。嵌套命名空间内部定义的名字局部于该命名空间。外围命名空间之外的代码只能通过限定名引用嵌套命名空间中的名字。
嵌套命名空间可以改进库中代码的组织:
namespace cplusplus_primer { namespace QueryLib { class Query { //... }; Query operator&(const Query &,const Query &); } namespace Bookstore { class Item_base { //... }; class Bulk_item : public Item_base { //... }; } }
当库提供者需要防止库中每个部分的名字与库中其他部分的名字冲突的时候,嵌套命名空间是很有用的。
嵌套命名空间中成员的名字由外围命名空间的名字和嵌套命名空间的名字构成:
cplusplus_primer::QueryLib::Query q;
//P604 习题17.17 namespace cplusplus_primer { namespace simpleSI { //...1 } namespace handleSI { //...2 } }
三、未命名的命名空间
命名空间可以是未命名的,未命名的命名空间在定义时没有给定名字。未命名的命名空间以关键字namespace开头,接在关键字namespace后面的是由花括号定界的声明块。
未命名的命名空间与其他命名空间不同,未命名的命名空间的定义可以在给定的文件中不连续并且局部于特定文件,从不跨越多个文本文件。每个文件都有自己的未命名的命名空间。
未命名的命名空间用于声明局部于文件的实体。在未命名的命名空间中定义的变量在程序开始时创建,在程序结束之前一直存在。
未命名的命名空间中定义的名字可直接使用,毕竟,没有命名空间名字来限定它们。不能使用作用域操作符来引用未命名的命名空间的成员。
未命名的命名空间中定义的名字只在包含该命名空间的文件中可见。如果另一文件包含一个未命名的命名空间,两个命名空间不相关。两个命名空间可以定义相同的名字,而这些定义将引用不同的实体。
未命名空间中定义的名字可以在定义该命名空间所在的作用域中找到。如果在文件的最外层作用域中定义未命名的命名空间,那么,未命名的空间中的名字必须与全局作用域中定义的名字不同:
int i; namespace { int i; } i = 10; //Error:ambiguous
像任意其他命名空间一样,未命名的命名空间也可以嵌套在另一命名空间内部。如果未命名的命名空间是嵌套的,其中的名字按常规方法使用外围命名空间名字访问:
namespace local { namespace { int i; } } local::i = 10; //OK
【小心地雷】
如果头文件定义了未命名的命名空间,那么,在每个包含该头文件的文件中,该命名空间中的名字将定义不同的局部实体。
在所有其他方式中,未命名的命名空间的成员都是普通程序实体。
【未命名的命名空间取代文件中的静态声明】
在标准C++中引入命名空间之前,程序必须将名字声明为static,使它们局部于一个文件。文件中静态声明的使用从C语言继承而来,在C 语言中,声明为static的局部实体在声明它的文件之外不可见。
C++不赞成文件静态声明。不赞成的特征是在未来版本中可能不支持的特征。应该避免文件静态而使用未命名空间代替[用于声明局部于文件的实体]。
//P605 习题17.19 namespace cplusplus_primer { namespace Matrix_primer { class matrix { //... }; matrix operator*(const matrix &,const matrix &); } } cplusplus_primer::Matrix_primer::matrix cplusplus_primer::Matrix_primer::operator*(const matrix &,const matrix &);
四、命名空间成员的使用
【最佳实践】
除了在函数或其他作用域内部,头文件不应该包含 using指示或using声明。在其顶级作用域包含using指示或 using声明的头文件,具有将该名字注入包含该头文件的文件中的效果。头文件应该只定义作为其接口的一部分的名字,不要定义在其实现中使用的名字。
1、using声明,扼要重述
using std::map; using std::pair; using std::size_t; using std::string; using std::vector;
一个using声明一次只引入一个命名空间成员,它使得无论程序中使用哪些名字,都能够非常明确。
2、using声明的作用域
从using声明点开始,直到包含using声明的作用域的末尾,名字都是可见的。外部作用域中定义的同名实体被屏蔽。
简写名字只能在声明它的作用域及其嵌套作用域中使用,一旦该作用域结束了,就必须使用完全限定名。
using声明可以出现在全局作用域、局部作用域或者命名空间作用域中。类作用域中的 using声明局限于被定义类的基类中定义的名字。
3、命名空间别名
可用命名空间别名将较短的同义词与命名空间名字相关联。例如,像
namespace cplusplus_primer { //... }
可以与较短的同义词相关联:
namespace primer = cplusplus_primer;
如果原来的命名空间名字是未定义的,就会出错。
命名空间别名也可以引用嵌套的命名空间:
namespace Qlib = cplusplus_primer::QueryLib; Qlib::Query tq;
一个命名空间可以有许多别名,所有别名以及原来的命名空间都可以互换使用。
4、using指示
using指示无法控制哪些名字可见 -- 它们都是可见的。
using指示使得特定命名空间所有名字可见,没有限制。短格式名字可从 using指示点开始使用,直到出现using指示的作用域的末尾。
【小心地雷】
可以尝试用using指示编写程序,但在使用多个库的时候,这样做会重新引入名字冲突的所有问题。
5、using指示与作用域
using声明将名字直接放入出现 using声明的作用域,好像using声明是命名空间成员的局部别名一样。因为这种声明是局部化的,冲突的机会最小。
using指示不声明命名空间成员名字的别名,相反,它具有将命名空间成员提升到包含命名空间本身和using指示的最近作用域的效果。
如下例:在f中,将好像A中的名字出现在全局作用域中 f的定义之前一样:
namespace A { int i,j; } void f() { using namespace A; cout << i * j << endl; }
【最佳实践】
using指示有用的一种情况是:用在命名空间本身的实现文件中。
6、using指示例子
namespace blip { int bi = 16,bj = 15,bk = 23; } int bj = 0; void manip() { using namespace blip; ++ bi; //17 ++ bj; //Error:ambiguous ++ ::bj; //1 ++ blip::bj; //16 int bk = 97; ++ bk; //98 }
manip中的 using提示使 manip能够直接访问blip中的所有名字:使用它们的简化形式,该函数可以引用这些成员的名字。
【警告:避免使用using指示】
using指示注入来自一个命名空间的所有名字,它的使用是靠不住的:只用一个语句,命名空间的所有成员名就突然可见了。虽然这个方法看似简单,但也有它自身的问题。如果应用程序使用许多库,并且用using 指示使得这些库中的名字可见,那么,全局命名空间污染问题就重新出现。
而且,当引入库的新版本的时候,正在工作的程序可能会编译失败。如果新版本引入一个与应用程序正在使用的名字冲突的名字,就会引发这个问题。
另一个问题是,由using指示引起的二义性错误只能在使用处检测,这个后来的检测意味着,可能在特定库引入很久之后才引发冲突,如果程序开始使用该库的新部分,就可能引发先前未检测到的冲突。相对于依赖于using指示,对程序中使用的每个命名空间名字使用 using声明更好,这样做减少注入到命名空间中的名字数目,由using 声明引起的二义性错误在声明点而不是使用点检测,因此更容易发现和修正。