曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 2586|回复: 0
打印 上一主题 下一主题

名称空间, namespace 与 using

[复制链接]

4917

主题

5879

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34382
跳转到指定楼层
楼主
发表于 2013-10-5 23:30:01 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
namespace 关键字是用来创建名称空间的,名称空间的目的是为了避免相同名称的冲突。下面用 namespace 创建了两个名称空间,Jack 和 Jill :
  1. namespace Jack {
  2. double pail; // 变量声明
  3. void fetch(); // 函数原型
  4. int pal; // 变量声明
  5. struct Well { ... }; // 结构声明
  6. }
  7. namespace Jill {
  8. double bucket(double n) { ... } // 函数定义
  9. double fetch; // 变量声明
  10. int pal; // 变量声明
  11. struct Hill { ... }; // 结构声明
  12. }
复制代码
名称空间可以是全局的,也可以位于另一个名称空间中,但不能位于代码块中。除了用户自定义的名称空间外,还有一个全局名称空间(global namespace),它对应于文件级声明区域,全局变量就是位于全局名称空间中的。

一个名称空间中的名称不会和其他名称空间中的同名名称冲突,就像两个不同的家庭,可以有同名的人。如上定义的 Jack 中的 fetch 可以和 Jill 中的 fetch 共存,Jill 中的 Hill 可以与外部的 Hill 共存。

名称空间被定义后,并不是关闭的,而是开放的,即后续可以再往已有的名称空间中添加新的名称,比如:
  1. namespace Jill {
  2. char * goose(const char *);
  3. }
复制代码
上面语句将名称 goose 添加到 Jill 已有的名称列表中。又如:
  1. namespace Jack {
  2. void fetch()
  3. {
  4. ...
  5. }
  6. }
复制代码
这是为原来 Jack 名称空间中的 fetch() 函数原型添加函数的实现代码,可以将其添加到该文件的后面,也可以在另外一个文件中。

访问给定名称空间中的名称使用域解析运算符 :: ,比如:
  1. Jack::pail = 12.34
  2. Jill::Hill mole
  3. Jack::fetch();
复制代码
包含名称空间的名称,如 Jack::pail ,称为限定的名称( qualified name ),反之称为未限定的名称  (unqualified name) 。


using 声明 和 using 编译指令

using 有两种形式,一种用作 using 声明,另一种用作编译指令,它们的目的都是为了简化对名称空间中名称的使用。
using 声明使特定的标识符可用;using 编译指令使整个名称空间可用。

using 声明的使用:
  1. using Jill::fetch;
复制代码
using 声明将特定的名称添加到它所属的声明区域中:

示例代码1
  1. namespace Jill {
  2. double bucket(double n) { ... }
  3. double fetch;
  4. struct Hill { ... };
  5. }
  6. char fetch;
  7. int main()
  8. {
  9. using Jill::fetch; // 将 fetch 放进本地名称空间
  10. double fetch; // 错误!已经有了一个本地 fetch
  11. cin >> fetch; // 读入一个值到 Jill::fetch
  12. cin >> ::fetch; // 读一个值到全局 fetch
  13. ...
  14. }
复制代码
上面,main() 中用 using 声明 Jill::fetch 将 fetch 添加到 main() 定义的声明区域中。声明后,以后就不需要再写 Jill::fetch ,而只用 fetch 即可。由于 using 声明将名称添加到局部声明区域中,因此就不能再讲另一个局部变量也命名为 fetch ;另外,和其它局部变量的一样,fetch 会覆盖同名的全局变量。

在函数的外面使用 using 声明时,将把名称添加到全局名称空间中:
  1. void other();
  2. namespace Jill {
  3. double bucket(double n) { ... }
  4. double fetch;
  5. struct Hill { ... };
  6. }
  7. using Jill::fetch; // 将 fetch 放到全局名称空间
  8. int main()
  9. {
  10. cin >> fetch; // 读入一个值到 Jill::fetch
  11. other()
  12. ...
  13. }
  14. void other()
  15. {
  16. cout << fetch; //显示 Jill::fetch
  17. ...
  18. }
复制代码
using 声明使一个名称可用,而 using 编译指令使所有的名称可用。using 编译指令由名称空间名和它前面的关键字 using namespace 组成,如:
  1. using namespace Jack;
复制代码
它使名称空间中的所有名称都可用,而不需要使用作用域解析运算符。这个用法最为常见,比如 using namespace std; 。

using 声明和 using 编译指令增加了名称冲突的可能性。比如说,如果有 jack 和 jill 两个名称空间,在代码中使用作用域解析运算符来使用其中的名称,那么不会存在二义性,如:
  1. jack::pal = 3;
  2. jill::pal = 10;
