c++名称空间
在 c++ 中,名称可以是变量、函数、结构、枚举、类以及类和结构的成员
假设这样一种情况,当一个班上有两个名叫 WYB 的学生时,为了明确区分它们,我们在使用名字之外,不得不使用一些额外的信息,比如他们的家庭住址,或者他们的其他身份等等。
同样,当随着项目的增大,名称相互冲突的可能性也将增加。使用多个厂商的类库是,可能会导致命名冲突,这样,编译器就无法判断用户想要使用哪个。
例如:两个库可能都定义了名为 HASH 和 TREE 的类,但定义的方式不兼容,用户可能希望使用一个库的 HASH 类,而使用另一个库的 TREE 类。这种冲突被称为命名冲突问题。
因此,引入了命名空间(namespace)这个概念,专门用于解决上面的问题,它可作为附加信息来区分不同库中相同名称的函数、类、变量等。使用了命名空间即定义了上下文。本质上,命名空间就是定义了一个范围。
A 有关概念
声明区域:可以在其中进行声明的区域。例如在函数外声明的变量,是全局变量,其声明区域为其声明所在的文件。
潜在作用域:从声明的位置开始,到其声明区域的结尾。
作用域:变量对于程序而言可见(可以调用)的范围。
变量并非在其潜在作用域内的任何位置都是可见的,当变量间出现重名的情况下,作用域小的会屏蔽作用域大的。例如:
#include <iostream> using namespace std; // 全局变量声明 int g = 20; int main () { // 局部变量声明 int g = 10; cout << g; return 0; }
当上面的代码被编译和执行时,它会产生下列结果:
B 定义命名空间
命名空间的定义使用关键字 namespace,后跟命名空间的名称,基本格式如下:
namespace namespace_name{ //代码声明 }
例如:
#include<bits/stdc++.h> using namespace std; namespace WYB{ double pail; //变量声明 void fetch(int,int); //函数原型 int girlfriend;//变量声明 struct parents{//结构声明 //... }; void print(){ printf("Inside namespace_WYB\n"); } } namespace SEN{ void pail(int,double);//函数原型 double fetch; //变量声明 int boyfriend; //变量声明 void print(){ printf("Inside namespace_SEN\n"); } } int main(){ WYB::print(); SEN::print(); return 0; }
运行,得到:
上述代码说明了两个问题:
- 任何名称空间中的名称都不会与其他名称空间中的名称发生冲突。即,WYB 中的 fetch函数 可以与 SEN 中的 fetch 变量共存。名称空间中的声明和定义规则同全局声明和定义规则相同。
- 可以通过作用域解析符 :: 来使用名称空间中的名称
另外,名称空间是开放的,即可以吧名称加入到已有的名称空间中。例如,下列语句将名称 waste 添加到 SEN 已有的名称列表中:
namespace SEN{ char * waste(const char *); }
同样,原来的 WYB 名称空间为 fecth() 函数提供了原型,可以在该文件后面(或另外一个文件中)再次使用 WYB 名称空间来提供该函数原型的函数体:
namespace WYB{ void fetch(int a,int b){ if(b==0){ throw "Division by zero condition!"; } } }
Tips:
未限定名称(unqualified name):未被装饰的名称,如 pail
限定的名称(qualified name):包含名称空间的名称,如 WYB:pail
C using声明和编译指令
真是麻烦!每次使用名称的时候都要对它进行限定
平时没有自己定义名称空间的时候为什么不用加 :: 呢?
原因是这样一条语句:
using namespace std;
(std 即 c++ 标准库)
我们并不希望每次使用名称时都对它进行限定,因此C++提供了两种机制(using声明和using编译指令)来简化对名称空间中名称的使用。using声明使特定的标识符可用,而using编译指令使整个名称空间可用。
举例:
int main(){
using namespace SEN; print(); using WYB::print;
print();
}
得到:
值得注意的是:(下列语句仍在main()中)
using WYB::print; print();
using namespace SEN; print();
其中using namespace 是using编译指令
using ... 是using声明
对于using声明
将特定的名称添加到它所属的声明区域中,例如main()中的using声明 WYB::print将print添加到main()定义的声明区域中。完成该声明后,便可以使用名称print代替WYB::print。
char fetch;
int main(){ using SEN::fetch;//put fetch into local namespace
double fetch;//Error!Already have a local fetch
cin>>fetch;//read a value into SEN::fetch cin>>::fetch;//read a value into global fetch return 0; }
上面的代码说明,由于using声明将名称添加到局部声明区域中,因此这个示例避免了将另一个局部变量也命名为fetch。另外,和其他局部变量一样,fetch也将覆盖同名的全局变量。
在函数的外面使用using声明时,将把名称添加到全局名称空间中。
对于using编译指令
using编译指令由名称空间名和它前面的关键字using namespace组成,它使该名称空间中的所有名称都可用,而不需要域名解析符,我们经常将这种格式用于名称空间std
Tips:有关using编译指令和using声明,需要记住的一点是,他们增加了名称冲突的可能性,但使用作用域解析符却不会出现这样的二义性问题,编译器将不会允许这样的情况。
二者之比较
使用 using 编译指令导入一个名称空间中所有的名称与使用多个using 声明是不一样的,而更像是大量
使用作用域解析运算符。
使用 using 声明时,就好像声明了相应的名称一样。
如果某个名称已经在函数中声明了,则不能用using声明导入相同的名称。然而,使用using编译指令时,将进行名称解析,就像在包含 using 声明和名称空间本身的最小声明区域中声明了名称一样。
在下面的示例中,名称空间为全局的。
如果使用 using 编译指令导入一个已经在函数中声明的名称,则局部名称将隐藏名称空间名,就像隐藏同
名的全局变量一样。不过仍可以像下面的示例中那样使用作用域解析运算符:
namespace Jill{ double bucket(double n){...} double fetch; struct Hill{...}; } char fetch;//global namespace int main(){ using namespace Jill; //import all namespaces names Hill Thrill; //create a type Jill::Hill structure double water=bucket(2);//use Jill::bucket(); double fetch; //not an error;hides Jill::fetch cin>>fetch; //read a value into the local fetch cin>>::fetch; //read a value into global fetch cin>>Jill::fetch; //read a value into Jill::fetch } int foom(){ Hill top; //Error Jill::Hill crest;//vaild }
在main( )中,名称 Jill::fetch 被放在局部名称空间中,但其作用域不是局部的,因此不会覆盖全局的
fetch.
然而,局部声明的fetch将隐藏Jill:fetch和全局fetch。.
然而,如果使用作用域解析运算符,则后两个fetch变量都是可用的。
需要指出的另一点是,虽然函数中的 using 编译指令将名称空间的名称视为在函数之外声明的,但它
不会使得该文件中的其他函数能够使用这些名称。因此,foom()函数不能使用未限定的标识符Hill
Tips:假设名称空间和声明区域定义了相同的名称。如果试图使用 using声明将名称空间的名称导入该声明区域,则这两个名称会发生冲突,从而出错。
如果使用 using 编译指令将该名称空间的名称导入该声明区域,则局部版本将隐藏名称空间版本。
一般说来,使用using声明比使用using编译指令更安全,这是由于它只导入指定的名称。如果该名称
与局部名称发生冲突,编译器将发出指示。using编译指令导入所有名称,包括可能并不需要的名称。如果
与局部名称发生冲突,则局部名称将覆盖名称空间版本,而编译器并不会发出警告。另外,名称空间的开
放性意味着名称空间的名称可能分散在多个地方,这使得难以准确知道添加了哪些名称。
D 名称空间嵌套
命名空间可以嵌套,您可以在一个命名空间中定义另一个命名空间,如下所示:
namespace namespace_name1 { // 代码声明 namespace namespace_name2 { // 代码声明 } }
可以通过使用 :: 运算符来访问嵌套的命名空间中的成员:
// 访问 namespace_name2 中的成员 using namespace namespace_name1::namespace_name2; // 访问 namespace:name1 中的成员 using namespace namespace_name1;
在上面的语句中,如果使用的是 namespace_name1,那么在该范围内 namespace_name2 中的元素也是可用的,如下所示:
#include <iostream> using namespace std; // 第一个命名空间 namespace first_space{ void func(){ cout << "Inside first_space" << endl; } // 第二个命名空间 namespace second_space{ void func(){ cout << "Inside second_space" << endl; } } } using namespace first_space::second_space; int main () { // 调用第二个命名空间中的函数 func(); return 0; }
当上面的代码被编译和执行时,它会产生下列结果:
E 引用
《C++ Primer Plus》 by Stephen Prata
C++ 命名空间 by 菜鸟教程