命名空间的作用
大型程序往往使用多个独立开发的库,这些库又会定义大量的全局名字,多个库将名字放在全局命名空间中将引发命名空间污染。
传统上,程序员通过将定义的全局实体名字设的很长来避免命名空间污染,这种方式比较费时费力。命名空间为防止名字冲突提供了更加可控的机制。
命名空间分割了全局命名空间,其中每个命名空间是一个作用域,通过在某个命名空间中定义库的名字,库的作者可以避免全局名字固有的限制。
命名空间的定义
一个命名空间的定义包含两部分,首先是关键字namespace,随后是命名空间的名字,在命名空间名字后面是一系列由花括号扩起来的声明和定义。
和其他名字一样,命名空间的名字也必须在定义它的作用域内保持唯一,命名空间既可以定义在全局作用域,也可以嵌套定义在其它命名空间中,但是不能定义在函数或类的内部。
定义在某个命名空间中的名字可以被该命名空间内的其它成员直接访问,也可以被这些成员内嵌作用域中的任何单位访问。位于命名空间之外的代码则必须明确指出所用的名字属于哪个命名空间。
namespace network
{
int code = 200;
namespace http
{
void testFunc()
{
std::cout << code << std::endl;
}
}
}
命名空间可以是不连续的,可以定义在不同的部分。
namespace network
{
int code = 100;
}
namespace network
{
void testFunc()
{
std::cout << code << std::endl;
}
}
不要把#include放在命名空间内部,因为如果这么做了,隐含的意思是把头文件中所有的名字定义成该命名空间的成员。
全局命名空间
全局命名空间是隐式的,我们可以使用::来显式访问全局作用域的成员。
内联命名空间
C++11引入了内联命名空间,内联命名空间中的名字可以被外层命名空间直接使用,定义内联命名空间的方式是在关键字namespace前添加关键字inline。
关键字inline必须出现在命名空间第一次定义的地方,后续再打开命名空间的时候可以写inline,也可以不写。
当我们迭代代码时,可以用命名空间管理新旧代码,把当前代码放在内联命名空间,旧代码放在一个非内联命名空间中。
inline namespace network
{
void testFunc()
{
std::cout << 100 << std::endl;
}
}
int main(void)
{
testFunc();
system("pause");
return 0;
}
未命名的命名空间
未命名的命名空间是指namespace后紧跟花括号扩起来的一系列声明语句。一个未命名的命名空间可以在某个给定的文件内不连续,但是不能跨多个文件,每个文件定义自己的未命名的命名空间,两个文件都含有未命名的命名空间时,这两个空间互相无关。
如果在一个头文件中定义了未命名的命名空间,则该命名空间中定义的名字将在每个包含了该头文件的文件中对应不同的实体。
在C++引入命名空间前,程序需要将名字声明成static使得对于整个文件有效,而对于文件外不可见,而现在更好的做法是使用未命名的命名空间。
namespace
{
void testFunc()
{
std::cout << 100 << std::endl;
}
}
命名空间别名
我们可以为命名空间的名字重新设定一个名字,例如下面代码中的namespace test = network::http;
namespace network
{
namespace http
{
void testFunc()
{
std::cout << "hello" << std::endl;
}
}
}
int main(void)
{
namespace test = network::http;
test::testFunc();
system("pause");
return 0;
}
using声明
一条using声明语句一次只引入命名空间的一个程序,在此过程中国,外层作用域的同名实体将被隐藏。
namespace network
{
void testFunc()
{
std::cout << "hello" << std::endl;
}
}
int main(void)
{
using network::testFunc;
testFunc();
system("pause");
return 0;
}
using指示
using指示可使得某个特定的命名空间中所有的名字都可见,这样我们就无需再为它们添加任何前缀限定符了。
namespace network
{
void testFunc()
{
std::cout << "hello" << std::endl;
}
}
int main(void)
{
using namespace network;
testFunc();
system("pause");
return 0;
}
头文件如果在顶层作用域中含有using指示或using声明,则会将名字注入到所有包含了该头文件的文件中,从而造成命名空间污染,因此,头文件中最多只能在它的函数或命名空间内使用using指示或using声明。
命名空间中名字的查找
对于命名空间内部名字的查找遵循常规的查找规则,即由内向外依次查找每个外层作用域,外层作用也可能是一个或多个嵌套的命名空间,直到最外层的全局命名空间查找过程终止。
当我们给函数传递一个类类型对象时,除了在常规的作用域查找外还会查找实参类所属的命名空间,这一例外对于传递类的指针或引用同样有效。下面的代码中operator<<属于命名空间std,但是我们不用std::限定符和using声明就可以调用operator<<。
std::cout << "hello" << std::endl;
标准库有很多名字太过常用,例如std::move和std::forward,如果我们调用这两个函数时隐藏std::,出现名字冲突的概率会很高,所以调用这些函数的时候最好加上命名空间限定符。
重载与命名空间
using声明语句声明的是一个名字,而不是一个特定的函数,一个using声明包括了重载函数的所有版本。一个using声明引入的函数将重载该声明语句所属作用域中已有的其它同名函数。
namespace network
{
void testfunc(int value)
{
std::cout << value << std::endl;
}
}
void testfunc(double value)
{
std::cout << value << std::endl;
}
using network::testfunc;
int main(void)
{
testfunc(100);
testfunc(1.34);
system("pause");
return 0;
}
using指示将命名空间的成员提升到外层作用域中,如果命名空间的某个函数与该命名空间所属作用域函数同名,则命名空间的函数将被添加到重载集合中。
与using声明不同的是,对于using指示,引入一个与已有函数形参列表完全相同的函数不会产生错误,此时我们指明调用的是命名空间的版本还是当前作用域的版本即可。
namespace network
{
void testfunc(int value)
{
std::cout << value << std::endl;
}
}
void testfunc(int value)
{
std::cout << value << std::endl;
}
using namespace network;
int main(void)
{
::testfunc(100);
network::testfunc(10);
system("pause");
return 0;
}
如果存在多个using指示,则来自每个命名空间的名字都会成为候选函数集的一部分。