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

相关文章

JAVA覆盖和重写的区别及说明

《JAVA覆盖和重写的区别及说明》非静态方法的覆盖即重写,具有多态性;静态方法无法被覆盖,但可被重写(仅通过类名调用),二者区别在于绑定时机与引用类型关联性... 目录Java覆盖和重写的区别经常听到两种话认真读完上面两份代码JAVA覆盖和重写的区别经常听到两种话1.覆盖=重写。2.静态方法可andro

C++中全局变量和局部变量的区别

《C++中全局变量和局部变量的区别》本文主要介绍了C++中全局变量和局部变量的区别,全局变量和局部变量在作用域和生命周期上有显著的区别,下面就来介绍一下,感兴趣的可以了解一下... 目录一、全局变量定义生命周期存储位置代码示例输出二、局部变量定义生命周期存储位置代码示例输出三、全局变量和局部变量的区别作用域

MyBatis中$与#的区别解析

《MyBatis中$与#的区别解析》文章浏览阅读314次,点赞4次,收藏6次。MyBatis使用#{}作为参数占位符时,会创建预处理语句(PreparedStatement),并将参数值作为预处理语句... 目录一、介绍二、sql注入风险实例一、介绍#(井号):MyBATis使用#{}作为参数占位符时,会

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期

Javaee多线程之进程和线程之间的区别和联系(最新整理)

《Javaee多线程之进程和线程之间的区别和联系(最新整理)》进程是资源分配单位,线程是调度执行单位,共享资源更高效,创建线程五种方式:继承Thread、Runnable接口、匿名类、lambda,r... 目录进程和线程进程线程进程和线程的区别创建线程的五种写法继承Thread,重写run实现Runnab

C++中NULL与nullptr的区别小结

《C++中NULL与nullptr的区别小结》本文介绍了C++编程中NULL与nullptr的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编... 目录C++98空值——NULLC++11空值——nullptr区别对比示例 C++98空值——NUL

Ubuntu如何分配​​未使用的空间

《Ubuntu如何分配​​未使用的空间》Ubuntu磁盘空间不足,实际未分配空间8.2G因LVM卷组名称格式差异(双破折号误写)导致无法扩展,确认正确卷组名后,使用lvextend和resize2fs... 目录1:原因2:操作3:报错5:解决问题:确认卷组名称​6:再次操作7:验证扩展是否成功8:问题已解

Conda与Python venv虚拟环境的区别与使用方法详解

《Conda与Pythonvenv虚拟环境的区别与使用方法详解》随着Python社区的成长,虚拟环境的概念和技术也在不断发展,:本文主要介绍Conda与Pythonvenv虚拟环境的区别与使用... 目录前言一、Conda 与 python venv 的核心区别1. Conda 的特点2. Python v

Go语言中make和new的区别及说明

《Go语言中make和new的区别及说明》:本文主要介绍Go语言中make和new的区别及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1 概述2 new 函数2.1 功能2.2 语法2.3 初始化案例3 make 函数3.1 功能3.2 语法3.3 初始化

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

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