C++中namespace用法详细介绍

前言

编写程序过程中,名称(name)可以是符号常量变量函数结构枚举对象等等。工程越大,名称互相冲突性的可能性越大。另外使用多个厂商的类库时,也可能导致名称冲突。为了避免,在大规模程序的设计中,以及在程序员使用各种各样的 C++ 库时,这些标识符的命名发生冲突,标准 C++ 引入关键字namespace(命名空间 / 名字空间 / 名称空间),可以更好地控制标识符的作用域。
例如:我们在 C 语言中,通过 static 可以限制名称只在当前编译单元内可见,在 C++ 中我们通过 namespace 来控制对名字的访问。总结如下:

1. namespace 使用基本语法:

  1. namespace 中可定义常量、变量、函数、结构体、枚举、类等
  2. namespace 只能在全局定义。
  3. namespace 支持嵌套定义。
  4. namespace 是开放的,可随时添加新的成员。
  5. namespace 关键字可以为已有空间名字增加别名
  6. 无名命名空间意味着命名空间中的符号只能在本文件中访问,相当于给符号增加了 static 修饰。推荐了解!!!

1.1 namespace 可包含的成员

在这里插入图片描述

1.2 namespace 只能在全局定义

在这里插入图片描述

1.3 namespace 支持嵌套定义

在这里插入图片描述
名字空间 my_space 中可以嵌套定义的子名字空间 my_sub_space

1.4 namespace 是开放的,随时可添加成员

在这里插入图片描述

1.5 namespace 为已有空间名字创建别名

在这里插入图片描述

1.6 namespace 创建匿名命名空间

无名命名
匿名命名空间意味着:命名空间中的符号只能在本文件中访问,相当于给符号增加了 static 修饰:只能在当前文件内访问。

2. 使用 namespace 中符号的方式:

  1. 直接通过 namespace 作用域访问

  2. using 声明指定某个符号在某个作用域下可见

  3. using 编译指令指定名字空间中所有符号在在某个作用域下可见

2.1 直接访问

在这里插入图片描述

2.2 using 声明

using 声明指定某个符号在某个作用域下可见。例如:
在这里插入图片描述

2.3 using 编译指令

using 编译指令指定名字空间中所有符号在在某个作用域下可见。例如:
在这里插入图片描述

