在C++中,名称可以是变量、函数、结构、枚举、类以及类和结构的成员。当随着项目的增大,名称相互冲突的可能性也将增加。使用多个厂商的类库时,可能导致名称冲突。例如,两个库可能都定义了名为 List、Tree 和 Node 的类,但定义的方式不兼容。用户可能希望使用这个个库的 List 类,而使用另一个库的Tree类。这种冲突被称为名称空间问题。
C++标准提供了名称空间工具,以便更好地控制名称的作用域。经过了一段时间后,编译器才支持名称空间,但现在这种支持很普遍。
介绍 C++中新增的名称空间特性之前,先复习一下 C++中已有的名称空间属性,并介绍一些术语,让
我们熟悉名称空间的概念。
第一个需要知道的术语是声明区域 (declaration region)。声明区域是可以在其中进行声明的区域。例如,可以在函数外面声明全局变量,对于这种变量,其声明区域为其声明所在的文件。对于在函数中声明的变量,其声明区域为其声明所在的代码块。
第二个需要知道的术语是潜在作用域(potential scope)。变量的潜在作用域从声明点开始,到其声明区域的结尾。因此潜在作用域比声明区域小,这是由于变量必须定义后才能使用。
然而,变量并非在其潜在作用域内的任何位置都是可见的。例如,它可能被另一个在嵌套声明区域中声明的同名变量隐藏。例如,在函数中声明的局部变量(对于这种变量,声明区域为整个函数)将隐藏在同一个文件中声明的全局变量(对于这种变量,声明区域为整个文件)。变量对程序而言可见的范围被称为作用域(scope),前面正是以这种方式使用该术语的。
C++关于全局变量和局部变量的规则定义了一种名称空间层次。每个声明区域都可以声明名称,这些名称独立于在其他声明区域中声明的名称。在一个函数中声明的局部变量不会与在另一个函数中声明的局部变量发生冲突。
C++新增了这样一种功能,即通过定义一种新的声明区域来创建命名的名称空间,这样做的目的之一是提供一个声明名称的区域。一个名称空间中的名称不会与另外一个名称空间的相同名称发生冲突,同时允许程序的其他部分使用该名称空间中声明的东西。例如,下面的代码使用新的关键字 namespace 创建了两个名称空间:Jack 和Jill。
?? ??? ?namespace Jack {
?? ??? ??? ??? ?double pail;?? ??? ??? ?// 声明变量 ?? ?
?? ??? ??? ??? ?int pal;?? ??? ??? ??? ?// 声明变量?
?? ??? ??? ??? ?struct Well{...};?? ??? ?// 声明结构体
?? ??? ??? ??? ?void fetch();?? ??? ??? ?// 函数原型
?? ??? ??? ?}
?? ??? ??? ?
?? ??? ?namespace Ji11 {?? ??? ?
?? ??? ??? ??? ?double fetch;?? ??? ?// 声明变量 ?? ?
?? ??? ??? ??? ?int pal;?? ??? ??? ?// 声明变量 ?? ?
?? ??? ??? ??? ?struct Hill{ ...};?? ?// 声明结构体
?? ??? ??? ??? ?double bucket(double n)( ...)?? ?// 函数定义
?? ??? ??? ?}
名称空间可以是全局的,也可以位于另一个名称空间中,但不能位于代码块中。因此,在默认情况下,在名称空间中声明的名称的链接性为外部的(除非它引用了常量)。
除了用户定义的名称空间外,还存在另一个名称空间——全局名称空间(global namespace)。它对应于文件级声明区域,因此前面所说的全局变量现在被描述为位于全局名称空间中。
任何名称空间中的名称都不会与其他名称空间中的名称发生冲突。因此,Jack 中的 fetch 可以与 Jill中的 fetch 共存,Jill 中的 Hill 可以与外部 Hill 共。名称空间中的声明和定义规则同全局声明和定义规则相同。
名称空间是开放的(open,即可以把名称加入到已有的名称空间中。例如,下面这条语句将名称 goose添加到Ji1l中已有的名称列表中:
?? ??? ?namespace Jill{
?? ??? ??? ??? ?char * goose(const char *];
?? ??? ??? ?}
同样,原来的Jack 名称空间为 fetch()函数提供了原型。可以在该文件后面(或另外一个文件中)再次使用Jack名称空间来提供该函数的代码:
?? ??? ?namespace Jackvoid {
?? ??? ?{
?? ??? ??? ?void fetch()
?? ??? ??? ?{
?? ??? ??? ??? ?...
?? ??? ??? ?}
?? ??? ?}
当然,需要有一种方法来访问给定名称空间中的名称。最简单的方法是,通过作用域解析运算符::,使用名称空间来限定该名称:
?? ??? ?Jack::pal = 12.34; ?? ?// 使用一个变量
?? ??? ?Jill::Hill mole;?? ?// 创建一个 Hill 结构体类型
?? ??? ?Jack::fetch();?? ??? ?// 使用一个函数
未被装饰的名称(如 pail)称为未限定的名称(unqualified name);包含名称空间的名称(如Jack::pail)称为限定的名称(qualified name)。
using声明和using编译指令
我们并不希望每次使用名称时都对它进行限定,因C++提供了两种机制( using 声明和using 编译指令)来简化对名称空间中名称的使用。using 声明使特定的标识符可用,usig 编译指令使整个名称空间可用。
using声明由被限定的名称和它前面的关键字using组成:
?? ??? ?using Jill::fetch; ? // 一个using声明
using 声明将特定的名称添加到它所属的声明区域中。例如 main()中的 using 声明 Jill::fetch 将 fetch添加到 main()定义的声明区域中。完成该声明后,便可以使用名称 fetch 代替 Jill::fetch。下面的代码段说了这几点:
namespace Jill {
?? ??? ?double bucket(double n) {...}
?? ??? ?double fetch;
?? ??? ?struct Hill{....};
?? ?}
ehar fetch;
int main()
{
?? ?using Jill::fetch;?? ?// 把fetch放入本地命名空间
?? ?double fetch;?? ??? ?// 定义错误,因为已经有了一个本地的fetch变量
?? ?cin >> fetch;?? ??? ?// 读入一个值存入到 Jill::fetch.
?? ?cin >> ::fetch;?? ??? ?// 从全局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; // 引入Jack命名空间下所有的元素
在全局声明区域中使用 using编译指令,将使该名称空间的名称全局可用。这种情况已出现过多次
?? ??? ?#include <iostream> ? ? // 在命名空间std中
?? ??? ?using namespace std;?? ?// 让元素属性称为全局?
在函数中使用 using 编译指令,将使其中的名称在该函数中可用,下面是个例子
?? ??? ?int main()
?? ??? ?{
?? ??? ??? ?using namespace jack; // make names available in vornf)
?? ??? ??? ?...
?? ??? ?}
在本书前面中,经常将这种格式用于名称空间std。
有关using编译指令和using声明,需要记住的一点是,它们增加了名称冲突的可能性。也就是说,如果有名称空间jack和jill,并在代码中使用作用域解析运算符,则不会存在二义性
?? ?
?? ??? ?jack::pal = 3;
?? ??? ?jill::pal = 10;
?? ??? ?
变量jack::pal 和jill:pal 是不同的标识符,表示不同的内存单元。然而,如果使用using 声明,情况将发生变化:
?? ??? ?using jack::pal;
?? ??? ?usinq iill::pal;
?? ??? ?pal = 4;?? ??? ?// 使用的是哪一个pal,这里会产生冲突
事实上,编译器不允许同时使用上述两个using 声明,因为这将导致二义性
示例源码:
// Len2024_0102_01.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
namespace Jack {
double pail; // 声明变量
int pal; // 声明变量
struct Well // 声明结构体
{
int headstate;
int bodystate;
} well;
void fetch() // 函数声明
{
printf("Jack::fetch pail=%lf, pal=%d, headstate=%d, bodystate=%d", pail, pal, well.headstate, well.bodystate);
}
}
namespace Ji11 {
double fetch; // 声明变量
int pal; // 声明变量
struct Hill // 声明结构体
{
int bigTree;
int smallTree;
}hill;
double bucket(double n) // 函数定义
{
fetch = n;
printf("Jill::bucket fetch=%lf,pal=%d, bigTree=%d, smallTree=%d\n", fetch,pal, hill.bigTree, hill.smallTree);
return n;
}
}
int main()
{
printf("\n");
Jack::pail = 101.99;
Jack::pal = 102;
Jack::well.bodystate = 103;
Jack::well.headstate = 104;
Jack::fetch();
printf("\n\n");
using namespace Ji11;
pal = 202;
hill.bigTree = 203;
hill.smallTree = 204;
bucket(201);
}
执行结果: