名称空间(C++)

在C++中,名称可以是变量、函数、结构、枚举、类以及类和结构的成员。当随着项目的增大,名称相互冲突的可能性也增加。使用多个厂商的类库时,可能导致名称冲突。例如,两个库可能都定义了名为List、Tree和Node的类,但定义的方式不兼容。用户可能希望使用一个库的List类,而使用另一个库的Tree类。这种冲突被称为名称空间问题。
C++标准提供了名称空间工具,以便更好地控制名称的作用域。

传统的C++名称空间:

声明区域(declaration region):声明区域是可以在其中进行声明的区域。
潜在作用域(oitential scope):变量的潜在作用域从声明点开始,到其生命区域的结尾。
然而,变量并非在其潜在作用域内的任何位置都是可见的。例如,它可能被另一个在嵌套声明区域中声明的同名变量隐藏(同名的局部变量隐藏全局变量)。

新的名称空间特性:

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

namespace Jack {
    double pail;
    void fetch();
    int pal;
    struct Well { ... };
}
namespace Jill {
    double bucket(double n) { ... };
    double fetch;
    int pal;
    struct Hill { ... };
};

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

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

任何名称空间中的名称都不会与其他名称空间中的名称发生冲突。因此,Jack中的fetch可以与Jill中的fetch共存,Jill中的Hill可以与外部Hill共存。

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

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

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

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

访问给定名称空间中的名称:通过作用域解析运算符::,使用名称空间来限定该名称:

Jack::pail = 12.34;
Jill::Hill mole;
Jack::fetch();

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

using声明和using编译指令:

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

using Jill::fetch;

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;
    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声明时,将把名称添加到全局名称空间中。

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

using namespace Jack;

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

Jack::pal = 3;
Jill::pal = 10;

变量Jack::pal与Jill::pal是不同的标识符,表示不同的内存单元。然而,如果使用using声明,情况将发生变化。事实上,编译器不允许您同时使用上述两个using声明,因为这将导致二义性。

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

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

namespace Jill {
    double bucket(double n) { ... };
    double fetch;
    struct Hill { ... };
};
char fetch;
int main() {
    using namespace Jill;
    Hill Thrill;
    double water = bucket(2);
    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声明将名称空间的名称导入该声明区域,则这两个名称会发生冲突,从而出错。如果使用using编译指令将该名称空间的名称导入该声明区域,则局部版本将隐藏名称空间版本。

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

名称空间的其他特性:

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

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

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

using namespace elements::fire;

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

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

using编译指令是可传递的。如果A op B且B op C则A op C,则说操作op是可传递性的。

using namespace myth;

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

using namespace myth;
using namespace elements;

可以给名称空间创建别名:

namespace my_very_favorite_things{ ... };
namespace mvft = my_very_favorite_things;

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

未命名的名称空间:

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

namespace {
    int ice;
    int bandycoot;
}

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

名称空间及其前途:

随着程序员逐渐熟悉名称空间,将出现统一的编程理念。

  • 使用在已命名的名称空间中声明的变量,而不是使用外部全局变量。
  • 使用在已命名的名称空间中声明的变量,而不是使用静态全局变量。
  • 如果开发了一个函数库或类库,将其放在一个名称空间中。事实上,C++当前提倡将标准函数库放在名称空间std中,这种做法扩展到了来自C语言中的函数。例如,头文件math.h是与C语言兼容的,没有使用名称空间,但C++头文件cmath应将各种数学库函数放在名称空间std中。
  • 仅将编译指令using作为一种将旧代码转换为使用名称空间的权宜之计。
  • 不要在头文件中使用using编译指令。
  • 导入名称时,首选使用作用域解析运算符或using声明的方法。
  • 对于using声明,首选将其作用域设置为局部而不是全局。

声明:以上整理自个人理解和Stephen Prata 著的《C++ Primer Plus》

猜你喜欢

转载自blog.csdn.net/MoooLi/article/details/82744128
今日推荐