补充 - 1:using用法

  1. 利用 using 声明来使用命名空间

    // 使用整个命名空间
    using namespace std;
    
    // 使用该命名空间某几个库函数
    using std::cin;
    using std::cout;
    
  2. 使用 using 起别名
    例:

    // using 类型别名 = 原类型
    using uint = unsigned int;
    uint i = 0;
    

    相当于传统的typedef起别名。

    // 如下两个写法是等价的:
    typedef std::vector<int> intvec;
    using intvec = std::vector<int>;	
    

    这个还不是很明显的优势,再来看一个列子:

    typedef void (*FP) (int, const std::string&);
    

    若不是特别熟悉函数指针与typedef,第一眼还是很难指出FP其实是一个别名,代表着的是一个函数指针,而指向的这个函数返回类型是void,接受参数是int, const std::string&。

    using FP = void (*) (int, const std::string&);
    

    这样就很明显了,一看FP就是一个别名。using的写法把别名的名字强制分离到了左边,而把别名指向的放在了右边,比较清晰,可读性比较好。比如:

    typedef std::string (Foo::* fooMemFnPtr) (const std::string&);
    using fooMemFnPtr = std::string (Foo::*) (const std::string&);
    

    再来看一下模板别名

    template <typename T>
    using Vec = MyVector<T, MyAlloc<T>>;
     
    // usage
    Vec<int> vec;
    

    若使用typedef

    template <typename T>
    typedef MyVector<T, MyAlloc<T>> Vec;
     
    // usage
    Vec<int> vec;
    

    当进行编译的时候,编译器会给出error: a typedef cannot be a template的错误信息。如果我们想要用typedef做到这一点,需要进行包装一层,如:

    template <typename T>
    struct Vec
    {
        typedef MyVector<T, MyAlloc<T>> type;
    };
    
    // usage
    Vec<int>::type vec;
    

    正如你所看到的,这样是非常不漂亮的。而更糟糕的是,如果你想要把这样的类型用在模板类或者进行参数传递的时候,你需要使用typename强制指定这样的成员为类型,而不是说这样的::type是一个静态成员亦或者其它情况可以满足这样的语法,如:

    template <typename T>
    class Widget
    {
    	typename Vec<T>::type vec;
    };
    

    然而,如果是使用using语法的模板别名,你则完全避免了因为::type引起的问题,也就完全不需要typename来指定了:

    template <typename T>
    class Widget
    {
    	Vec<T> vec;
    };
    

    一切都会非常的自然,所以于此,模板起别名时推荐using,而非typedef。

  3. 在子类中引用基类成员
    在子类中对基类成员进行声明,可恢复基类的防控级别。有如下三点规则:
    1. 在基类中的private成员,不能在派生类中任何地方用using声明。
    2. 在基类中的protected成员,可以在派生类中任何地方用using声明。当在public下声明时,在类定义体外部,可以用派生类对象访问该成员,但不能用基类对象访问该成员;当在protected下声明时,该成员可以被继续派生下去;当在private下声明时,对派生类定义体外部来说,该成员是派生类的私有成员。
    3. 在基类中的public成员,可以在派生类中任何地方用using声明。具体声明后的效果同基类中的protected成员。

    具体使用:

    • 当一个派生类私有继承基类时,基类的public和protected数据成员在派生类中是private的形式,如果想让这些继承而来的数据成员作为public或者protected成员,可以用using来重新声明。using声明语句中名字的访问权限由该using声明语句之前的访问说明符决定。
      class Basic {
      public:
          int a;
          int b;
      };
      
      class Bulk : private Basic {
      public:
          using Basic::a;
      protected:
          using Basic::b;
      };
      
    • 因为派生类可以重载继承自基类的成员函数,所以如果派生类希望所有的重载版本对于它都是可见的,那么它就要覆盖所有版本或者一个也不覆盖。但是,有时一个类仅需要覆盖重载部分函数,若覆盖所有函数,就太繁琐了。那么using就派上用场了。只要为重载的成员函数提供一条using声明,这样我们就无需覆盖基类中的每一个版本了。
      class base
      {
      public:
          void test()
          {
              cout << "base::test()" << endl;
          }
          void test(double)
          {
              cout << "base::test(double)" << endl;
          }
          void test(int)
          {
              cout << "base::test(int)" << endl;
          }
      };
      
      class derived : public base
      {
      public:
          void test()
          {
              cout << "derived::test()" << endl;
          }
          // using base::test;
      };
      int main(int argc, char **argv)
      {
          derived *pb = new derived();
          pb->test(2.4);	// 会提示:没有函数derived::test(double)
          return 0;
      }
      

