C++ 名字空间namespace的使用
A namespace is a scope.
C++ provides namespaces to prevent name conflicts.
A namespace is a mechanism for expressing logical grouping. That is, if some declarations logically belong together to some criteria(准则), they can be put in a common namespace to express that fact.
That is, the namespace is the mechanism(机制) for supporting module programming paradigm(范型).
C++名字空间是一种描述逻辑分组的机制。也就是说,如果有一些声明按照某种准则在逻辑上属于同一个模块,就可以将它们放在同一个名字空间,以表明这个事实。名字空间对于模块化的程序设计有重要作用。
例如:
// x.h namespace MyNamespace1 { int i; void func(); class CHello { public: void print(); } };
// y.h namespace MyNamespace2 { class CHello { public: void print(); } };
// z.cpp #include"x.h" #include"y.h" int main() { MyNamespace1:: CHello x; //声明一个文件x.h中类MyClass的实例x MyNamespace2:: CHello y; //声明一个文件x.h中类MyClass的实例x x. print(); //调用文件x.h中的函数print y. print(); //调用文件y.h中的函数print return 0; }
从上面可以看出,名字空间中可以定义变量、函数和类等自定义数据类型,它们具有相同的作用范围。对于不同的名空间,可以定义相同的变量名、函数名、类名等,在使用的时候,只要在成员前区分开不同的名字空间就可以了。名字空间实质上是一个作用域,上例中通过引入名字空间解决了名字冲突。
名字空间的定义:
namespace是名字空间,可以防止多个文件有重复定义成员(变量,函数,类等)。
名字空间是一个作用域,其形式以关键字namespace开始,后接名字空间的名字,然后一对大括号内写上名字空间的内容。
//point.h namespace spacepoint { struct point { int x; int y; }; void set(point& p, int i, int j) { p.x = i; p.y = j; } } //point.cpp using namespace spacepoint; int main() { point p; set(p,1,2); return 0; }
在头文件point.h中定义了一个名字空间spacepoint,在point.cpp文件中若要访问spacepoint中的成员,就必须加:using namespace spacepoint,表示要使用名字空间spacepoint,若没加这句代码,则会有编译错误:
//point.cpp int main() { point p; //error C2065: “point”: 未声明的标识符 set(p,1,2); // error C3861: “set”: 找不到标识符 return 0; }
名字空间的成员,是在名字空间定义中的花括号内声明了的名称。可以在名字空间的定义内,定义命名空间的成员(内部定义)。也可以只在名字空间的定义内声明成员,而在名字空间的定义之外,定义名字空间的成员(外部定义)。命名空间成员的外部定义的格式为:
命名空间名::成员名 ……
例如:
// space.h namespace Outer // 命名空间Outer的定义 { int i; // 命名空间Outer的成员i的内部定义 namespace Inner // 子命名空间Inner的内部定义 { void f() { i++; } // 命名空间Inner的成员f()的内部定义,其中的i为Outer::i int i; void g() { i++; } // 命名空间Inner的成员g()的内部定义,其中的i为Inner::i void h(); // 命名空间Inner的成员h()的声明 } void f(); // 命名空间Outer的成员f()的声明 } void Outer::f() {i--;} // 命名空间Outer的成员f()的外部定义 void Outer::Inner::h() {i--;} // 命名空间Inner的成员h()的外部定义
域操作符:
The scope resolution operator (域解析符):: occurs between the namespaces name and the variable.
“::”就是作用域操作符,首先要有名字空间的概念。用户声明的名字空间成员名自动被加上前缀,名字空间名后面加上域操作符“::”,名字空间成员名由该名字空间名进行限定修饰。名字空间成员的声明被隐藏在其名字空间中,除非我们为编译器指定查找的声明的名字空间,否则编译器将在当前域及嵌套包含当前域的域中查找该名字的声明。
#include "space.h" int main ( ) { Outer::i = 0; Outer::f(); // Outer::i = -1; Outer::Inner::f(); // Outer::i = 0; Outer::Inner::i = 0; Outer::Inner::g(); // Inner::i = 1; Outer::Inner::h(); // Inner::i = 0; std::cout << "Hello, World!" << std::endl; std::cout << "Outer::i = " << Outer::i << ", Inner::i = " << Outer::Inner::i << std::endl; }
上例中Outer::及Outer::Inner::,都是为编译器制定了要查找的名字空间,名字空间Outer中函数:void f() { i++; },其中i为嵌套域中的Outer::i。对于函数Outer::Inner::g() { i++ },其中的i为当前域中的Outer::Inner::i。因为名字空间就是作用域,所有普通的作用域规则也对名字空间成立。因此,如果一个名字先前已经在空间或者外围作用域里声明过,就可以直接使用。
C++标准程序库中的所有标识符都被定义于一个名为std的namespace中。由于namespace的概念,使用C++标准程序库的任何标识符时,可以有三种选择:
1、直接指定标识符。例如std::ostream而不是ostream。完整语句如下:
#include <iostream> std::cout << "hello!!"<< std::endl;
2、使用using关键字进行声明
显然,当某个名字在它自己的名字空间之外频繁使用时,在反复写它时都要加上名字空间来作限定词,是一件令人厌烦的事情。这时,可以通过一个使用声明而清楚掉,只需要在某个地方说明,在这个作用域其后的代码中,使用此名字时可以自动解析出此名字所在的空间。例如:
#include <iostream> using std::cout; using std::endl;
此后在使用cout和endl时,都无需再加上名字空间前缘了:
cout <<"hello!!"<< endl;
3、最方便的就是使用指令using namespace std;
一个使用指令能把来自另一个名字空间的所有名字都变成在当前名字空间内可用,就像这些名字就在当前名字空间中一样。例如,在一个名字空间内有命令 using namespace std; 则在此后的代码中(当前空间最小局部内),对于所有名字都无需有名字空间前缀即可使用。
#include <iostream> using namespace std;
这样命名空间std内定义的所有标识符都有效(曝光)。就好像它们被声明为全局变量一样。那么以上语句可以如下写:
cout <<"hello!!"<< endl;
A namespace is a scope. Thus, “namespace is a very fundamental and relatively simple concept. The larger a program is, the more useful namespaces are to express logical separations of its parts.
Ideally(理想地), a namespace should
[1] express a logically coherent (相关的) set of features.
[2] not give users access to unrelated features,
[3] not impose a significant notational burden on users.(给用户显著需要注意的负担)
无名的命名空间
在C++中 我们可以用未命名的名字空间(unnamed namespace)声明一个局部于某一文件的实体,未命名的名字空间以关键字namespace开头,同时该名字空间是没有名字的,所以在关键字 namespace后面没有名字,而在关键字 namespace后面使用花括号包含声明块。在一些情况下,名字空间的作用仅仅是为了保持代码的局部性。
假设,在A.cpp中有如下定义:
//A.cpp namespace { void show(){ cout << "hello!" << endl; } }
这就像后面跟着using编译指令一样,即在该名字空间空声明的名称的作用域为:从声明到该声明区域末尾。这里show()函数在A.cpp中相当于全局函数,若还有show()函数的定义会出错:
#include <iostream> using namespace std; void show() { cout << "hello!" << endl; } namespace { void show() { cout << "hello!!" << endl; } } int main() { show(); // error C2668: “show”: 对重载函数的调用不明确 return 0; }
同一个文件中可以有多个未命名的名字空间,但名字空间中不能有相同的成员:
#include <iostream> using namespace std; namespace { void show(){ cout << "hello!!" << endl; } } namespace { // error C2084: 函数“void `anonymous-namespace'::show(void)”已有主体 void show(){ cout << "hello!!!" << endl; } } int main() { show(); return 0; }
同一个文件中的多个未命名的名字空间,若它们位于不同的作用域,在它们名字空间中可以有相同的成员。下列中的两个无命名空间中有相同的函数,但它们位于不同的作用域,若访问aaa名字中的show函数,就需要加上作用域:aaa::show()。
#include <iostream> using namespace std; namespace //全局作用域下的无名字名字空间 { void show() { cout << "hello!!" << endl; } } namespace aaa { namespace //// aaa作用域下的无名字名字空间 { void show() { cout << "hello!!!" << endl; } } } int main() { show(); aaa::show(); return 0 }
名字空间的别名
名字空间的别名一般是为了方便使用。如果用户给他们的名字空间名字起得太短,不同名字空间也可能引起冲突;而名字空间太长可能又变得不很实用。这时,可以通过在使用指令中给名字空间提供一个别名来改善情况。取别名的语法:
namespace别名 = 原空间名;
能使用别名来访问原命名空间里的成员,但不能为源命名空间引入新的成员。例如:
#include <iostream> namespace mystd = std; mystd::cout << "hello!!" << mystd::endl;
在namespace mystd = std之后,可以用mystd来代表名字空间std。
组合和选择
我们常常需要从现有的界面出发组合出新的界面,方法是在新界面中用使用using指令引入已有的界面。
namespace A { int i; void func1(){} void func2(){} } namespace B { int j; void func3(){} void func4(){} } namespace C { using namespace A; using namespace B; void func5(int m,int n){} } using namespace C; int main() { func1(); func3(); func5(i,j); return 0; }
在名字空间C中,通过using组合了名字空间A和B,形成了新的界面。这样,使用using namespace C后,这样不用using namespace C、using namespace B,就可以直接访问func1()、func3()。
有时我们只是想从一个名字空间里选用几个名字,通过使用声明来做,可以使从名字空间里选择一些特征的变得更加明确。
namespace A { int i; void func1(){} void func2(){} } namespace myA { using A::i; using A::func1; void func3(int a){} } using namespace myA; int main() { func1(); func3(i); return 0; }
名字空间和重载
如果从多个命名空间里引入同名而不同参的函数,且它们满足构成重载的条件,则构成重载。
#include <iostream> using namespace std; namespace A { void f(char c) { cout<<"char is "<<c<<endl; } } namespace B { void f(int i) { cout<<"int is "<<i<<endl; } } int main() { using A::f; using B::f; f('a'); //A::f(char) f(10); //B::f(int) return 0; }
一般而言,名字空间内部的函数匹配按以下方式进行:
(1)找到候选函数集合。
(2)从候选集合中选择可行函数。
(3)从可行集合中选择一个最佳匹配。
名字查找
一个取T类型参数的函数常常与T类型本身定义在同一个名字空间里。因此,如果在使用一个函数的环境中无法找到它,我们就去查看它的参数所在的名字空间。
#include <iostream> using namespace std; namespace A { class Date{}; void func1(const Date&); } namespace B { class Time{}; void f(A::Date d,int i) { func1(d); // OK func1(i); // error C3861: “func1”: 找不到标识符 } void func2(Time t){} } int main() { B::Time t1; func2(t1); return 0; }
上例中,在名字空间B中调用的func1(d),函数func1不是在B中定义的,参数类型为Date,因此在Date所在的域A中进行查找,找到了void func1(const Date&)。但是调用func1(i)时,在作用域里没有找到void format(int)型函数。
与显式地使用限定比,这个查找规则能使程序员节省许多输入,而且又不会以“使用指令”那样的方式污染名字空间。这个规则对于运算符的运算对象和模板参数特别有用,因为在那里使用显式限定是非常麻烦的。
名字空间是开放的
另外,命名空间是开放的,即可以随时把新的成员名称加入到已有的命名空间之中去。方法是,多次声明和定义同一命名空间,每次添加自己的新成员和名称。
namespace A { int i; void func1(); } // 现在A有成员i和func1() namespace A { int j; void func2(); } // 现在A有成员i、func1()、j和func2()