当前位置:嗨网首页>书籍在线阅读

16-新的名称空间特性

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

9.3.2 新的名称空间特性

C++新增了这样一种功能,即通过定义一种新的声明区域来创建命名的名称空间,这样做的目的之一是提供一个声明名称的区域。一个名称空间中的名称不会与另外一个名称空间的相同名称发生冲突,同时允许程序的其他部分使用该名称空间中声明的东西。例如,下面的代码使用新的关键字namespace创建了两个名称空间:Jack和Jill。

namespace Jack {
    double pail;         // variable declaration
    void fetch();        // function prototype
    int pal;             // variable declaration
    struct Well { ... }; // structure declaration
}
namespace Jill {
    double bucket(double n) { ... }  // function definition
    double fetch;                    // variable declaration
    int pal;                         // variable declaration
    struct Hill { ... };             // structure declaration
}

名称空间可以是全局的,也可以位于另一个名称空间中,但不能位于代码块中。因此,在默认情况下,在名称空间中声明的名称的链接性为外部的(除非它引用了常量)。

除了用户定义的名称空间外,还存在另一个名称空间——全局名称空间(global namespace)。它对应于文件级声明区域,因此前面所说的全局变量现在被描述为位于全局名称空间中。

任何名称空间中的名称都不会与其他名称空间中的名称发生冲突。因此,Jack中的fetch可以与Jill中的fetch共存,Jill中的Hill可以与外部Hill共存。名称空间中的声明和定义规则同全局声明和定义规则相同。

名称空间是开放的(open),即可以把名称加入到已有的名称空间中。例如,下面这条语句将名称goose添加到Jill中已有的名称列表中:

namespace Jill {
    char * goose(const char *);
}

同样,原来的Jack名称空间为fetch()函数提供了原型。可以在该文件后面(或另外一个文件中)再次使用Jack名称空间来提供该函数的代码:

namespace Jack {
    void fetch()
    {
        ...
    }
}

当然,需要有一种方法来访问给定名称空间中的名称。最简单的方法是,通过作用域解析运算符::,使用名称空间来限定该名称:

Jack::pail = 12.34; // use a variable
Jill::Hill mole;    // create a type Hill structure
Jack::fetch();      // use a function

未被装饰的名称(如pail)称为未限定的名称(unqualified name);包含名称空间的名称(如Jack::pail)称为限定的名称(qualified name)。

1.using声明和using编译指令

我们并不希望每次使用名称时都对它进行限定,因此C++提供了两种机制(using声明和using编译指令)来简化对名称空间中名称的使用。using声明使特定的标识符可用,using编译指令使整个名称空间可用。

using声明由被限定的名称和它前面的关键字using组成:

using Jill::fetch; // a using declaration

using声明将特定的名称添加到它所属的声明区域中。例如main()中的using声明Jill::fetch将fetch添加到main()定义的声明区域中。完成该声明后,便可以使用名称fetch代替Jill::fetch。下面的代码段说明了这几点:

namespace Jill {
    double bucket(double n) { ... }
    double fetch;
    struct Hill { ... };
}
char fetch;
int main()
{
    using Jill::fetch; // put fetch into local namespace
    double fetch;      // Error! Already have a local fetch
    cin >> fetch;      // read a value into Jill::fetch
    cin >> ::fetch;    // read a value into global fetch
    ...
}

由于using声明将名称添加到局部声明区域中,因此这个示例避免了将另一个局部变量也命名为fetch。另外,和其他局部变量一样,fetch也将覆盖同名的全局变量。

在函数的外面使用using声明时,将把名称添加到全局名称空间中:

void other();
namespace Jill {
    double bucket(double n) { ... }
    double fetch;
    struct Hill { ... };
}
using Jill::fetch; // put fetch into global namespace
int main()
{
    cin >> fetch;  // read a value into Jill::fetch
    other()
...
}
void other()
{
    cout << fetch; // display Jill::fetch
...
}

using声明使一个名称可用,而using编译指令使所有的名称都可用。using编译指令由名称空间名和它前面的关键字using namespace组成,它使名称空间中的所有名称都可用,而不需要使用作用域解析运算符:

using namespace Jack; // make all the names in Jack available

在全局声明区域中使用using编译指令,将使该名称空间的名称全局可用。这种情况已出现过多次:

#include <iostream>  // places names in namespace std
using namespace std; // make names available globally

在函数中使用using编译指令,将使其中的名称在该函数中可用,下面是一个例子:

int main()
{
    using namespace jack; // make names available in vorn()
...
}

在本书前面中,经常将这种格式用于名称空间std。

有关using编译指令和using声明,需要记住的一点是,它们增加了名称冲突的可能性。也就是说,如果有名称空间jack和jill,并在代码中使用作用域解析运算符,则不会存在二义性:

jack::pal = 3;
jill::pal =10;

变量jack::pal和jill::pal是不同的标识符,表示不同的内存单元。然而,如果使用using声明,情况将发生变化:

