《C++ Primer Plus》第九章:内存模型和名称空间(2)

名称空间

传统的C++名称空间

  1. 声明区域
  2. 潜在作用域
  3. 作用域

新的名称空间特性

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 共存。名称空间中的声明和定义规则同全局声明和定义规则相同。
名称空间是开放的,即可以把名称加入到已有的名称空间中。例如,下面这条语句将名称 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()
    }
    

    有关 using 编译指令 和 using 声明,需要记住的一点是,它们增加了名称冲突的可能性。

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

    使用 using 编译指令导入一个名称空间中所有的名称与使用多个 using 声明是不一样的,而更像是大量使用作用域解析运算符。使用 using 声明时,就好像声明了相应的名称一。如果某个名称已经在函数中声明了,则不能用using声明导入相同的名称。然而,使用 using 编译指令时,将进行名称解析,如果使用 using 编译指令导入一个已经在函数中声明的名称,则局部名称将隐藏名称空间名,就像隐藏同名的全局变量一样。但是仍可以通过作用域解析运算符来使用。

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

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

  3. 名称空间的其他特性
    可以将名称空间声明进行嵌套:

    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;
    }
    

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

    std::cin>>myth::fetch;
    

    当然,由于它也位于 Jill 名称空间中,因此仍然可以称作 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 和 elements:

    using namespace myth;
    

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

    using namespace myth;
    using namespace elements;
    

    可以给名称空间创建别名,
    可以使用这种技术把复杂的名称空间名简化:

    namespace mvft = my_very_favorite_things;
    

    也可以使用这种结束来简化对嵌套名称空间的使用:

    namespace MEF = myth::elements::fire;
    using MEF::flame;
    
  4. 未命名的名称空间

    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(){
    	...
    }
    

名称空间及其前途

随着程序员逐渐熟悉名称空间,将出现统一的编程理念。下面是当前的一些指导原则。

  • 使用在已命名的名称空间中声明的变量,而不是使用外部全局变量
  • 使用在已命名的名称空间中声明的变量,而不是使用静态全局变量
  • 如果开发了一个函数库或类库,将其放在一个名称空间中
    事实上,C++ 当前提倡将标准函数库放在名称空间std中,这种做法扩展到了来自C语言中的函数。例如,头文件math.h 是与 C 语言兼容的,没有名称空间,但C++头文件cmatch应将各种数学库函数放在名称空间std中。实际上,并非所有的编译器都完成了这种过渡。
  • 仅将编译指令 using 作为一种将旧代码转换为使用名称空间的权宜之计。
  • 不要在头文件中使用 using 编译指令。首先,这样做掩盖了要让哪些名称可用;另外,包含头文件的顺序可能影响程序的行为。如果非要使用编译指令 using,应将其放在所有预处理器编译指令 #include 之后
  • 导入名称时,首选使用作用域解析运算符或 using 声明的方法
  • 对于 using 声明,首选将其作用域设置为局部而不是全局。

使用名称空间的主旨是简化大型编程项目的管理工作。对于只有一个文件的简单程序,使用 using 编译指令并非什么大逆不道的事。
正如前面指出的,头文件名的变化反映了这些变化。老式头文件(如 iostream.h)没有使用名称空间,但新头文件 iostream 使用了 std 名称空间。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值