学习笔记一:命名空间(namespace)之四:using declarations和using directives的区别

本文主要是介绍学习笔记一:命名空间(namespace)之四:using declarations和using directives的区别,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

(本文主要参考c++ primer第17章2.4节内容。)
由前面内容,我们知道c++引入命名空间(namespace)概念的主要目的是避免命名冲突。但是当我们想要引用命名空间成员时,我们就会体会到它相比直接使用变量或函数的不便之处,特别是当命名空间名字很长时,更是如此。比如,我们有一个函数printIsbn(),定义在命名空间cplusplus_primer内,我们引用该成员函数时,要这样写:

cplusplus_primer::printIsbn();

如果再复杂一点,命名空间嵌套命名空间(我们称之为”nested namespace”)的话,那么光写个函数名就得占一行。假如上面的cplusplus_primer是嵌套在命名空间primer下的一个命名空间,那么这个成员函数就变得更长:

primer::cplusplus_primer::printIsbn();

如果每次调用printIsbn()函数,都要写这么一大堆,我想每个人都会觉得烦。
当然,既然我们都能意识到这个问题,c++的设计者们当然也知道。所以针对这类问题,他们提出了三种解决方案:
(1)using declarations(using声明)
(2)namespace aliases (命名空间别名)
(3)using directives(using指示)
下面我们将会对这三种方法一一介绍。

(1)using declarations
什么是using declarations呢?
我们举个例子来说吧!
假设我们要在主函数中,输出”Hello World!”的字符串。代码如下:

#include <iostream>
int main()
{using std::cout;using std::endl;cout << "Hello World!" <<endl;return 0;
}

上述代码中的

using std::cout;
using std::endl;

