C++提供命名空间用于组织代码并避免命名冲突,这在大型项目和库开发中尤为重要。 命名空间机制只会影响编译期间的各种符号可见性和链接顺序,并不会产生任何的运行时影响。

基本使用

定义一个命名空间的示例如下,可以在命名空间中加入类/函数/变量等,在命名空间之外必须加上命名空间的前缀才能访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>

// 声明需要包裹在命名空间中
namespace MyNamespace {
extern int s;

int func();
}

// 定义也需要包裹在命名空间中
namespace MyNamespace {
int s = 20;

int func() { return 40; }
}

int func() { return 30; }

int main() {
int s = 10;
std::cout << "s = " << s << "\n"; // 10
std::cout << "s = " << MyNamespace::s << "\n"; // 20

std::cout << "func = " << func() << "\n"; // 30
std::cout << "func = " << MyNamespace::func() << "\n"; // 40
return 0;
}

注意,对命名空间中成员无论是定义还是声明的代码都需要包裹在命名空间中,在头文件中的声明也需要包裹在命名空间中。

命名空间是允许分段定义的,还可以在不同文件中定义,在编译链接时会将所有的同名命名空间组成一个整体,例如

1
2
3
4
5
6
7
8
9
10
11
namespace A {
int s1 = 10;
}

namespace B {
int s1 = 10;
}

namespace A {
int s2 = 10;
}

在外部使用命名空间中的内容时,默认都需要加上命名空间名称前缀,例如

1
2
3
4
5
6
7
8
9
10
11
namespace demo{
int s = 10;

void func(){}
}

int main(){
demo::func();

std::cout << demo::s;
}

每次都加上前缀比较繁琐,可以使用using导入命名空间中的某个符号或者整个命名空间,见下文。

嵌套命名空间

C++提供了命名空间的嵌套,例如

1
2
3
4
5
namespace Outer {
namespace Inner {
void foo() {}
}
}

对其中内容的访问加上两层命名空间的前缀即可,例如

1
Outer::Inner::foo();

在C++17之后,可以将嵌套的命名空间简写为

1
2
3
namespace Outer::Inner {
void foo() {}
}

匿名命名空间

我们可以不给命名空间起名字,这会生成一个匿名的命名空间,匿名空间的作用是将其中的内容限定在当前源文件中访问(与static的作用比较类似)。

访问匿名空间中的内容不需要加上任何前缀

1
2
3
4
5
6
7
8
namespace {
void internalFunction() {}
}

int main() {
internalFunction();
return 0;
}

标准库命名空间 std

标准库中的所有内容都位于std命名空间中,使用标准库中的内容时需要使用std::前缀,例如

1
2
3
4
5
6
#include <iostream>

int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}

我们甚至可以向std命名空间中加入自己写的内容,这在目前是可以编译通过的,但是显然这种做法是非常不推荐的。

命名空间的符号导入

在命名空间之外访问命名空间中的符号,都需要加上命名空间的前缀,例如

1
2
3
4
5
6
7
8
9
10
11
namespace demo{
int s = 10;

void func(){}
}

int main(){
demo::func();

std::cout << demo::s;
}

每次使用时都加上前缀实在过于繁琐,可以使用using导入命名空间中的符号,此后对这个符号的使用不再需要加上前缀

1
2
3
4
5
6
7
8
9
10
11
12
13
namespace demo{
int s = 10;

void func(){}
}

using demo::func; // 导入demo::func

int main(){
func(); // func == demo::func

std::cout << demo::s;
}

还可以一次性导入整个命名空间的所有符号,此后对这个命名空间中所有符号的使用都不需要加上前缀

1
2
3
4
5
6
7
8
9
10
11
12
13
namespace demo{
int s = 10;

void func(){}
}

using namespace demo; // 导入整个demo命名空间

int main(){
func(); // func == demo::func

std::cout << s; // s == demo::s
}

但是这种做法是不推荐的,因为比较危险,在头文件中导入整个命名空间则是更加的危险。

在很多C++基础教程中,采用了直接导入整个std命名空间的做法

1
using namespace std;

这是一个非常糟糕的语法习惯,它一次性导入了太多的符号,可能导致难以发现的错误。 例如标准库中提供了std::bind函数,如果本地也定义了bind函数,就可能触发编译错误,因为将这两个函数搞混了。

推荐的做法是在源文件中明确导入我们需要的几个符号,例如

1
2
3
using std::cout;
using std::cin;
using std::endl;