0%

C++ 的名称空间

之前在学 C++ 的时候,第一个例子大概是:

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
using namespace std;
int main(){
cout << "hello world" << endl;
return 0;
}
// 或者
#include <iostream>
int main(){
std::cout << "hello world" << std::endl;
return 0;
}

当时也没多想,std 这个东西是什么。后来在接触了其它库后,发现也需要 std 的配合使用,如 std::sort()。今日来仔细研究下名称空间这个东西。

先掏出 cplusplus.com 来看看 <iostream> 是什么东西,官方的定义如下:

Header that defines the standard input/output stream objects. After C++11, including <iostream> automatically includes also <ios>, <streambuf>, <istream>, <ostream> and <iosfwd>.

而对于 std 而言,std 是一个名称空间,:: 是作用域解析运算符,std::cout 的意思就是使用 std 名称空间中的 cout 标识符,也就是这个对象。而这个对象的定义在 <iostream> 这个标准库文件中,所以要包含这个头文件,才能使用 cout 这个对象。

除此之外,C++ 标准库中的函数或对象都是在名称空间 std 中定义的,所以我们要使用标准函数库中的函数或对象都要使用 std 来限定。所以使用 cout 的时候要加上 std:: 时,编译器就会明白我们调用的 cout 是名字空间 std中的 cout,而不是其它名称空间中的 cout

  • #include 是预处理器编译指令,表示使用预处理器在主编译之前对源文件进行整理。这里并不需要任何指令调用预处理器,编译时自动调用执行。这里的意思就是将 iostream 文件中的内容添加到程序中,即合成为一个新文件。这里的用途就是,在源文件被编译之前,替换或添加文本,这也是典型的一种预处理器操作。
  • using namespcec std 是编译指令,如果是 #include <xxx.h> 则不需要 using 编译指令,因为老式的头文件没有使用名称空间。新头文件使用了 std 名称空间,标准库的类、函数、变量是 C++ 编译器的标准组件,被放到了 std 空间中。

但是,尽量不要使用 using namespace std,这句话的意思是告诉编辑器我们将要使用空间 std 中的函数或者对象。或者说,能不用就不用,能在大括号里面用就不要在外面用,尤其是在 .h 等头文件中。幻想一下,你写的 .h 文件被其它人使用,你的名字空间和他人的名字空间不一样,但名字空间下面的函数名一样,就会导致冲突。跟 python 中写 from numpy import * 一个道理。

自定义名称空间

名称空间提供了一个声明名称的区域,而可以通过作用域解析运算符 :: 访问其中的名称。如下是一种简单的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// mylib/show_info.h 文件下
#include <iostream>

// 名称空间
namespace std1 {
// 名称
void cout () {
std::cout << "first" << std::endl;
}
}

namespace std2 {
void cout () {
std::cout << "second" << std::endl;
}
}

// main.cpp 文件下
#include "mylib/show_info.h"
using namespace std1;
int main (){
cout();
return 0;
}

using 声明与编译指令

有的时候并不希望每次使用名称时都进行限定,所以 C++ 提供了两种机制,using 声明使得特定的名称可用,using 编译指令使名称空间中的所有名称可用,两者都可以简化名称空间中名称的使用,也都会增加名称冲突的可能性。

对于 using 声明而言,将指定的的名称添加到声明区域,使其可用。如下所示的代码:

  • 在声明的作用域内,不能在声明其它同名变量;
  • 屏蔽全局同名变量。除用户定义的名称空间外,还存在一个全局名称空间,全局变量在这里面。同 C++ 的局部变量会屏蔽全局变量一样,名称空间也是如此,但两个名称空间中的同名变量并不会冲突。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>

namespace test {
int a;
int b;
};

int a{12};

int main () {
// 冲突
// int a;
using test::a;
// 冲突
// int a;
a = 10;
std::cout << a << std::endl; // 10
std::cout << ::a << std::endl; // 12
return 0;
}

对于 using 编译指令而言,会使所有名称可用,包括可能不会使用的名称。如下所示的代码:

  • test::a 位于局部名称空间,不会影响全局变量;
  • 局部同名变量会屏蔽名称空间里的变量和全局变量。
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 test {
int a = 100;
int b;
};

// 全局名称空间
// using namespace test;

int a{0};

int main (){
// 编译指令
// 局部名称空间
using namespace test;
// 不冲突
int a{1};
// 局部变量
std::cout << a << std::endl;
// 全局变量
std::cout << ::a << std::endl;
// 名称空间的变量
test::a = 3;
std::cout << test::a << std::endl;
return 0;
}

总结一下就是,当名称空间和声明区域定义了相同的名称:

  1. using 声明导入时,会冲突;
  2. using 编译指令导入时,局部名称会屏蔽名称空间里面的名称,且没有警告。

因此,使用 using 声明会更加安全,编译指令可能会掩盖一些同名变量。此外,还有一些其它要注意的点:

  • 名称空间可以嵌套,但最好加上限定,表明这个名称的来源;
  • 以函数为例,名称空间里面的函数的声明和定义要在同一名称空间内;
  • 如果函数被重载,那么一个 using 声明将导入所有版本;
  • 对于未命名的名称空间,不能显式使用 using,不能在名称空间之外的文件使用名称空间中的名称。这可以作为链接性为内部静态变量static的替代品
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #include <iostream>

    namespace {
    int a = 100;
    int b;
    };

    int main (){
    // 名称空间中的 a
    std::cout << a << std::endl; // 100
    return 0;
    }
  • 当名称空间很长时,可以简化名称空间:
    1
    2
    namespace MEF = math::element::fire;
    using MEF::flame;

与构造函数

在 C++11 中,派生类能够重用其直接基类定义的构造函数。

1
2
3
4
5
6
class Derived : Base {
public:
using Base::Base;
/* ... */
};

如上 using 声明,对于基类的每个构造函数,编译器都生成一个与之对应(形参列表完全相同)的派生类构造函数。生成如下类型构造函数:

1
Derived(parms) : Base(args) { }
感谢上学期间打赏我的朋友们。赛博乞讨:我,秦始皇,打钱。

欢迎订阅我的文章