由独立开发的库构成的复杂程序更有可能遇到 名字冲突。这种名字冲突问题称为 命名空间污染 问题。
一个命名空间是一个作用域,通过在命名空间内部定义库中的名字,库的作者可以避免全局名字固有的限制。
一、命名空间的定义
以关键字 namespace开始,后接命名空间的名字:
namespace c_primer
{
class Sales_item
{
//...
};
Sales_item operator+(const Sales_item &, const Sales_item &);
class Query
{
public:
Query(const std::string &);
std::ostream &display(std::ostream &)const;
//...
};
class Query_base
{
//...
};
}
定义了c_primer的命名空间,它有四个成员。
命名空间的名字在定义该命名空间的作用域中必须是唯一的。命名空间不能在函数或类内部定义。
1.每个命名空间是一个作用域
在命名空间中定义的名字可以被命名空间中的其他成员直接访问,命名空间外部的代码必须指出名字定义在哪个命名空间中:
c_primer::Query q = c_primer::Query("hello");
q.display(cout);
2.从命名空间外部使用命名空间成员
可以使用using声明来获得对我们知道将经常使用的名字的直接访问:
using c_primer::Query;
Query q = Query("world");
q.display(cout);
3.命名空间可以是不连续的
命名空间由它的分离定义部分的总和构成,命名空间是累积的。
一个命名空间的分离部分可以分散在多个文件中,在不同文本文件中的命名空间定义也是累积的。
namespace namespace_name
{
//...
}
既可以定义新的命名空间,也可以添加到现存命名空间中。
如果名字namespace_name不是引用前面定义的命名空间,则用该名字创建新的命名空间,否则,这个定义打开一个已存在的命名空间,并将这些新声明加到那个命名空间。
4.接口和实现的分离定义多个不相关类型的命名空间应该使用分离的文件,表示该命名空间定义的每个类型。
//1 ---Sales_item.h---
#ifndef SALES_ITEM_H_INCLUDED
#define SALES_ITEM_H_INCLUDED
namespace cplusplus_primer
{
class Sales_item
{
//...
};
Sales_item operator+(const Sales_item &,const Sales_item &);
}
#endif // SALES_ITEM_H_INCLUDED
//2 ---Query.h---
#ifndef QUERY_H_INCLUDED
#define QUERY_H_INCLUDED
#include <fstream>
#include <string>
namespace cplusplus_primer
{
class Query
{
public:
Query(const std::string &);
std::ostream &display(std::ostream &) const;
};
class Query_base
{
//...
};
}
#endif // QUERY_H_INCLUDED
//3 ---Sales_item.cpp---
#include "Sales_item.h"
namespace cplusplus_primer
{
//定义
}
//4 ---Query.cpp---
#include "Query.h"
namespace cplusplus_primer
{
//定义
}
5.定义命名空间成员
在命名空间内部定义的函数可以使用同一命名空间中定义的名字的简写形式:
namespace cplusplus_primer
{
std::istream &operator>>(std::istream &in,Sales_item &)
{
//...
return in;
}
}
在命名空间的外部定义命名空间成员:名字的命名空间声明必须在作用域中 , 并且定义必须指定该名字所属的命名空间 :
cplusplus_primer::Sales_item
cplusplus_primer::operator+(const Sales_item &lhs,
const Sales_item &rhs)
{
Sales_item ret(lhs);
//...
return ret;
}
定义看起来类似于定义在类外部的类成员函数
,
返回类型和函数名由命名空间名字限定。一旦看到完全限定的函数名
,
就处于命名空间的作用域中。因此
,
形参表和函数体中的命名空间成员引用可以使用非限定名引用
Sales_item
。
6.不能在不相关的命名空间中定义成员
虽然可以在命名空间定义的外部定义命名空间成员,对这个定义可以出现的地方仍有些限制:只有包围成员声明的命名空间可以包含成员的定义。例如,operator+ 既可以定义在命名空间cplusplus_primer中,也可以定义在全局作用域中,但它不能定义在不相关的命名空间中。
7.全局命名空间
定义在全局作用域的名字(在任意类、函数或命名空间外部声明的名字)是定义在全局命名空间中的。全局命名空间是隐式声明的,存在于每个程序中。在全局作用域定义实体的每个文件将那些名字加到全局命名空间。
可以用作用域操作符引用全局命名空间的成员。因为全局命名空间是隐含的,它没有名字,所以记号
::member_name;
二、嵌套命名空间
即一个嵌套作用域--其作用域嵌套在包含它的命名空间内部。规则:外围命名空间中声明的名字 被 嵌套命名空间中同一名字声明 所屏蔽。
当库提供者需要防止库中每个部分的名字与库中其他部分的名字冲突的时候,嵌套命名空间是很有用的。
嵌套命名空间中成员的名字由外围命名空间的名字和嵌套命名空间的名字构成:
cplusplus_primer::QueryLib::Query q;
三、未命名的命名空间
未命名的命名空间以关键字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++不赞成文件静态声明。不赞成的特征是在未来版本中可能不支持的特征。应该避免文件静态而使用未命名空间代替[用于声明局部于文件的实体]。
四、命名空间成员的使用1.using声明,扼要重述
using std::map;
using std::pair;
using std::size_t;
using std::string;
using std::vector;
2.
using
声明的作用域
从using声明点开始,直到包含using声明的作用域的末尾,名字都是可见的。外部作用域中定义的同名实体被屏蔽。
简写名字只能在声明它的作用域及其嵌套作用域中使用,一旦该作用域结束了,就必须使用完全限定名。
using声明可以出现在全局作用域、局部作用域或者命名空间作用域中。类作用域中的 using声明局限于被定义类的基类中定义的名字。
3.命名空间别名可用命名空间别名将较短的同义词与命名空间名字相关联。例如,像
namespace cplusplus_primer
{
//...
}
可以与较短的同义词相关联:namespace primer = cplusplus_primer;
如果原来的命名空间名字是未定义的,就会出错。
命名空间别名也可以引用嵌套的命名空间:
namespace Qlib = cplusplus_primer::QueryLib;
Qlib::Query tq;
一个命名空间可以有许多别名,所有别名以及原来的命名空间都可以互换使用。
4.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 声明引起的二义性错误在声明点而不是使用点检测,因此更容易发现和修正。