C++中namespace用法详细介绍

  • 前言
  • 1. namespace 使用基本语法:
  • 1.1 namespace 可包含的成员
  • 1.2 namespace 只能在全局定义
  • 1.3 namespace 支持嵌套定义
  • 1.4 namespace 是开放的,随时可添加成员
  • 1.5 namespace 为已有空间名字创建别名
  • 1.6 namespace 创建匿名命名空间
  • 2. 使用 namespace 中符号的方式:
  • 2.1 直接访问
  • 2.2 using 声明
  • 2.3 using 编译指令
  • 补充 - 1:using用法
  • 补充 - 2:using声明、using指示及其作用域详解


前言

编写程序过程中,名称(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 可包含的成员

单namespace多少pod namespace有什么用_命名空间

1.2 namespace 只能在全局定义

单namespace多少pod namespace有什么用_单namespace多少pod_02

1.3 namespace 支持嵌套定义

单namespace多少pod namespace有什么用_命名空间_03


名字空间 my_space 中可以嵌套定义的子名字空间 my_sub_space

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

单namespace多少pod namespace有什么用_命名空间_04

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

单namespace多少pod namespace有什么用_命名空间_05

1.6 namespace 创建匿名命名空间

单namespace多少pod namespace有什么用_命名空间_06


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

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

  1. 直接通过 namespace 作用域访问
  2. using 声明指定某个符号在某个作用域下可见
  3. using 编译指令指定名字空间中所有符号在在某个作用域下可见

2.1 直接访问

单namespace多少pod namespace有什么用_作用域_07

2.2 using 声明

using 声明指定某个符号在某个作用域下可见。例如:

单namespace多少pod namespace有什么用_开发语言_08

2.3 using 编译指令

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

单namespace多少pod namespace有什么用_单namespace多少pod_09

补充 - 1:using用法

  1. 利用 using 声明来使用命名空间
// 使用整个命名空间
using namespace std;

// 使用该命名空间某几个库函数
using std::cin;
using std::cout;
  1. 使用 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。

  1. 在子类中引用基类成员
    在子类中对基类成员进行声明,可恢复基类的防控级别。有如下三点规则:
    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

  1. 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指示点以后才能够以短格式即不带限定符使用这些成员。