的方式就是using declarations。
我们可以这样简单地理解using declarations方式:这种方式每次只引入一个命名空间成员,形式是 using namespace_name::member_name
使用using declarations方式的name的作用域:它服从一般的作用域法则。name,从using declarations开始一直到当前作用域结束,都是可见的。(用上面的例子来解释就是,cout这种缩写形式从using std::cout一直到main函数结束都是可用的。)而且它像普通变量和函数那样,如果在当前作用域中存在相同名称的实体,定义在外层的实体会被屏蔽。(using declarations似乎是声明命名空间成员名字的别名
举个小例子:

#include <iostream>
namespace A {int bi = 10;
}
int bi = 0;
int main()
{using std::cout;using std::endl;using A::bi;cout << "bi = "  << bi <<endl;return 0;
}

该段代码的输出结果为10。原因就是using A::bi中的变量bi的作用域是从using A::bi到main函数结束,它屏蔽了全局变量bi,所以输出结果为10 。
要点:using declaration可以出现在global,local,namespace内。类(class)中的using declaration只能使用在该类的基类中定义的命名空间成员。

using declarations,这种引入命名空间的方式有什么好处呢?
它能够让我们对自己程序中使用了哪些命名空间函数有很明确的认识和把握。并且由于它的局部性,使得它能最大可能地避免命名冲突。

(2)namespace aliases(给命名空间取别名)
对于一个名字过长的命名空间,我们也可以指定别名
假如我们自定义一个命名空间(命名空间不以分号结尾)

namespace cplusplus_primer { /* ... */ }

我们可以为这个命名空间指定别名:

namespace primer = cplusplus_primer;

(但是如果cplusplus_primer没有被定义的话,这句话就会报错)
(格式:namespace short_name = origin_name)
命名空间别名也可以用于nested namespace(即包含在命名空间下的命名空间)
比如
QueryLib是定义在命名空间cplusplus_primer内的一个命名空间,Query是它的一个类成员
当我们声明类Query的一个对象tq时,
一般会这样写

cplusplus_primer::QueryLib::Query tq;

如果用namespace aliases改写的话,我们可以为cplusplus_primer::QueryLib指定一个别名:

namespace QLib = cplusplus_primer::QueryLib;
QLib::Query tq;

一个命名空间可以有多个别名,而且别名和原始名之间可以交换使用。
小例子:

#include <iostream>
namespace std01 = std;
int main()
{std01::cout << "Hello World!" <<std::endl;return 0;
}

上面我们给std空间取别名为std01,在main函数中我们发现stdstd01都有用,std并没有因为取过别名之后而失去作用。

(3)using directives
using derective的形式是using namespace namespace_name;
//举例

using namespace std;

这种方式使得来自某个特定命名空间的所有命名在当前作用域都是可见的。
同样一个小例子:
(注意它和之前代码的区别)

#include <iostream>
int main()
{using namespace std;cout << "Hello World!" <<endl;return 0;
}

这里的using namespace std; 使得命名空间std下的所有命名在main函数下都可见。
注意:虽然using directives方式对于编程者而言,似乎使用起来更加方便(只要在全局变量中使用using namespace namespace_name,之后该命名空间下的所有命名都可以使用简写形式),但是我们最好不要那么做。因为当我们引用多个库时,采用using directives的方式,又会重新引起命名冲突问题,那么命名空间也就失去了它最初的作用。(文章开头提到:命名空间的目的就是避免命名冲突)

要点:using directive可以出现命名空间,函数和块中,但不能出现在类中。

<>内容由该篇博文http://blog.csdn.net/custa/article/details/5811160改写
<
primer原文:A using directive does not declare local aliases for the namespace member names. Rather, it has the effect of lifting the namespace members into the nearest scope that contains both the namespace itself and the using directive.
使用using directives方式的name的作用域:using directives不声明命名空间成员名字的别名,相反,using directives具有将命名空间成员提升到包含命名空间本身和 using 指示的最近作用域的效果。(只是效果,实际不是那么回事)
primer在解释作用域提升(也就是上面的lift)的时候,它用的是“as if”,只是看起来像而已,也就是说这并不等价于在“最近作用域”声明了命名空间成员。
两个小例子
Example 1(from Primer)

namespace blip {int bi = 16, bj = 15, bk = 23;// other declarations}int bj = 0; // ok: bj inside blip is hidden inside a namespacevoid manip(){// using directive - names in blip "added" to global scopeusing namespace blip;// clash between ::bj and blip::bj// detected only if bj is used++bi;           // sets blip::bi to 17++bj;           // error: ambiguous// global bj or blip::bj?++::bj;         // ok: sets global bj to 1++blip::bj;     // ok: sets blip::bj to 16int bk = 97;    // local bk hides blip::bk++bk;           // sets local bk to 98}

例子1,我们可以看到,从manip函数内部看,bj的作用域是全局。但通过例子2,我们从全局考虑,可以了解到这只是一种效果,并非真使得其成为一个全局变量。
Example 2(from blog http://blog.csdn.net/custa/article/details/5811160 )

#include <iostream>
using std::cout;
using std::endl;
namespace A  
{  int i = 1;  
}  namespace B  
{  using namespace A;  int i = 2;  void fn()  {  cout << i << endl;  }  
}  int main(int argc, char* argv[])  
{  B::fn();  
}

在B中使用using指示引入A中的成员,但这些成员看起来好像是在全局作用域(也就是包括命名空间本身和using指示的最近作用域)中声明的。
B中的fn()函数使用i并不会产生歧义,虽然使用using指示引入了A中的i,但是那看起来就像是在全局作用域里声明的,B本身声明的i屏蔽了外围作用域(全局作用域)中相同的名字。所以fn()调用的是B中的i,打印2。
还有这里强调了“好像是”(as if),它并不等价于在全局作用域中声明,如果等价于在全局作用域声明了A中的成员,那么可以在全局作用域定义这样的函数:

void fn_()  
{  cout << i << endl;  
}  

如果不是“好像”,而是“实际”时,它应该会打印输出2。而实际上在编译的时候会给出错误:i未声明。要访问命名空间A中的i,还是只能使用A::i访问。
>

注意:为了避免命名冲突,一般不要使用using directives,而使用using declarations。但有一种情况,using directives是有用的,那就是用在它自身的补充文件中(比如文中17.2.1提到的Sales_item.cc and Query.cc)

附:用命名空间成员理解作用域和生命周期
生命周期与作用域是两个不同的概念:生命周期是对象或变量生存的时段,作用域是对象或变量起作用的地方。那么我们如何形象地理解这两个概念呢?举个命名空间成员的小例子:

#include<iostream>
using std::cout;
using std::endl;
namespace blip {int bi = 0;
}
void fir_ip()
{using blip::bi;++bi;cout << "bi = " << bi << endl;
}
void sec_ip()
{using namespace blip;++bi;cout << "bi = " << bi << endl;
}
int main()
{using blip::bi;cout << "blip::bi = " << bi << endl;cout << "fir_ip" << endl;fir_ip();cout << "blip::bi = " << bi << endl;cout << "sec_ip" << endl;sec_ip();cout << "blip::bi = " << bi << endl;return 0;
}

运行结果为:
运算结果
从前面的内容,我们知道函数fir_ip() 中变量bi 作用域为using blip::bifir_ip() 函数结束;函数sec_ip() 中变量bi 为作用域为全局(效果上),而main()bi作用域也只是using blip::bimain() 结束。
如果认为生命周期和作用域是同一概念。那么我想输出结果应该为

blip::bi = 0
fir_ip
bi = 1
blip::bi = 0
sec_ip
bi = 1
blip::bi = 1

然而结果显然不是这样。我认为可以这样解释:

int bi = 0;

一开始就在命名空间blip (全局)中定义,所以blip::bi 生命周期是从定义blip::bi开始一直到程序结束。在fir_ip() 用“别名”bi 对它进行自增,虽然别名bi 的 作用域只在fir_ip() 内,但因为blip::bi 生命周期贯穿整个程序,blip::bi 通过别名bi 自增得到的值并不会随着fir_ip()bi的消亡而重置。

这篇关于学习笔记一:命名空间(namespace)之四:using declarations和using directives的区别的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/892018

相关文章

关于Mybatis和JDBC的使用及区别

《关于Mybatis和JDBC的使用及区别》:本文主要介绍关于Mybatis和JDBC的使用及区别,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、JDBC1.1、流程1.2、优缺点2、MyBATis2.1、执行流程2.2、使用2.3、实现方式1、XML配置文件

重新对Java的类加载器的学习方式

《重新对Java的类加载器的学习方式》:本文主要介绍重新对Java的类加载器的学习方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、介绍1.1、简介1.2、符号引用和直接引用1、符号引用2、直接引用3、符号转直接的过程2、加载流程3、类加载的分类3.1、显示

浅谈Redis Key 命名规范文档

《浅谈RedisKey命名规范文档》本文介绍了Redis键名命名规范,包括命名格式、具体规范、数据类型扩展命名、时间敏感型键名、规范总结以及实际应用示例,感兴趣的可以了解一下... 目录1. 命名格式格式模板:示例:2. 具体规范2.1 小写命名2.2 使用冒号分隔层级2.3 标识符命名3. 数据类型扩展命

exfat和ntfs哪个好? U盘格式化选择NTFS与exFAT的详细区别对比

《exfat和ntfs哪个好?U盘格式化选择NTFS与exFAT的详细区别对比》exFAT和NTFS是两种常见的文件系统,它们各自具有独特的优势和适用场景,以下是关于exFAT和NTFS的详细对比... 无论你是刚入手了内置 SSD 还是便携式移动硬盘或 U 盘,都需要先将它格式化成电脑或设备能够识别的「文

什么是ReFS 文件系统? ntfs和refs的优缺点区别介绍

《什么是ReFS文件系统?ntfs和refs的优缺点区别介绍》最近有用户在Win11Insider的安装界面中发现,可以使用ReFS来格式化硬盘,这是不是意味着,ReFS有望在未来成为W... 数十年以来,Windows 系统一直将 NTFS 作为「内置硬盘」的默认文件系统。不过近些年来,微软还在研发一款名

Java中的工具类命名方法

《Java中的工具类命名方法》:本文主要介绍Java中的工具类究竟如何命名,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录Java中的工具类究竟如何命名?先来几个例子几种命名方式的比较到底如何命名 ?总结Java中的工具类究竟如何命名?先来几个例子JD

Java学习手册之Filter和Listener使用方法

《Java学习手册之Filter和Listener使用方法》:本文主要介绍Java学习手册之Filter和Listener使用方法的相关资料,Filter是一种拦截器,可以在请求到达Servl... 目录一、Filter(过滤器)1. Filter 的工作原理2. Filter 的配置与使用二、Listen

go 指针接收者和值接收者的区别小结

《go指针接收者和值接收者的区别小结》在Go语言中,值接收者和指针接收者是方法定义中的两种接收者类型,本文主要介绍了go指针接收者和值接收者的区别小结,文中通过示例代码介绍的非常详细,需要的朋友们下... 目录go 指针接收者和值接收者的区别易错点辨析go 指针接收者和值接收者的区别指针接收者和值接收者的

售价599元起! 华为路由器X1/Pro发布 配置与区别一览

《售价599元起!华为路由器X1/Pro发布配置与区别一览》华为路由器X1/Pro发布,有朋友留言问华为路由X1和X1Pro怎么选择,关于这个问题,本期图文将对这二款路由器做了期参数对比,大家看... 华为路由 X1 系列已经正式发布并开启预售,将在 4 月 25 日 10:08 正式开售,两款产品分别为华

利用Python快速搭建Markdown笔记发布系统

《利用Python快速搭建Markdown笔记发布系统》这篇文章主要为大家详细介绍了使用Python生态的成熟工具,在30分钟内搭建一个支持Markdown渲染、分类标签、全文搜索的私有化知识发布系统... 目录引言:为什么要自建知识博客一、技术选型:极简主义开发栈二、系统架构设计三、核心代码实现(分步解析