复制代码
jack::pal 和 jill::pal 是不同的标识符,表示不同的内存单元,因此不会有什么问题。但是,如果使用 using 声明,那就另当别论:
  1. using jack::pal;
  2. using jill::pal;
  3. pal = 4;   //二义性,不知道选哪一个
复制代码
事实上,编译器不允许同时上述那样使用两个 using 声明,因为这将导致二义性。

using 编译指令和 using 声明的比较

实际上,使用 using 编译指令导入一个空间名称中所有的名称与使用多个 using 声明是不一样的,而更像是大量使用作用域解析运算符。使用 using 声明时,就好像声明了相应的名称一样。如果某个名称已经在函数中声明了,就不能用 using 声明导入相同的名称。然而,在使用 using 编译指令时,将进行名称解析,就像你在一个同时包含了 using 声明和名称空间本身的最小的声明区域里声明名称一样。


在下面的示例中,名称空间是全局的。如果使用 using 编译指令导入一个已经在函数中声明的名称,那么该局部名称将隐藏名称空间名,就像隐藏同名的全局变量一样。不过仍可以像下面的示例中那样使用作用域解析运算符:


示例代码2
[C++] 纯文本查看 复制代码
#include <iostream>
using namespace std;

namespace Jill {
double bucket(double n) { return (n+1); }
double fetch;
struct Hill { int i; char c; };
}
char fetch; // 全局名称空间
int main()
{
	using namespace Jill; // 导入 Jill 名称空间里的所有名
	Hill Thrill; // 创建一个 Jill::Hill 类型结构
	double water = bucket(2); // 使用 Jill::bucket();
	double fetch; // 这里不是错误,它将 Jill::fetch 隐藏
	cin >> fetch; // 读入一个值到本地 fetch
	cin >> ::fetch; // 读入一个值到全局 fetc
	cin >> Jill::fetch; // 读入一个值到 Jill::fetch

	return 0;
}
int foom()
{
	//Hill top; // 错误!
	Jill::Hill crest; // 没有错!
}


在 main() 中,名称 Jill::fetch 被放在局部名称空间中,但其作用域并不是局部的,因此不会覆盖全局的 fetch 。然而,局部声明的 fetch 将隐藏 Jill::fetch 和全局 fetch 。

虽然函数中的 using 编译指令将名称空间的名称视为函数之外声明的,但它不会使得该文件中的其他函数能够使用这些名称。比如上面的 foom() 函数不能使用未限定标识符 Hill ,在 g++ 编译下,会看到错误提示:
[beyes@groad ~]$ g++ tmp.cc -o tmp
tmp.cc: In function ‘int foom()’:
tmp.cc:24: error: ‘Hill’ was not declared in this scop

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


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

我们为了图方便,经常会像下面这么写:
  1. #include <iostream>
  2. using namespace std;
复制代码
这使得名称空间 std 中的所有内容导出到全局名称空间中。

名称空间的嵌套

可以将名称空间声明进行嵌套:
  1. namespace elements
  2. {
  3. namespace fire
  4. {
  5. int flame;
  6. ...
  7. }
  8. float water;
  9. }
复制代码
这里,flame 指的是 element::fire::flame 。可以使用下面的 using 编译指令使得内部的名称可用:
  1. using namespace elements::fire;
复制代码
给名称空间起别名
假设有如下名称空间:
  1. namespace my_space { ... };
复制代码
那么使用如下语句让 myalias 成为 my_space 的别名:
  1. namespace myalias = my_space;
复制代码
未命名的名称空间
可以通过省略名称空间的名称来创建未命名的名称空间:
  1. namespace //未命名名称空间
  2. {
  3. int ice;
  4. int bandycoot;
  5. }
复制代码
在这种名称空间声明的名称的潜在作用域为:从声明点到该声明区域末尾。从这个角度来看,它与全局变量相似。然而,由于这种名称空间没有名称,因此不能显示地使用 using 编译指令或 using 声明来使它在其它位置都可用。也就是说,不能在未命名名称空间所述文件之外的其他文件中使用该名称空间中的名称。这提供了链接性为内部的静态变量的替代品。比较下面的代码:
  1. static int counts; // 静态存储,内部链接
  2. int other();
  3. int main()
  4. {
  5. ...
  6. }
  7. int other()
  8. {
  9. ...
  10. }
复制代码
采用名称空间的方法如下:
  1. namespace
  2. {
  3. int counts; // 静态存储,内部链接
  4. }
  5. int other();
  6. int main()
  7. {
  8. ...
  9. }
  10. int other()
  11. {
  12. ...
  13. }
复制代码
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2024-5-17 09:32 , Processed in 0.064930 second(s), 27 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表