using jack::pal;
using jill::pal;
pal = 4;       // which one? now have a conflict

事实上,编译器不允许您同时使用上述两个using声明,因为这将导致二义性。

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

使用using编译指令导入一个名称空间中所有的名称与使用多个using声明是不一样的,而更像是大量使用作用域解析运算符。使用using声明时,就好像声明了相应的名称一样。如果某个名称已经在函数中声明了,则不能用using声明导入相同的名称。然而,使用using编译指令时,将进行名称解析,就像在包含using声明和名称空间本身的最小声明区域中声明了名称一样。在下面的示例中,名称空间为全局的。如果使用using编译指令导入一个已经在函数中声明的名称,则局部名称将隐藏名称空间名,就像隐藏同名的全局变量一样。不过仍可以像下面的示例中那样使用作用域解析运算符:

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
}

在main()中,名称Jill::fetch被放在局部名称空间中,但其作用域不是局部的,因此不会覆盖全局的fetch。然而,局部声明的fetch将隐藏Jill::fetch和全局fetch。然而,如果使用作用域解析运算符,则后两个fetch变量都是可用的。读者应将这个示例与前面使用using声明的示例进行比较。

需要指出的另一点是,虽然函数中的using编译指令将名称空间的名称视为在函数之外声明的,但它不会使得该文件中的其他函数能够使用这些名称。因此,在前一个例子中,foom()函数不能使用未限定的标识符Hill。

注意: 假设名称空间和声明区域定义了相同的名称。如果试图使用using声明将名称空间的名称导入该声明区域,则这两个名称会发生冲突,从而出错。如果使用using编译指令将该名称空间的名称导入该声明区域,则局部版本将隐藏名称空间版本。

一般说来,使用using声明比使用using编译指令更安全,这是由于它只导入指定的名称。如果该名称与局部名称发生冲突,编译器将发出指示。using编译指令导入所有名称,包括可能并不需要的名称。如果与局部名称发生冲突,则局部名称将覆盖名称空间版本,而编译器并不会发出警告。另外,名称空间的开放性意味着名称空间的名称可能分散在多个地方,这使得难以准确知道添加了哪些名称。

下面是本书的大部分示例采用的方法:

#include <iostream>
int main()
{
    using namespace std;

首先,#include语句将头文件iostream放到名称空间std中。然后,using编译指令是该名称空间在main()函数中可用。有些示例采取下述方式:

#include <iostream>
using namespace std;
int main()
{

这将名称空间std中的所有内容导出到全局名称空间中。使用这种方法的主要原因是方便。它易于完成,同时如果系统不支持名称空间,可以将前两行替换为:

#include <iostream.h>

然而,名称空间的支持者希望有更多的选择,既可以使用解析运算符,也可以使用using声明。也就是说,不要这样做:

using namespace std; // avoid as too indiscriminate

而应这样做:

int x;
std::cin >> x;
std::cout << x << std::endl;

或者这样做:

using std::cin;
using std::cout;
using std::endl;
int x;
cin >> x;
cout << x << endl;

可以用嵌套式名称空间(将在下一节介绍)来创建一个包含常用using声明的名称空间。

3.名称空间的其他特性

可以将名称空间声明进行嵌套:

namespace elements
{
    namespace fire
    {
        int flame;
        ...
    }
    float water;
}

这里,flame指的是element::fire::flame。同样,可以使用下面的using编译指令使内部的名称可用:

using namespace elements::fire;

另外,也可以在名称空间中使用using编译指令和using声明,如下所示:

namespace myth
{
    using Jill::fetch;
    using namespace elements;
    using std::cout;
    using std::cin;
}

假设要访问Jill::fetch。由于Jill::fetch现在位于名称空间myth(在这里,它被叫作fetch)中,因此可以这样访问它:

std::cin >> myth::fetch;

当然,由于它也位于Jill名称空间中,因此仍然可以称作Jill::fetch:

Jill::fetch:
std::cout << Jill::fetch;   // display value read into myth::fetch

如果没有与之冲突的局部变量,则也可以这样做:

using namespace myth;
cin >> fetch;   // really std::cin and Jill::fetch

现在考虑将using编译指令用于myth名称空间的情况。using编译指令是可传递的。如果A op B且B op C,则A op C,则说操作op是可传递的。例如,>运算符是可传递的(也就是说,如果A>B且B>C,则A>C)。在这个情况下,下面的语句将导入名称空间myth和elements:

using namespace myth;

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

using namespace myth;
using namespace elements;

可以给名称空间创建别名。例如,假设有下面的名称空间:

namespace my_very_favorite_things { ... };

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

namespace mvft = my_very_favorite_things;

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

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

4.未命名的名称空间

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

namespace // unnamed namespace
{
    int ice;
    int bandycoot;
}

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

static int counts; // static storage, internal linkage
int other();
int main()
{
...
}
int other()
{
...
}

采用名称空间的方法如下:

namespace
{
    int counts; // static storage, internal linkage
}
int other();
int main()
{
...
}
int other()
{
...
}