0%

C++之名称空间

传统的C++名称空间

一些术语

  • 声明区域
  • 潜在作用域
    变量的潜在作用域从声明点开始,到其声明区域的结尾。因此潜在作用域的比声明区域小,这是由于变量必须定义后才能使用。变量并非在其潜在作用域内的任何位置都是可见的。例如,它可能被另一个在嵌套声明区域中声明的同名变量隐藏。

C++关于全局变量和局部变量的规则定义了一种名称空间层次。每个声明区域都可以声明名称,这些名称独立于在其他声明区域中声明的名称。在一个函数中声明的局部变量不会与在另一个函数中声明的局部变量发生冲突。


新的名称空间的特性

C++新增了这样一种功能,即通过定义一种新的声明区域来创建命名的名称空间,这样做的目的之一是提供一个声明名称的区域。

特性

  • 一个名称空间中的名称不会与另外一个名称空间的相同名称发生冲突,同时允许程序的其他部分使用该名称空间中声明的东西。
  • 名称空间可以是全局的,也可以位于另一个名称空间,但不能位于代码块中。因此,在默认情况下,在名称空间中声明的名称的链接性为外部的(除非它引用了常量)。
  • 除了用户定义的名称空间外,还存在另一个名称空间——全局名称空间。它对应于文件级声明区域,因此前面所说的全局变量现在被描述为位于全局名称空间中。
  • 名称空间是开放的,即可以把名称加入到已有的名称空间中。
    例如,下面这条语句将名称goose添加到Jill中已有的名称列表中:
1
2
3
namespace Jill{
char * goose(const char *);
}
  • 访问给定空间中的名称,通过作用域解析运算符::,使用名称空间来限定该名称。违背装饰的名称称为未限定的名称(unqualified name);包含名称空间的名称称为限定的名称(qualified name)。

using声明和using编译指令

using声明

  • 使特定的标识符可用
  • 由被限定的名称和它前面的关键字using组成:using Jill::fetch;
  • 将特定的名称添加到它所属的声明区域中
  • 在函数的外面使用using声明时,将把名称添加到全局名称空间中

using编译指令

  • 使整个名称空间可用
  • 由名称空间名和它前面的关键字using namespace组成;using namespace Jill;
  • 在函数中使用using编译指令,将使其中的名称在该函数中可用
  • 在全局声明区域中使用using编译指令,将使该名称空间的名称全局可用

using编译指令和using声明之比较

使用using编译指令导入一个名称空间中所有的名称

  • 进行名称解析,就像在包含using声明和名称空间本身的最小声明区域中声明了名称一样

使用多个using声明

  • 好像声明了相应的名称
  • 如果某个名称已经在函数中声明了,则不能用using声明导入相同的名称

在下面的示例中,名称空间为全局的。如果使用using编译指令导入一个已经在函数中声明的名称,则局部名称将隐藏名称空间名,就像隐藏同名的全局变量一样。不过人可以像下面的示例中那样使用作用域解析运算符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
namespace Jill{
double bucket(double n){...}
double fetch;
struct Hill{...};
}
char fetch; //global namespace
int main()
{
using namespace Jill; //import all namespace names
Hill Thrill; //create a type Jill::Hill structure
double water = bucket(2); //use Jill::bucket();
double fetch; //not an error; hides Jill::fetch
cin >> fetch //read a value into the local fetch
cin >> ::fetch; //read a value into global fetch
cin >> Jill::fetch; //read a value into Jill::fetch
...
}
int foom()
{
Hill top; //ERROR
Jill::Hill crest; //valid
}

注意:

  • 虽然函数中的using编译指令将名称空间的名称视为在函数之外声明,但它不会使得该文件中的其他函数能够使用这些名称。因此,在前一个例子中,foom()函数不能使用未限定的标识符Hill。
  • 一般来说,使用using声明比使用using编译指令更安全,这是由于它只导入指定的名称。如果该名称与局部名称发生冲突,编译器将发出指示。
  • 如果系统不支持名称空间,可以将
1
2
#include <iostream>
using namespace std;

换成

1
#include <iostream.h>
  • 可以用嵌套式名称空间来创建一个包含常用using声明的名称空间。

名称空间的其他特性

将名称空间声明进行嵌套

1
2
3
4
5
6
7
8
9
namespace elements
{
namespace fire
{
int flame;
...
}
float water;
}

注意:

  • flame指的是element::fire::flame
  • 可以使用using namespace elements::fire的using编译指令使内部的名称可用
  • 可以在名称空间中使用using编译指令和using声明,如下所示:
1
2
3
4
5
6
7
namespace myth
{
using Jill::fetch;
using namespace elements;
using std::cout;
using std::cin;
}

将using编译指令用于myth名称空间的情况

using编译指令是可以传递的。如果A op B 且 B op C,则A op C,则说明操作 op 是可传递的。
在这个情况下,下面的语句将导入名称空间myth和elements:

1
using namespace myth;

这条编译指令与下面两条编译指令等价:

1
2
using namespace myth;
using namespace elements;

可以给名称空间创建别名

例如,假设有下面的名称空间:

1
namespace my_very_favorite_things{...};

则可以使用下面的语句让mvft成为my_very_favorite_things的别名:

1
namespace mvft = my_very_favorite_things{...};

可以使用这种技术来简化对嵌套名称空间的使用:

1
2
namespace MEF = myth::elements::fire;
using MEF::flame;

未命名的名称空间

可以通过省略名称空间的名称来创建未命名的名称空间:

1
2
3
4
5
namespace         // unnamed namespace
{
int ice;
int bandycoot;
}

这就像后面跟着using编译指令一样,也就是说,在该名称空间中声明的名称的潜在作用域为:从声明点到该声明区域末尾。从这方面看,它们与全局变量相似。然而,由于这种名称空间没有名称,因此不能显式地使用using编译指令或using声明来使它在其他位置都可用。具体地说,不能在未命名名称空间所属文件之外的其他文件中,使用该名称空间的名称。这提供了链接性为内部的静态变量的替代品。

例如:

1
static int counts;

采用名称空间的方法可替换为:

1
2
3
namespace{
int counts;
}

名称空间及其前途

指导原则

  • 使用在已命名的名称空间中声明的变量,而不是使用外部全局变量或静态全局变量
  • 如果开发了一个函数库或类库,将其放在一个名称空间中。
  • 仅将编译指令using作为一种将旧代码转换为使用名称空间的权宜之计。
  • 不要在头文件中使用using编译指令
    首先,这样做掩盖了要让哪些名称可用;另外,包含头文件的顺序可能影响程序的行为
    应将其放在所有预处理编译指令#include之后
  • 导入名称时,首先选用作用域解析符或using声明的方法。
  • 对于using声明,首选将其作用域设置为局部而不是全局。

注意:使用名称空间的主旨使简化大型编程项目的管理工作。

Thank you for your reward !