第10讲——名称空间
因为自学C++,第一次使用“using namespace std;”时完全是照着书抄的,关于它的意思也是一知半解,现在,我就要学习它了。
在C++中,名称可以是变量、函数、结构、枚举、类以及类和结构的成员。那么随着项目的增大,名称相互冲突的可能性也将增大。使用多个厂商的类库时,可能导致名称冲突。例如,两个库可能都定义了名为List、Tree和Node的类,但定义的方式不兼容。用户可能希望使用一个库的List类,而使用另一个库的Tree类。这种冲突被称为名称空间问题。
C++标准提供了名称空间工具,以便更好地控制名称的作用域。后来,编译器普遍开始支持名称空间。
首先,我们要熟悉名称空间的概念。
- 声明区域:声明区域是可以在其中进行声明的区域。例如,可以在函数外面声明全局变量,对于这种变量,其声明区域为其所在的文件。对于在函数中声明的变量,其声明区域为其声明所在的代码块。
- 潜在作用域:变量的潜在作用域从声明点开始,到其声明区域的结尾。因此潜在作用域比声明作用域小,这是由22222222222222222于变量必须定义后才能使用。
然而,变量并非在其潜在作用域内的任何位置都是可见的。例如,它可能被另一个在嵌套声明区域中声明的同名变量隐藏。例如,在函数中声明的局部变量(对于这种变量,声明区域为整个函数)将隐藏在同一个文件中声明的全局变量(对于这种变量,声明区域为整个文件)。变量对程序而言可见的范围被称为作用域,前面正是以这种方式使用该术语的。下图对术语声明区域、潜在作用域和作用域进行了说明。
C++关于全局变量和局部变量的规则定义了一种名称空间层次。每个声明区域都可以声明名称,这些名称独立于在其他区域中声明的名称。在一个函数中声明的局部变量不会与在另一个函数中声明的局部变量发生冲突。
上面一段内容是传统的C++名称空间,接下来我们将学习新的名称空间特性。
C++新增了这样一种功能——通过定义一种新的声明区域来创建命名的名称空间,这样做的目的是提供一个声明名称的区域。一个名称空间中的名称不会与另外一个名称空间的相同名称发生冲突,同时允许程序的其他部分使用该名称空间中声明的东西。
例如,下面的代码使用新的关键字namespace创建了两个名称空间:Jack 和 jill 。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
namespace Jack{ double pail; //定义变量 void fetch(); //函数原型 int pal; //定义变量 struct Well { ... }; //定义结构体 } namespace Jill{ double bucket(double n) { ... } //定义函数 double fetch; //定义变量 int pal; //定义变量 struct Hill { ... }; //定义结构体 }
名称空间可以是全局的,也可以位于另一个名称空间中,但不能位于代码块中。因此,在默认情况下,在名称空间中声明的名称的链接性为外部的(除非它引用了常量)。
除了用户定义的名称空间外,还存在另一个名称空间——全局名称空间。它对应于文件级声明区域,因此前面所说的全局变量现在被描述为位于全局名称空间中。
任何名称空间的名称都不会与其他名称空间中的名称发生冲突。因此,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; //create a type Hill structure Jack::fetch(); //使用函数
未被装饰的名称(如pail)称为未限定的名称;包含名称空间的名称(如Jack::pail)称为限定的名称。
但我们并不希望每次使用名称时都对它进行限定,因此C++提供了两种机制(using声明和using编译指令)来简化对名称空间中名称的使用。
- using声明和using编译指令
using声明使特定的标识符可用,using编译指令使整个名称空间可用。
using声明由被限定的名称和它前面的关键字using组成:
using Jill::fetch; //a using declation
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 Jack; //make names available globally
在函数中使用using编译指令,将使其中的名称在该函数中可用,下面是一个例子:
int main() { using namespace jack; //make names availbale in vorn() ... }
有关using编译指令和using声明,需要记住的一点是:它们增加了名称冲突的可能性。也就是说,如果有名称空间jack和jill,并在代码中使用作用域解析运算符,则不会存在二义性:
jack::pal = 3; jill::pal = 10;
变量jack::pal和jill::pal是不同的标识符,表示不同的内存单元。然而,如果使用using声明,情况将发生变化:
using jack::pal; using jill::pal; pal = 4; //while one? now have a conflict
事实上,编译器不允许我们同时使用上述两个using声明,因为这将导致二义性。
- using编译指令和using声明之比较
- 名称空间的其他特性
- 未命名的名称空间
【名称空间实例】
【名称空间及其前途】