Skip to content
On this page

命名空间

一个中大型软件往往由多名程序员共同开发,会使用大量的变量和函数,不可避免地会出现变量或函数的命名冲突。当所有人的代码都测试通过,没有问题时,将它们结合到一起就有可能会出现命名冲突。

例如小李和小韩都参与了一个文件管理系统的开发,它们都定义了一个全局变量fp,用来指明当前打开的文件,将他们的代码整合在一起编译时,很明显编译器会提示fp重复定义(Redefinition)错误。

使用简例

为了解决合作开发时的命名冲突问题,C++引入了命名空间(Namespace)的概念。请看下面的例子:

cpp
namespace Li { 
  //小李的变量定义
  FILE* fp = NULL;
}
namespace Han { 
  //小韩的变量定义
  FILE* fp = NULL;
}

TIP

命名空间有时也被称为名字空间、名称空间

小李与小韩各自定义了以自己姓氏为名的命名空间,此时再将他们的fp变量放在一起编译就不会有任何问题。

命名空间

namespace 是 C++ 中的关键字,用来定义一个命名空间,语法格式为:

cpp
namespace name {
    //variables, functions, classes
}

name是命名空间的名字,它里面可以包含变量、函数、类、typedef#define 等,最后由大括号对{ }包围。

变量和命名空间

当你在命名空间里面放了变量的时候就要注意了,有以下几种情况

变量没有初始化

在下面这种情况下,你对其直接使用的话,它就是一个全局变量,全局变量会自己做初始化,比如下面这段代码,int类型的值会自动初始化变成 0

cpp
#include<iostream>

namespace water {
	int count;
	double a;
}

int main() {
	std::cout << water::a << std::endl;
	std::cout << water::count << std::endl;
}

运行结果如下

txt
0
0

TIP

注意此处的代码,我们并没有使用语句using namespace std;,所以使用coutendl的时候就要专门声明std::,相应的,water命名空间也是一样

命名空间变量已初始化

变量初始化之后就可以开始使用变量了,所以在编译的时存在变量重名,能正常通过。

但是编译过了不代表你能精确使用重名的变量,当变量存在重名的时候,就需要使用一些指定使用变量的方法,下面我们先看一个例子

cpp
#include<iostream>

namespace water {
	int count = 1;
	double a;
}

int main() {
  // 此处的count是局部变量
  int count = 9;
  std::cout << water::count << std::endl << water::a << std::endl;
}

请看仔细些,我们要把上面这段代码☝和下面的代码👇做比较

cpp
#include<iostream>

namespace water {
	int count = 1;
	double a;
}

int main() {
  // 这段代码会直接报错,因为 count 既不是局部变量也是不是 water 命名空间内部的变量
  // 那就是全局变量,但是没有对 count 进行声明,于是报错
  count = 9;
  std::cout << water::count << std::endl << water::a << std::endl;
}

请仔细查看上面代码以及注释,为了挽救第二段代码的情况,我们就要使用一些语句,让重名变量的使用都变得稍微明确些,请看下一小节

可行的修改示范

域解析操作符

使用变量、函数时要指明它们所在的命名空间。

以上面的fp变量为例,可以这样来使用:

cpp
//使用小李定义的变量 fp
Li::fp = fopen("one.txt", "r"); 

//使用小韩定义的变量 fp
Han::fp = fopen("two.txt", "rb+");

::是一个新符号,称为域解析操作符,在 C++ 中用来指明要使用的命名空间。

using关键字

除了直接使用域解析操作符,还可以采用using关键字声明,例如:

cpp
// 使用小李定义的变量 fp
using Li::fp;

fp = fopen("one.txt", "r"); 

//使用小韩定义的变量 fp  
Han :: fp = fopen("two.txt", "rb+");

在代码的开头用using声明了Li::fp,它的意思是,using 声明以后的程序中如果出现了未指明命名空间的fp,就使用 Li::fp

但是如果我们要使用小韩定义的 fp,就需要 Han::fp

using 声明不仅可以针对命名空间中的一个变量,也可以用于声明整个命名空间,例如

cpp
using namespace Li;

//使用小李定义的变量 fp
fp = fopen("one.txt", "r");

//使用小韩定义的变量 fp
Han::fp = fopen("two.txt", "rb+");

如果命名空间Li中还定义了其他的变量,那么同样具有fp变量的效果。

using声明后,如果有未具体指定命名空间的变量产生了命名冲突,那么默认采用命名空间Li中的变量。

命名空间内部不仅可以声明或定义变量,对于其它能在命名空间以外声明或定义的名称,同样也都能在命名空间内部进行声明或定义,例如类、函数、typedef#define等都可以出现在命名空间中。

站在编译和链接的角度,代码中出现的变量名、函数名、类名等都是一种符号(Symbol)。有的符号可以指代一个内存位置,例如变量名、函数名;有的符号仅仅是一个新的名称,例如typedef定义的类型别名。

下面来看一个命名空间完整示例代码

cpp
#include <stdio.h>
//将类定义在命名空间中

namespace Diy {
	class Student {
	public:
		const char* name;
		int age;
		float score;
	public:
		void say() {
			printf("%s的年龄是 %d,成绩是 %f\n", name, age, score);
		}
	};
}

int main() {
	Diy::Student stu1;

	stu1.name = "小明";
	stu1.age = 15;
	stu1.score = 92.5f;

	stu1.say();
}

运行结果是

txt
小明的年龄是 15,成绩是 92.500000

不被建议的修改示范

请观察下面这段代码,我们可以发现我们直接在main函数内部声明使用了waternamespace,这种大范围声明命名空间的行为不是一个好行为,请避免这么做

cpp
#include<iostream>

namespace water {
	int count = 1;
	double a;
}

int main(){
  using namespace water;
  // 这里是 water 命名空间里面的count
  // 不建议这样做,因为频繁的切换语句运行时所在的命名空间很容易造成混淆
  count = 9;
  int count = 7;//这里因为是局部变量,所以也没有问题

  std::cout << water::count << std::endl << water::a << std::endl;
}

我们查看运行结果

txt
9
0

DANGER

这绝不是一个好的编程习惯,频繁切换语句运行的默认命名空间是一件很危险的事情,搞不好就会出现一些神奇并且难以排查的bug