本文保存在gitee仓库,接受PR。
命名空间的作用是解决程序中常见的同名冲突。例如,我可能在编写一个库,里面具有Boss
类,同时另外一个库里面也有一个Boss
类,这时如果我写的程序需要这两个库,那么编译器就无法区分程序里使用的Boss
类到底是哪一个库的。
C++的命名空间解决问题的办法是:
下面这个示例演示了如何利用命名空间来解决名字冲突的问题。假设当前项目文件夹有5个文件,my.h
,my.cpp
,another.h
,another.cpp
,main.cpp
。其中my
和another
开头的文件里分别存在Boss
类的声明和成员函数的实现,main.cpp
使用它们提供的Boss
类。文件内容分别如下:
my.h
#ifndef MY_H
#define MY_H
namespace my {
class Boss {
public:
void say();
};
}
#endif
my.cpp
#include "my.h"
#include
namespace my {
void Boss::say() {
std::cout << "我是你大爷!" << std::endl;
}
}
another.h
#ifndef ANOTHER_H
#define ANOTHER_H
namespace another {
class Boss {
public:
void say();
};
}
#endif
another.cpp
#include "another.h"
#include
namespace another {
void Boss::say() {
std::cout << "我不是你大爷!" << std::endl;
}
}
main.cpp
#include "my.h"
#include "another.h"
int main() {
my::Boss myBoss;
another::Boss anotherBoss;
myBoss.say();
anotherBoss.say();
return 0;
}
可以看到,main.cpp
中通过my::Boss
和another::Boss
使用了命名空间my
和another
下的Boss
类,这样就算类名称相同也不会有问题。编译以上的代码并执行可以得到预期的结果:
$ g++ my.cpp another.cpp main.cpp
$ ./a.out
我是你大爷!
我不是你大爷!
命名空间的名字位于关键字namespace
之后,它必须是合法标识符,也就是由大写字母、小写字母、下划线和数字0~9,且以非数字字符开头的字符组成的字符序列。例如:
namespace abc123_123_ABC {} // 正确
namespace _______0_abc {} // 正确
namespace MyNamespace {} // 正确
namespace 0_abc {} // 错误
namespace +abc {} // 错误
namespace ?lKk {} // 错误
命名空间名字是区分大小写的,例如My
和my
、Another
和another
都是不同的命名空间。
命名空间内声明的东西叫做命名空间成员。比如上面的示例中的类Boss
。除了类之外还可以声明别的,比如变量、常量、函数,甚至嵌套命名空间:
#include
namespace X {
int a; // 变量
int b = 9; // 带初始化的变量
void c() { // 函数
std::cout << "我是函数c" << std::endl;
}
const int d = 6; // 常量
namespace Y { // 嵌套命名空间
int b = 10;
}
}
int main() {
X::a = 1;
X::c();
std::cout << X::a << std::endl;
std::cout << X::b << std::endl;
std::cout << X::d << std::endl;
std::cout << X::Y::b << std::endl;
return 0;
}
编译并运行:
$ g++ main.cpp
$ ./a.out
我是函数c
1
9
6
10
除了在使用命名空间时通过形如X::a
、X::Y::b
这种用::
连起来的形式直接访问命名空间成员外,还可以通过关键字using namespace
加命名空间名称的方式,引入命名空间下的所有命名空间成员到当前的作用域,或者直接using
加命名空间加::
号加命名空间成员,直接在当前作用域引入该成员(只引入命名空间成员能防止因引入所有命名空间成员导致的标识符污染问题),这样使用时可以省略::
符号。比如:
* using
整个命名空间:
#include
using namespace std;
int main() {
cout << "你好" << endl;
return 0;
}
* using
只引入命名空间成员:
#include
using std::cout;
using std::endl;
int main() {
cout << "你好" << endl;
return 0;
}
* using
可以在特定的作用域里使用,比如只在函数作用域内使用:
#include
void say() {
using namespace std;
cout << "你好" << endl;
}
int main() {
say();
return 0;
}
可以声明没有命名空间名字的命名空间,这种叫做匿名命名空间。匿名命名空间的命名空间成员作用范围是在当前编译单元(编译器处理的对象称为编译单元,通常是一个源代码文件。我们可以将程序的所有的源代码放在一个文件中,或者都定义在头文件里然后通过include
在预处理阶段包含到同一个文件内,让编译器处理,这样这个程序只有一个编译单元。或者简单地说,一次g++
命令处理的所有文件组合成的就是一个编译单元)。比如:
#include
namespace {
void say() {
using namespace std;
cout << "你好" << endl;
}
}
int main() {
say(); // 这里会执行上面的 say 函数
return 0;
}
匿名命名空间可以用于声明、定义仅在一个编译单元里有效的成员,类似C语言的static
关键字起到的作用。
例如:
#include
namespace X {
void say_1() {
using namespace std;
cout << "你好 1" << endl;
}
}
namespace X {
void say_2() {
using namespace std;
cout << "你好 2" << endl;
}
}
int main() {
X::say_1();
X::say_2();
return 0;
}
编译运行:
$ g++ main.cpp
$ ./a.out
你好 1
你好 2
如果有需要给命名空间指定一个别名,C++也支持。比如下面演示了如何给命名空间X::Y::Z
指定别名A
,然后使用A
就像使用X::Y::Z
一样。
#include
// 在任何地方定义的命名空间,X::Y::Z
namespace X {
namespace Y {
namespace Z {
void say() {
using namespace std;
cout << "你好" << endl;
}
}
}
}
// 下面给命名空间 X::Y::Z 设置别名,A,然后使用 A
namespace A = X::Y::Z;
int main() {
A::say();
return 0;
}
编译并运行:
$ g++ main.cpp
$ ./a.out
你好
关键字using
除了可以用来使用命名空间成员外,也可以给类型指定别名。例如:
#include
using A = int;
int main() {
A x = 567;
std::cout << x << std::endl;
return 0;
}
运行输出
$ g++ main.cpp
$ ./a.out
567