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 使用基本语法:
- namespace 中可定义常量、变量、函数、结构体、枚举、类等
- namespace 只能在全局定义。
- namespace 支持嵌套定义。
- namespace 是开放的,可随时添加新的成员。
- namespace 关键字可以为已有空间名字增加别名
- 无名命名空间意味着命名空间中的符号只能在本文件中访问,相当于给符号增加了 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 中符号的方式:
- 直接通过 namespace 作用域访问
- using 声明指定某个符号在某个作用域下可见
- using 编译指令指定名字空间中所有符号在在某个作用域下可见
2.1 直接访问
2.2 using 声明
using 声明指定某个符号在某个作用域下可见。例如:
2.3 using 编译指令
using 编译指令指定名字空间中所有符号在在某个作用域下可见。例如:
补充 - 1:using用法
- 利用 using 声明来使用命名空间
// 使用整个命名空间
using namespace std;
// 使用该命名空间某几个库函数
using std::cin;
using std::cout;
- 使用 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. 在基类中的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指示及其作用域详解
- using 声明:一个using声明一次只能引入一个命名空间成员,从using声明点开始,直到包含该using声明的作用域结尾,声明的名字仅仅在该作用域是可见的,外部作用域中相同的名字被屏蔽,它可以出现在全局作用域,局部作用域或者命名空间作用域中,类中的using声明局限于使用其基类中定义的名字;
注意:
- using声明将名字直接放入出现using声明的作用域,好像using声明是命名空间成员的局部别名一样,这种声明是局部化的,名字仅仅在using声明被包含的作用域有效;
- 一定记住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
;
- 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
指示点以后才能够以短格式即不带限定符使用这些成员。