C++ 名字空间详解

代码编译运行环境:VS2017+Win32+Debug


1.名字空间的由来

名字空间(Namespace)是由 C++ 引入的一种新的作用域级别,可以由用户命名,用来处理程序中常见的同名冲突。

原来 C++ 标识符的作用域从小到大分为四级:局部作用域(代码块)、函数作用域、类域和全局作用域。如今,在类作用域和全局作用域之间,C++ 标准又添加了名字空间域这一个作用域级别。

2.名字空间的作用

名字空间的作用主要是为了解决日益严重的名称冲突问题。随着可重用代码的增多,各种不同的代码体系中的标识符之间同名的情况就会显著增多,解决的办法是将不同的代码库放到不同的名字空间。

访问一个具体标识符的时候,可以使用如下形式:

space_name::identifier

即用作用域指示符“::”将名字空间的名称和该空间下的标识符连接起来,这要,即使使用同名的标识符,由于它们处于不同的名字空间,也不会发生冲突。

有两种形式的命名空间——有名的和无名的。
定义格式为:

有名的命名空间:
       namespace 命名空间名
       {
              声明序列可选
       }
匿名的命名空间:
       namespace
       {
              声明序列可选
       }

3.名字空间的注意要点

(1)一个名字空间可以在多个头文件或源文件中实现,称为分段定义。如果想在当前文件访问定义在另一个文件中的同名名字空间内的成员变量,需要在当前文件的名字空间内部进行申明。如标准C++库中的所有组件都是在一个被称为std的名字空间中声明和定义的,这些组件当然分散在不同的头文件和源文件中。

(2)名字空间内部可以定义类型、函数、变量等内容,但名字空间不能定义在类和函数的内部。

(3)在一个名字空间中可以自由地访问另一个名字空间的内容,因为名字空间并没有保护级别的限制。

(4)虽然经常可以见到using namespace std;这样的用法,我们也可以用同样的方法将名字空间中的内容一次性“引入”到当前的名字空间中来,但这并不是一个值得推荐的用法。因为这样做的相当于取消了名字空间的定义,使发生名称冲突的机会增多。所以,用 using 单独引入需要的内容,这样会更有针对性。例如,要使用标准输入对象,只需用 using std::cin; 就可以了。

(5)不能在名字空间的定义中声明另一个嵌套的子命名空间,只能在命名空间中定义子命名空间。

(6)名字空间的成员,可以在命名空间的内部定义,也可以在名字空间的外部定义,但是要在名字空间进行声明。
命名空间成员的外部定义的格式为:

名字空间名::成员名 ……

(7)名字空间在进行分段定义时,不能定义同名的变量,否则连接出现重定义错误。因为名字空间不同于类,具有外部连接的特性。由于外部连接特性,请不要将名字空间定义在头文件,因为当被不同的源文件包含时,会出现重定义的错误。

结合以上几点,观察如下程序。

//main.cpp
#include <iostream>

namespace myspace1
{
	extern int gvar;//内部声明
	extern int otherVar; //另一个文件中同名名字空间中定义
	using std::cout;
	using std::endl;
	
	class myclass
	{
	public:
		void print()
		{
			cout<<"in space1,gvar="<<gvar<<endl;
		}
	};
}


namespace myspace2
{
	using std::cout;
	using std::endl;
	int i=5;
	
	class myclass
	{
	public:
		void print(){
			cout<<"in space2"<<endl;
		}
	};
	namespace nestedspace
	{
		void ExternFunc();//内部声明
	}
}

//外部定义
int myspace1::gvar=1;
void myspace2::nestedspace::ExternFunc()
{
	cout<<"in nestedspace"<<endl;
}

int main(int argc,char* argv[])
{
	myspace1::myclass obj1;
	obj1.print();
	myspace2::myclass obj2;
	obj2.print();
	myspace2::nestedspace::ExternFunc();
	std::cout<<myspace1::otherVar<<std::endl;
}

//sp2.cpp
namespace myspace1
{
	int otherVar=3;
}

程序输出结果是:

in space1,gvar=1
in space2
in nestedspace
3

(8)为了避免命名空间的名字与其他的命名空间同名,可以用较长的标识符作为命名空间的名字。但是书写较长的命名空间名时,有些冗余,因此,我们可以在特定的上下文环境中给命名空间起一个相对简单的别名。
参考如下程序。

namespace MyNewlyCreatedSpace
{
	void show()
	{
		std::cout<<"a function within a namespace"<<std::endl;
	}
}

int main(int argc,char* argv[])
{
	namespace sp=MyNewlyCreatedSpace;
	sp::show();
}

4.匿名名字空间

4.1与 static 的共同作用

匿名名字空间提供了类似在全局函数前加 static 修饰带来的限制作用域的功能。它的这种特性可以被用在struct和class上, 而普通的static却不能。比如,在两个源文件中定义了相同的全局变量(或函数),就会发生重定义的错误。如果将它们声明为全局静态变量(或函数)就可以避免重定义错误。在C++中,除了可以使用static关键字避免全局变量(函数)的重定义错误,还可以通过匿名名字空间的方式实现。参考如下代码。

//main.cpp
#include <iostream>
using namespace std;

namespace
{
	double dvar=1.8;
}
void show1()
{
	cout<<"dvar:"<<dvar<<endl;
}

int main(int argc,char* argv[])
{
	void show2();
	show1();
	show2();
}

//a.cpp
#include <iostream>
using namespace std;

double dvar=2.8;
void show2()
{
	cout<<"dvar:"<<dvar<<endl;
}

程序输出:

dvar:1.8
dvar:2.8

未命名的名字空间中定义的变量(或函数)只在包含该名字空间的文件中可见,但其中的变量的生存期却从程序开始到程序结束。如果有多个文件包含未命名的名字空间,这些名字空间是不相关的,即使这些名字空间中定义了同名的变量(或函数),这些标识符也代表不同的对象。

4.2与 static 的不同

通过匿名名字空间,同样实现了对不同源文件中同名全局变量(函数)的保护,使它们不至于发生冲一定冲突。在这一点上,匿名名字空间和 static 的作用是相同的。

但是,用static修饰的变量(函数)具有内部连接特性,而具有内部连接特性的变量(函数)是不能用来实例化一个模板的。参考如下程序。

#include <iostream>
using namespace std;

template <char*p> class Example
{
public:
	void display()
	{
		cout<<*p<<endl;
	}
};

static char c='a';
int main(int argc,char* argv[])
{
	Example<&c> a; //编译出错
	a.display();
}

此程序无法通过编译,因为静态变量c不具有外部连接特性,因此不是真正的“全局”变量。而类模板的非类型参数要求是编译时常量表达式,或者是指针类型的参数且要求指针指向的对象具有外部连接性。具体要求,参见 C++ 标准关于模板非类型参数的要求:ISO相关标准官网

为了实现既能保护全局变量(函数)不受重定义错误的干扰,能够使它们具有外部连接特性的目的,必须使用匿名名字空间机制。同样是上面的这个程序,将char c=’a’;置于匿名名字空间进行定义,即可通过编译并运行。读者可自行考证。

通过以上程序,可以看出匿名名字空间与 static 的区别:包含在匿名名字空间中的全局变量(函数)具有外部连接特性,而用static修饰的全局变量具有内部连接特性,不能用来实例化模板的非类型参数。


参考文献

[1] C++高级进阶教程[M].陈刚.武汉大学出版社.C1.15关于名字空间.P40-46
[2] c++匿名名字空间问题

展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客
应支付0元
点击重新获取
扫码支付

支付成功即可阅读