之前在学 C++ 的时候,第一个例子大概是:
1 |
|
当时也没多想,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 | // mylib/show_info.h 文件下 |
using 声明与编译指令
有的时候并不希望每次使用名称时都进行限定,所以 C++
提供了两种机制,using
声明使得特定的名称可用,using
编译指令使名称空间中的所有名称可用,两者都可以简化名称空间中名称的使用,也都会增加名称冲突的可能性。
对于 using
声明而言,将指定的的名称添加到声明区域,使其可用。如下所示的代码:
- 在声明的作用域内,不能在声明其它同名变量;
- 屏蔽全局同名变量。除用户定义的名称空间外,还存在一个全局名称空间,全局变量在这里面。同
C++
的局部变量会屏蔽全局变量一样,名称空间也是如此,但两个名称空间中的同名变量并不会冲突。
1 |
|
对于 using
编译指令而言,会使所有名称可用,包括可能不会使用的名称。如下所示的代码:
test::a
位于局部名称空间,不会影响全局变量;- 局部同名变量会屏蔽名称空间里的变量和全局变量。
1 |
|
总结一下就是,当名称空间和声明区域定义了相同的名称:
using
声明导入时,会冲突;using
编译指令导入时,局部名称会屏蔽名称空间里面的名称,且没有警告。
因此,使用 using
声明会更加安全,编译指令可能会掩盖一些同名变量。此外,还有一些其它要注意的点:
- 名称空间可以嵌套,但最好加上限定,表明这个名称的来源;
- 以函数为例,名称空间里面的函数的声明和定义要在同一名称空间内;
- 如果函数被重载,那么一个
using
声明将导入所有版本; - 对于未命名的名称空间,不能显式使用
using
,不能在名称空间之外的文件使用名称空间中的名称。这可以作为链接性为内部静态变量static的替代品。1
2
3
4
5
6
7
8
9
10
11
12
namespace {
int a = 100;
int b;
};
int main (){
// 名称空间中的 a
std::cout << a << std::endl; // 100
return 0;
} - 当名称空间很长时,可以简化名称空间:
1
2namespace MEF = math::element::fire;
using MEF::flame;
与构造函数
在 C++11 中,派生类能够重用其直接基类定义的构造函数。
1 | class Derived : Base { |
如上 using
声明,对于基类的每个构造函数,编译器都生成一个与之对应(形参列表完全相同)的派生类构造函数。生成如下类型构造函数:
1 | Derived(parms) : Base(args) { } |