c++基础篇(三)——命名空间

前言:
   与c相比,命名空间是c++的一大特色,这样的设计注定了c++是面向超大型编程项目的。

1 介绍

  为什么需要命名空间?在c中并不支持命名命名空间,c在大型项目中会遇到烦人的命名重复的问题。大型工程中,不同的模块会分配给不同的人或团队开发,各自模块测试通过,最终合到一起时可能会发现命名重复,给模块设计带来额外的负担。使用第三方代码时,也存在与其命名重复的可能,c++引入命名空间可以很好的解决大型项目中命名重复的问题。

2 定义命名空间

   c++中通过关键词 namespace 定义一个命名空间,在这个命名空间内定义的变量或函数即属于这个命名空间,必须通过该命名空间才能访问到。以下代码定义了命名空间 MyNameSpace ,并且通过命名空间名访问了命名空间内的函数和变量,访问通过 :: 符号。

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

namespace MyNameSpace
{
int Val1 = 1;
int Val2 = 2;

int Add(int Num1, int Num2)
{
return Num1 + Num2;
}
}

int main(void)
{
int Rtn = 0U;

Rtn = MyNameSpace::Add(MyNameSpace::Val1, MyNameSpace::Val2);
std::cout << Rtn << std::endl;

return 0;
}

  一个源文件内可同时定义多个命名空间,不同命名空间内的命名可以重复,通过命名空间名来区分。

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
28
29
30
31
32
33
34
35
36
37
#include <iostream>

namespace MyNameSpaceFirst
{
int Val1 = 1;
int Val2 = 2;

int Add(int Num1, int Num2)
{
return Num1 + Num2;
}
}

namespace MyNameSpaceSecond
{
int Val1 = 11;
int Val2 = 22;
int Val3 = 33;

int Add(int Num1, int Num2, int Num3)
{
return Num1 + Num2 + Num3;
}
}

int main(void)
{
int Rtn = 0U;

Rtn = MyNameSpaceFirst::Add(MyNameSpaceFirst::Val1, MyNameSpaceFirst::Val2);
std::cout << Rtn << std::endl;

Rtn = MyNameSpaceSecond::Add(MyNameSpaceSecond::Val1, MyNameSpaceSecond::Val2, MyNameSpaceSecond::Val3);
std::cout << Rtn << std::endl;

return 0;
}

  命名空间可以多次定义,即继续往里添加内容。

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
28
29
30
31
32
33
#include <iostream>

namespace MyNameSpace
{
int Val1 = 1;

int Add(int Num1, int Num2)
{
return Num1 + Num2;
}
}

namespace MyNameSpace
{
int Val2 = 2;
}

namespace MyNameSpace
{
int Sub(int Num1, int Num2)
{
return Num1 - Num2;
}
}

int main(void)
{
std::cout << MyNameSpace::Val1 << std::endl;
std::cout << MyNameSpace::Val2 << std::endl;
std::cout << MyNameSpace::Sub(5, 1) << std::endl;

return 0;
}

3 使用命名空间

  命名空间定义后,通过关键词 using 指令导入一个名称空间进行使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>

namespace MyNameSpace
{
int Val1 = 1;
int Val2 = 2;
}

using namespace MyNameSpace;

int main(void)
{
std::cout << Val1 << std::endl;
std::cout << Val2 << std::endl;

return 0;
}

   using namespace MyNameSpace 使用 using 导入 MyNameSpace 命名空间中的所有名称,就不需要每次在调用命名空间中成员时都加 <MyNameSpace>:: 了,注意其有作用域。
  命名空间也有作用域的限制,仅在其域内可见,以下是示例代码。

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

namespace MyNameSpace
{
int Val1 = 1;
int Val2 = 2;
}

int main(void)
{
{
using namespace MyNameSpace;

std::cout << Val1 << std::endl;
std::cout << Val2 << std::endl;
}

// std::cout << Val1 << std::endl; /* Error. */
// std::cout << Val2 << std::endl;

return 0;
}

  若命名空间里的名称与局部变量同名,则会使用局部变量。

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
#include <iostream>

