命名空间与链接性

C++中,名称可以是变量、函数、结构、枚举、类、以及类和结构的成员。使用名称空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染

传统C++名称空间

声明区域

声明区域是可以在其中进行声明的区域。例如,全局变量的声明区域为其声明所在的文件;自动变量的声明区域为其声明所在的代码块

潜在作用域和作用域

变量的潜在作用域从声明点开始,到其声明区域的结尾
潜在作用域比声明区域小,因为变量必须定义后才能使用
变量并非在其潜在作用域都是可见的,它可能被另一个在嵌套声明区域中声明的同名变量隐藏。变量对程序可见的范围被称为作用域

1
2
3
4
5
6
7
8
#include <iostream>
int a = 1;
int main() {
int a = 3;
std::cout << ::a << std::endl; // 1
std::cout << a << std::endl; // 3
return 0;
}

例如全局变量a的潜在作用域是第2行~第8行;作用域是第2行~第3行
定义同名局部变量后构成同名变量隐藏,但通过::a可以访问

新的名称空间特性

通过定义一种新的声明区域来创建命名的名称空间
可以是全局的,也可以嵌套定义,但不能位于代码块中。默认情况下链接性为外部
同一个工程中允许存在多个相同名称的命名空间编译器最后会合成同一个命名空间中

使用方法

  • 加命名空间名称及作用域限定符
  • 使用using将命名空间中成员引入(using声明)
  • 使用using namespace命名空间名称引入(using编译指令)

using声明如果发生名称冲突会报错,而using编译指令会构成同名隐藏,局部版本将隐藏名称空间版本
using编译指令可传递可以给名称空间创建别名,列如,namespace STD = std可以通过这种技术简化对嵌套名称的使用
优先使用::或using声明

匿名名称空间

潜在作用域和全局变量相似,其特性提供了链接性为内部的静态变量的替代品
例如全局静态变量static int counts;可以这样代替namespace { int counts; }

使用在已命名的名称空间中声明的变量代替外部全局变量、静态全局变量

1
2
3
4
5
namespace {
char c;
int i;
double d;
}

编译器在内部会为这个命名空间生成一个唯一的名字,而且还会为这个匿名的命名空间生成一条using指令。所以上面的代码在效果上等同于:

1
2
3
4
5
6
namespace __UNIQUE_NAME_ {
char c;
int i;
double d;
}
using namespace __UNIQUE_NAME_;

例子

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// spaceA.h
#pragma once

namespace SpaceA {
class TestA {
public:
TestA();
~TestA();
void print();
};
}

// spaceA.cpp
#include <iostream>
#include "spaceA.h"

SpaceA::TestA::TestA() {}

SpaceA::TestA::~TestA() {}

void SpaceA::TestA::print() {
std::cout << "SpaceA::TestA::print()" << std::endl;
}

// spaceB.h
#pragma once

// 使用前声明一下
namespace SpaceA {
class TestA;
}

namespace SpaceB {
extern int i;
const int ci = 2;
extern const int eci;
class TestB {
public:
TestB();
~TestB();
void printB();
private:
SpaceA::TestA* a; // 类的定义为内部链接,不能直接定义,除非写出类的完整声明,此处采用指针的方式
};
}

// spaceB.cpp
#include <iostream>
#include "spaceB.h"
#include "spaceA.h"

int SpaceB::a = 123;

SpaceB::TestB::TestB() {
a = new SpaceA::TestA;
}

SpaceB::TestB::~TestB() {
delete a;
a = nullptr;
}

void SpaceB::TestB::printB() {
std::cout << "SpaceB::TestB::printB()" << std::endl;
}

// main.cpp
#include <iostream>
#include "spaceB.h"

int main() {
SpaceB::TestB b;
b.printB();
std::cout << SpaceB::i << std::endl;
std::cout << SpaceB::ci << std::endl;
std::cout << SpaceB::eci << std::endl;
return 0;
}

链接性

描述了名称在不同单元间共享

外部链接性:可以在文件间共享

  • 非inline函数、命名空间中非静态函数、类成员函数和类静态成员函数
  • 类静态成员变量总有外部链接
  • 命名空间(不包括无名命名空间)中非静态变量

内部链接性:只能由一个文件中的函数共享,编译后不会产生修饰名字,因此不与链接器打交道

  • 所有的声明
  • 命名空间(包括全局命名空间)中的静态自由函数、静态友元函数、静态变量的定义、const常量定义(除非在const限定声明之时或之前已经声明为extern)
  • enum定义
  • inline函数定义(包括自由函数和非自由函数)
  • 类(class、struct、union)的定义

无链接性:不能共享,具有局部作用域,如局部变量、函数形参等

跨语言链接:有时候在C++工程中可能需要将某些函数按照C的风格来编译,在函数前加extern “C”,意思是告诉编译器,将该函数按照C语言规则来编译

1
2
3
4
5
extern "C" int Add(int left, int right);
int main() {
Add(1,2);
return 0;
}