学习笔记一:命名空间(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

相关文章

MySQL之InnoDB存储页的独立表空间解读

《MySQL之InnoDB存储页的独立表空间解读》:本文主要介绍MySQL之InnoDB存储页的独立表空间,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、背景2、独立表空间【1】表空间大小【2】区【3】组【4】段【5】区的类型【6】XDES Entry区结构【

深度解析Spring Boot拦截器Interceptor与过滤器Filter的区别与实战指南

《深度解析SpringBoot拦截器Interceptor与过滤器Filter的区别与实战指南》本文深度解析SpringBoot中拦截器与过滤器的区别,涵盖执行顺序、依赖关系、异常处理等核心差异,并... 目录Spring Boot拦截器(Interceptor)与过滤器(Filter)深度解析:区别、实现

CSS实现元素撑满剩余空间的五种方法

《CSS实现元素撑满剩余空间的五种方法》在日常开发中,我们经常需要让某个元素占据容器的剩余空间,本文将介绍5种不同的方法来实现这个需求,并分析各种方法的优缺点,感兴趣的朋友一起看看吧... css实现元素撑满剩余空间的5种方法 在日常开发中,我们经常需要让某个元素占据容器的剩余空间。这是一个常见的布局需求

Before和BeforeClass的区别及说明

《Before和BeforeClass的区别及说明》:本文主要介绍Before和BeforeClass的区别及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Before和BeforeClass的区别一个简单的例子当运行这个测试类时总结Before和Befor

Go学习记录之runtime包深入解析

《Go学习记录之runtime包深入解析》Go语言runtime包管理运行时环境,涵盖goroutine调度、内存分配、垃圾回收、类型信息等核心功能,:本文主要介绍Go学习记录之runtime包的... 目录前言:一、runtime包内容学习1、作用:① Goroutine和并发控制:② 垃圾回收:③ 栈和

Android学习总结之Java和kotlin区别超详细分析

《Android学习总结之Java和kotlin区别超详细分析》Java和Kotlin都是用于Android开发的编程语言,它们各自具有独特的特点和优势,:本文主要介绍Android学习总结之Ja... 目录一、空安全机制真题 1:Kotlin 如何解决 Java 的 NullPointerExceptio

MySQL启动报错:InnoDB表空间丢失问题及解决方法

《MySQL启动报错:InnoDB表空间丢失问题及解决方法》在启动MySQL时,遇到了InnoDB:Tablespace5975wasnotfound,该错误表明MySQL在启动过程中无法找到指定的s... 目录mysql 启动报错:InnoDB 表空间丢失问题及解决方法错误分析解决方案1. 启用 inno

Linux中的more 和 less区别对比分析

《Linux中的more和less区别对比分析》在Linux/Unix系统中,more和less都是用于分页查看文本文件的命令,但less是more的增强版,功能更强大,:本文主要介绍Linu... 目录1. 基础功能对比2. 常用操作对比less 的操作3. 实际使用示例4. 为什么推荐 less?5.

Java 关键字transient与注解@Transient的区别用途解析

《Java关键字transient与注解@Transient的区别用途解析》在Java中,transient是一个关键字,用于声明一个字段不会被序列化,这篇文章给大家介绍了Java关键字transi... 在Java中,transient 是一个关键字,用于声明一个字段不会被序列化。当一个对象被序列化时,被

在Java中基于Geotools对PostGIS数据库的空间查询实践教程

《在Java中基于Geotools对PostGIS数据库的空间查询实践教程》本文将深入探讨这一实践,从连接配置到复杂空间查询操作,包括点查询、区域范围查询以及空间关系判断等,全方位展示如何在Java环... 目录前言一、相关技术背景介绍1、评价对象AOI2、数据处理流程二、对AOI空间范围查询实践1、空间查