学习笔记一:命名空间(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 namespace
    void manip()
    {

         // using directive - names in blip "added" to global scope
         using 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 16
         int 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的消亡而重置。

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值