C++编程中的命名空间基本知识讲解
命名空间是一个声明性区域,为其内部的标识符(类型、函数和变量等的名称)提供一个范围。命名空间用于将代码组织到逻辑组中,还可用于避免名称冲突,尤其是在基本代码包括多个库时。命名空间范围内的所有标识符彼此可见,而没有任何限制。命名空间之外的标识符可通过使用每个标识符的完全限定名(例如std::vector<std::string>vec;)来访问成员,也可通过单个标识符的using声明(usingstd::string)或命名空间中所有标识符的using指令(C++)(usingnamespacestd;)来访问成员。头文件中的代码应始终使用完全限定的命名空间名称。
下面的示例演示了一个命名空间声明和命名空间之外的代码可访问其成员的三种方法。
namespaceContosoData { classObjectManager { public: voidDoSomething(){} }; voidFunc(ObjectManager){} }
使用完全限定名:
ContosoData::ObjectManagermgr; mgr.DoSomething(); ContosoData::Func(mgr);
使用using声明,以将一个标识符引入范围:
usingWidgetsUnlimited::ObjectManager; ObjectManagermgr; mgr.DoSomething();
使用using指令,以将命名空间中的所有内容引入范围:
usingnamespaceWidgetsUnlimited; ObjectManagermgr; mgr.DoSomething(); Func(mgr);
using指令
通过using指令,可使用命名空间中的所有名称,而不需要命名空间名称为显式限定符。如果在一个命名空间中使用多个不同的标识符,则在实现文件中使用using指令(即*.cpp);如果仅使用一个或两个标识符,则考虑使用声明,以仅将这些标识符而不是命名空间中的所有标识符引入范围。如果本地变量的名称与命名空间变量的名称相同,则隐藏命名空间变量。使命名空间变量具有与全局变量相同的名称是错误的。
注意
using指令可以放置在.cpp文件的顶部(在文件范围内),或放置在类或函数定义内。
一般情况下,避免将using指令放置在头文件(*.h)中,因为任何包含该标头的文件都会将命名空间中的所有内容引入范围,这将导致非常难以调试的名称隐藏和名称冲突问题。在头文件中,始终使用完全限定名。如果这些名称太长,可以使用命名空间别名将其缩短。(请参阅下文。)
声明命名空间和命名空间成员
通常情况下,在头文件中声明一个命名空间。如果函数实现位于一个单独的文件中,则限定函数名称,如本示例所示。
//contosoData.h #pragmaonce namespaceContosoDataServer { voidFoo(); intBar(); } contosodata.cpp中的函数实现应使用完全限定名,即使将一个using指令放置于文件的顶部也是如此: #include"contosodata.h" usingnamespaceContosoDataServer; voidContosoDataServer::Foo() { //noqualificationbecauseusingdirectiveabove Bar(); } intContosoDataServer::Bar(){return0;}
可以在单个文件中的多个块中声明命名空间,也可在多个文件中声明命名空间。编译器在预处理过程中将各部分联接在一起,产生的命名空间中包含所有部分中声明的所有成员。一个相关示例是在标准库中的每个头文件中声明的std命名空间。
指定的命名空间的成员可以在定义的名称的显式限定所声明的命名空间的外部进行定义。但是,定义必须出现在命名空间中的声明位置之后,该命名空间包含在声明的命名空间中。例如:
//defining_namespace_members.cpp //C2039expected namespaceV{ voidf(); } voidV::f(){}//ok voidV::g(){}//C2039,g()isnotyetamemberofV namespaceV{ voidg(); } }
当跨多个头文件声明命名空间成员,并且未以正确的顺序包含这些标头时,可能出现此错误。
全局命名空间
如果未在显式命名空间中声明某个标识符,则该标识符属于隐式全局命名空间的一部分。通常情况下,如果可能,尝试避免在全局范围内进行声明,入口点main函数除外,它必须位于全局命名空间中。若要显式限定全局标识符,请使用没有名称的范围解析运算符,如::SomeFunction(x);中所示。这将使标识符与任何其他命名空间中具有相同名称的任何内容区分开来,并且还有助于使其他人更轻松地了解你的代码。
Std命名空间
所有C++标准库类型和函数都在std命名空间或嵌套在std内的命名空间中进行声明。
嵌套命名空间
可以嵌套命名空间。普通的嵌套命名空间具有对其父级成员的非限定访问权限,而父成员不具有对嵌套命名空间的非限定访问权限(除非它被声明为内联),如下面的示例所示:
namespaceContosoDataServer { voidFoo(); namespaceDetails { intCountImpl; voidBan(){returnFoo();} } intBar(){...}; intBaz(inti){returnDetails::CountImpl;} }
普通嵌套命名空间可用于封装不属于父命名空间的公共接口的一部分的内部实现详细信息。
内联命名空间(C++11)
与普通嵌套命名空间不同,内联命名空间的成员会被视为父命名空间的成员。这一特性使针对重载函数的依赖于参数的查找可以对父命名空间和嵌套内联命名空间中具有重载的函数起作用。它还可让你在内联命名空间中声明的模板的父命名空间中声明专用化。下面的示例演示在默认情况下,外部代码如何绑定到内联命名空间:
//Header.h #include<string> namespaceTest { namespaceold_ns { std::stringFunc(){returnstd::string("Hellofromold");} } inlinenamespacenew_ns { std::stringFunc(){returnstd::string("Hellofromnew");} } } #include"header.h" #include<string> #include<iostream> intmain() { usingnamespaceTest; usingnamespacestd; strings=Func(); std::cout<<s<<std::endl;//"Hellofromnew" return0; }
下面的示例演示如何在内联命名空间中声明的模板的父命名空间中声明专用化:
namespaceParent { inlinenamespacenew_ns { template<typenameT> structC { Tmember; }; } template<> classC<int>{}; }
可以将内联命名空间用作版本控制机制,以管理对库的公共接口的更改。例如,可以创建单个父命名空间,并将接口的每个版本封装到嵌套在父命名空间内的其自己的命名空间中。保留最新或首选的版本的命名空间限定为内联,并因此以父命名空间的直接成员的形式公开。调用Parent::Class的客户端代码将自动绑定到新代码。通过使用指向包含该代码的嵌套命名空间的完全限定路径,选择使用较旧版本的客户端仍可以对其进行访问。
Inline关键字必须应用到编译单元中命名空间的第一个声明中。
下面的示例演示一个接口的两个版本,每个版本位于一个嵌套命名空间中。通过v_10接口对v_20命名空间进行了某些修改,且该命名空间被标记为内联。使用新库并调用Contoso::Funcs::Add的客户端代码将调用v_20版本。尝试调用Contoso::Funcs::Divide的代码现在将获取一个编译时错误。如果它们确实需要该函数,则仍可以通过显式调用Contoso::v_10::Funcs::Divide访问v_10版本。
namespaceContoso { namespacev_10 { template<typenameT> classFuncs { public: Funcs(void); TAdd(Ta,Tb); TSubtract(Ta,Tb); TMultiply(Ta,Tb); TDivide(Ta,Tb); }; } inlinenamespacev_20 { template<typenameT> classFuncs { public: Funcs(void); TAdd(Ta,Tb); TSubtract(Ta,Tb); TMultiply(Ta,Tb); std::vector<double>Log(double); TAccumulate(std::vector<T>nums); }; } }
命名空间别名
命名空间名称必须是唯一的,这意味着通常它们不应太短。如果名称的长度使代码难以阅读,或在不能使用using指令的头文件中进行键入单调乏味,则可以使用用作实际名称的缩写的命名空间别名。例如:
namespacea_very_long_namespace_name{classFoo{};} namespaceAVLNN=a_very_long_namespace_name; voidBar(AVLNN::Foofoo){}
匿名或未命名的命名空间
可以创建显式命名空间,但不为其提供一个名称:
namespace { intMyFunc(){} }
这称为未命名的命名空间或匿名命名空间,在你想要使变量声明对于其他文件中的代码不可见(即为它们提供内部链接),而不必创建已命名的命名空间时非常有用。同一文件中的所有代码都可以看到未命名的命名空间中的标识符,但这些标识符以及命名空间本身在该文件外部(或更准确地说,在翻译单元外部)不可见。