命名空间
参考:http://www.jb51.net/article/53760.htm
通常来说,在C++中,命名空间(namespace)的目的是为了防止名字冲突。每个命名空间是一个作用域,在所有命名空间之外,还存在一个全局命名空间(global namespace),全局命名空间以隐式的方式声明,它并没有名字,在命名空间机制中,全局变量就位于全局命名空间中(可以用::member的形式表示)。
一、定义命名空间
1、每个命名空间都是一个作用域
和其他作用域类似,在命名空间中的每个名字必须表示唯一实体,而在不同命名空间中,可以有相同名字的成员。
2、命名空间可以是不连续的
命名空间可以定义在几个不同的部分:
namespace nsp { /* ... */ }//命名空间作用域后面无须分号
如果之前没有名为nsp的命名空间定义,则上述代码创建一个新的命名空间;否则,上述代码打开已经存在的命名空间添加一些新的成员。
另外,命名空间的不连续性不局限于一个文件,我们也可以另一个文件中对它进行重复创建,这样,我们就可以随时随地扩充这个命名空间的内容。
3、命名空间是可以嵌套的
嵌套的命名空间是指定义在其他命名空间中的命名空间。嵌套的命名空间是一个嵌套的作用域,内层命名空间声明的名字将隐藏外层命名空间声明的同名成员:
int x = 20; namespace outer { int x = 10; namespace inner { int z = x; } } int main() { std::cout<<outer::inner::z; // 输出10 return 0; }
注意,通常我们不把#include头文件放在命名空间内部。
二、使用命名空间
对命名空间中成员的引用,需要使用命名空间的作用域运算符(::)。但是,像namespace_name::member_name这样使用命名空间的成员非常烦琐,我们需要使用一些其他的更简便的方法。
1、命名空间的别名
有些命名空间的名字很长或者命名空间嵌套了很多层,我们可以为其设定一个较短的同义词,也就是别名:
namespace cln = cpluslus_learning_namespace; namespace Qlib = outer::inner::QueryLib;
2、using声明
一条using声明(using declaration)语句一次只引入命名空间的一个成员:
using 命名空间名::[命名空间名::……]成员名; // 例如 using OLib::List;
3、using指示
using指示(using directive)和using声明不同的地方是,我们无法控制哪些名字是可见的,因为using指示会使得某个特定的命名空间中所有的名字都可见:
using namespace std; // 引入命名空间std
使用命名空间主要是为了防止名字冲突,如果随意使用using指示注入命名空间的所有名字,将重新引入名字冲突的问题。
4、using声明和using指示的区别
关键字using的作用域从其声明之处开始一直持续到当前作用域结束,如:
#include <iostream> using namespace std; namespace num { int x=10; int y=20; } int main() { { using namespace num; cout<<"x:"<<x<<" y:"<<y<<endl; } //cout<<"x:"<<x<<" y:"<<y<<endl; //语句超出using namespace num的作用域 system("pause"); return 0; }
using声明好像是声明了一个局部变量,而using指示则没有这个特性。
//using声明好像是声明了一个局部变量 #include <iostream> using namespace std; namespace num { int x=10; } int x = 30; int main() { using num::x; //using声明好像是声明了一个局部变量,因此此时全部变量x不可见 cout<<"x = "<<x<<endl; //10 //int x = 20; //此时如果再定义一个局部变量x,则导致重定义 system("pause"); return 0; }
//using指示 #include <iostream> using namespace std; namespace num { int x=10; } int x = 30; int main() { using namespace num; //using指示 //cout<<"x = "<<x<<endl; //此时不知道取哪个x system("pause"); return 0; }
三、未命名的命名空间
未命名的命名空间就是没有取名字的命名空间,不给它取名字是为了防止在编译时因为不同文件的变量或对象名相同而产生冲突,引起不必要的错误。当创建了未命名的命名空间,编译器便为这个命名空间自动分配一个与众不同的名字,并且每个文件中的名字都不相同,因此未命名空间中的成员只会在该文件中可见,而在其他文件中是不可见的,这样就避免了发生重名引起的错误。另外,由于编译器记录了它为未命名空间分配的名字,并且在创建它的文件中自动添加该名字,因此未命名空间中的所有成员都可以在创建它的文件中直接进行访问,如:
#include <iostream> using namespace std; namespace { int x=2; } namespace { int y=3; } int main() { cout<<"x:"<<x<<" y:"<<y<<endl; system("pause"); return 0; }
其实我们可以把未命名的命名空间中的变量看做是全局变量,当然,这两者之间是有区别的。
1、未命名命名空间与全局变量的区别
未命名命名空间与全局变量有所不同,如:
//1.cpp #include <iostream> int x=5;
//2.cpp #include <iostream> using namespace std;
int x=6;
int main() { cout<<x<<endl; return 0; }
我们在1.cpp和2.cpp中都定义了全局变量x,这将导致重定义,解决的方式是将1.cpp中的x放到匿名命名空间中,即:
namespce { int x=5; }
由于未命名命名空间的所有成员在其他文件中是不可见的,因此间接地解决了不同文件中变量、函数或者对象的重名问题。
2、未命名命名空间与static的区别
未命名命名空间的作用与static相同,比如说我们可以将上节的:
namespce { int x=5; }
替换为:
static int x = 5;
同样可以解决重名问题,这是因为static相对于全局变量来说,是文件级的静态对象,也就是说static只在使用它的文件中起作用,而其他文件必须显示地包含它才可以使用(一般都是使用#include包含static对象所在的文件),由于我们没有在2.cpp文件中显示地包含static对象所在的1.cpp,因此1.cpp文件中的静态变量x在2.cpp文件中是不可见的。
3、未命名命名空间与static、extern的区别
extern用来声明一个在其他文件中定义的变量、函数或者对象,如:
//2.cpp中的内容 #include <iostream> using namespace std; extern int x; int main() { cout<<x<<endl; return 0; } //1.cpp中的内容 int x=10;
其中,2.cpp文件中的extern int x;是告诉编译器,整型变量x已将在另外的文件中(1.cpp)定义过了。
extern只是起说明的作用,它通常用来表示所参照的变量来自另一个文件,编译器根据它提供的类型和名字从程序的其他编译文件中进行查找。
static与extern的一个区别是:static是内部链接,extern是外部链接。比如说static定义的变量只能在定义此变量的文件中使用,而在其他文件中该变量是不可见的;而extern则将它所声明的变量链接到其他文件,以便于编译器查找到此变量的定义部分。
另外值得注意的是,未命名命名空间也是外部链接。未命名命名空间并不是没有名字,而是匿名,每个文件中的未命名命名空间其实都拥有一个系统为它分配的独一无二的名字,有了这个名字作为限制,自然就不会与其他文件中匿名空间中的相同成员名产生冲突了。
为什么namespace不采取static的做法,非要大费周折地采取匿名方式来做外部链接呢?这是因为假如我们将全局变量和函数放心地扔到匿名空间中,并将它的地址作为模板参数,那么就要求这个匿名空间必须是外部链接,否则模板就没有多大的意义,而static是内部链接,只在当前文件中可见,而在其他文件中不可见,因此无法实例化模板。由于namespace和static的这个区别,C++标准委员会不建议我们使用static来避免不同文件中的变量、函数或者对象的重名问题,而是推荐使用namespace。