namespace MyNameSpace
{
int Val1 = 1;
int Val2 = 2;
}

int main(void)
{
int Val1 = 1;
int Val2 = 2;

{
using namespace MyNameSpace;

std::cout << Val1 << std::endl;
std::cout << Val2 << std::endl;
}

std::cout << Val1 << std::endl;
std::cout << Val2 << std::endl;

return 0;
}

  输出结果为。

1
2
3
4
1
2
1
2

  当命名空间中的名称与非局部名称同名时,编译器会报错,因为产生了二义性,程序不知道应该调用哪个。

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
28
29
30
31
32
33
34
35
36
37
#include <iostream>

namespace MyNameSpace
{
int Val1 = 1;
int Val2 = 2;

void Fun(void)
{
printf("Call Fun 1\n");
}
}

int Val1 = 3;
int Val2 = 4;

void Fun(void)
{
printf("Call Fun 2\n");
}

int main(void)
{
{
using namespace MyNameSpace;

std::cout << Val1 << std::endl;
std::cout << Val2 << std::endl;
Fun();
}

std::cout << Val1 << std::endl;
std::cout << Val2 << std::endl;
Fun();

return 0;
}

4 不同命名空间内成员同名

  如果两个命名,比如变量名同名,但属于不同命名空间,可以通过命名空间名分别调用他们,语法为 <NameSpace>::<ObjectName>

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

namespace MyNameSpace1
{
int Val1 = 1;
int Val2 = 2;
}

namespace MyNameSpace2
{
int Val1 = 3;
int Val2 = 4;
}

int main(void)
{
std::cout << MyNameSpace1::Val1 << std::endl;
std::cout << MyNameSpace2::Val1 << std::endl;

return 0;
}

  输出结果为。

1
2
1
3

  建议不止是在命名重复时用这种方式调用命名空间内成员,养成这种好的编码习惯可以预防将来发生命名重复。

5 命名空间新增成员

  已经定义的命名空间如何继续往里新增成员,新增和定义的方式相同。

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
28
29
30
31
32
#include <iostream>

namespace MyNameSpace
{
int Val1 = 1;
int Val2 = 2;

void Fun(void)
{
printf("Call Fun 1\n");
}
}

namespace MyNameSpace
{
int Val3 = 3;
}

void Fun(void)
{
printf("Call Fun 1\n");
}

int main(void)
{
std::cout << MyNameSpace::Val1 << std::endl;
std::cout << MyNameSpace::Val2 << std::endl;
std::cout << MyNameSpace::Val3 << std::endl;
Fun();

return 0;
}

  输出结果为。

1
2
3
4
1
2
3
Call Fun 1

6 命名空间嵌套

  命名空间也支持嵌套,以下是代码示例。

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

namespace MyNameSpace1
{
int Val1 = 1;
int Val2 = 2;

namespace MyNameSpace2
{
int Val1 = 3;
int Val3 = 4;
}
}

int main(void)
{
std::cout << MyNameSpace1::Val1 << std::endl;
std::cout << MyNameSpace1::MyNameSpace2::Val1 << std::endl;

return 0;
}

  输出结果为。

1
2
1
3

  命名空间嵌套的不同层级之间成员名也可同名,通过加 <NameSpace>:: 前缀来调用,前缀数量和顺序和嵌套层级对应。
  也可以把头文件包含放到命名空间中,这样头文件中的成员都需要通过该命名空间进行访问,在一些大型项目中为了防止与第三方库的命名同名,会使用此方法,比如Qt6。

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
QT_BEGIN_NAMESPACE
#include <QString>
QT_END_NAMESPACE

int main() {
using namespace Qt;

QString message = "Hello, World!";
std::cout << qPrintable(message) << std::endl;

return 0;
}

  有时嵌套层级太多了,为了省去前缀可以这样写。

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

namespace MyNameSpace1
{
int Val1 = 1;
int Val2 = 2;

namespace MyNameSpace2
{
int Val1 = 2;
int Val3 = 3;
}
}

int main(void)
{
using namespace MyNameSpace1::MyNameSpace2;

std::cout << Val1 << std::endl;

return 0;
}

  输出结果为。

1
2