补充 - 2:using声明、using指示及其作用域详解

  1. using 声明:一个using声明一次只能引入一个命名空间成员,从using声明点开始,直到包含该using声明的作用域结尾,声明的名字仅仅在该作用域是可见的,外部作用域中相同的名字被屏蔽,它可以出现在全局作用域,局部作用域或者命名空间作用域中,类中的using声明局限于使用其基类中定义的名字;
    注意:

    1. using声明将名字直接放入出现using声明的作用域,好像using声明是命名空间成员的局部别名一样,这种声明是局部化的,名字仅仅在using声明被包含的作用域有效;
    2. 一定记住using声明是局部的,它涉及到的作用域只有一个,就是从using声明点开始,直到包含该using声明的作用域结尾,别无他处;

    举例:

    //头文件 named_namespace.h  
    #ifndef NAME_17_2_3  
    #define NAME_17_2_3  
    namespace name_17_2_3
    {
        class AA
        {
            AA() {}
        };
        extern int name_17_2_3_fun(); //声明  
        extern int i; //声明  
    }
    #endif  
    
    //实现文件 named_namespace.cpp  
    #include "named_namespace.h"  
    namespace name_17_2_3
    {
        int name_17_2_3_fun() //定义  
        {
            return 998;
        }
        int i; //定义  
    }
    
    // 使用命名空间的文件:
    #include <iostream>  
    #include "named_namespace.h"  
    
    using namespace std;
    using name_17_2_3::i;  //using声明,在全局作用域声明命名空间name_17_2_3的成员变量i,全局可见;  
    using name_17_2_3::name_17_2_3_fun;  //using声明,声明了命名空间name_17_2_3的成员函数name_17_2_3_fun;  
    int i = 200; //全局变量i,将与using声明引入的命名空间的成员变量i冲突  
    int main()
    {
        cout << "i=" << i << endl; //调用哪一个 i 呢?  
        cout << "name_17_2_3_fun():" << name_17_2_3_fun() << endl; //调用命名空间name_17_2_3的成员函数  
        return 0;
    }
    

    在上述代码中的全局作用域中,有两个变量i可见,一个是全局变量 i ,另一个是using声明引入的命名空间name_17_2_3的成员变量i,因为using声明处于全局作用域,这时编译就会出错,因为两个声明冲突了,而成员函数则不冲突因为只有一个声明;
    如果将using name_17_2_3::i;放入main函数,那这个using声明就是局部的,命名空间name_17_2_3的成员变量i就是局部变量,将会屏蔽全局变量i,编译通过,如果想使用全局变量i时,可以使用作用域操作符::i进行限定,例如:::i表示全局变量i

  2. using指示:using指示使得特定命名空间的所有名字可见,从using指示点开始(这点同using声明一致),对名字可以不加限定符使用,直到包含using指示的作用域的末尾;using指示具有将命名空间成员提升到包含命名空间本身和using指示的最近作用域的效果;但最后一句话:“包含命名空间本身的作用域”“using指示的最近作用域” 分别指的是哪个作用域,下边进行详细解释:
    例如:将上述代码的修改如下:

    #include <iostream>  
    #include "named_namespace.h" //将命名空间name_17_2_3插入到当前位置,也就是全局作用域,相当于在此处定义命名空间;  
    
    using namespace std;
    using namespace name_17_2_3;  //using指示,处于全局作用域,;  
    int i = 200; //全局变量;  
    int main()
    {
        //using namespace name_17_2_3;  
        cout << "i=" << i << endl; //调用哪一个 i 呢?编译器不知道应该使用哪个变量 i;  
        return 0;
    }
    

    因为#include指令将头文件包含的命名空间name_17_2_3插入到了main之前,这与在main之前定义一个命名空间是一样的,main之前属于全局作用域,所以命名空间name_17_2_3就被包含在了全局作用域中,那这句话“包含命名空间本身的作用域” 中的 “作用域” 指的就是全局作用域了,那么命名空间name_17_2_3的所有成员自然而然就在全局作用域中可见了,虽然在全局作用域可见,但如果把using指示写在main函数内部,那么只有main函数内可以访问所有成员名字,所以:上述代码中,在全局作用域通过using指示的命名空间name_17_2_3,好像name_17_2_3的所有成员在main之前定义一样,所以在使用变量i时将遇到编译错误,不知道应该使用哪个位置的变量 i
    现在修改以上代码,把using指示写在main函数内部,为了容易试验,就写在了main内部第一个引用变量 i的语句后边:

    #include <iostream>  
    #include "named_namespace.h"  
    
    using namespace std;
    int i = 200; //全局变量;  
    int main()
    {
        cout << "i=" << i << endl; //在此语句之前,只有一个全局变量 i 可见,调用的是全局变量i,即 int i = 200,如果此处要使用name_17_2_3的成员,必须以name_17_2_3进行限定;  
        using namespace name_17_2_3;
        cout << "i=" << i << endl; //调用哪一个 i 呢?经过using指示后,从此处指示点开始,name_17_2_3的所有成员可见,就会有一个main之前定义的全局变量 i, 和一个被using指示引入的变量 i,所以使用 i 会产生歧义;  
        return 0;
    }
    

    从上述例子可以看出,只有在using指示点以后,name_17_2_3的所有成员名字才可见,才可以不带命名空间名字使用成员名字,那么 “using指示的最近作用域”中的“作用域”指的就是:"从using指示点开始,直到包含using指示的作用域的末尾" 这样的一个作用域;
    小结:以上两种情况,不管将using指示写在main内部还是外部,都将把name_17_2_3的所有成员引入全局作用域,因为name_17_2_3#include指令插入到了全局作用域,相当于在全局作用域定义它,虽然如此,只有在using指示点以后才能够以短格式即不带限定符使用这些成员。

  • 7
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

相约~那雨季

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值