C++中namespace的使用
概述
C++中使用namespace定义作用域,所有变量都有作用域,以往C中定义一个变量,
主要取决于定义的位置及修饰(如static),这样定义的作用域应用灵活度不高。
C++兼容以往的做法,但更推荐使用namespace来定义作用域。
且部分原来的C语法在C++已经被取消(如使用static定义局部静态变量已经被C++取消了,使用未命名空间取代),
以下就针对namespace的相关语法和使用做详细分析。
本文主要参考:C++ primer
namespace定义
namespace test{ //关键字namesapce + 名字
class test_data {}; //类
int n = 1; //变量(及初始化)
extern float fExtVal; //外部域声明
int fn1(int x); //函数(及定义)
template <> struct hash; //模版
namspace other_name; //其他名空间
}//这个地方不要;
//名空间定义包括两部分:关键字namespace和名字,名字后面是{},包括声明和定义。
//名空间包含的内容主要包括如上.
名空间实质
1:名空间最基本的作用还是定义作用域
2:名空间中的每个名字都必须表示该空间的唯一实体,
不同名空间作用域不同,所以可以使用同名成员。
3:名空间中成员可以被名空间中其他成员直接访问
4;名空间可以不连续,如namespace test{},如test存在,就是添加新成员,
如不存在,就是定义一个新的名空间,这样就可以将名空间独立接口和具体实现组成一个名空间。
名空间的使用注意细节
1:不用把#include放在名空间中,因为这会把头文件中的所有名字定义为该名空间内部成员。
2:程序中某些实体只能定义一次:非内联函数,静态数据成员,变量等
3:全局命名空间:以隐式方式声明,且所有程序有存在,名字被隐式添加到全局命名空间中,
因为全局作用域是隐式的,所以使用这样的形式 ::member_name
嵌套名空间
namespace test{
namespace test-1{
class cTest1{};
int a;
}
namespace test-2{
class cTest1{};
class cTest3 : public cTest1 {};
//.....
}
}
//如上,test分隔为两个,内层名空间隐藏外层名空间同名成员,如上的cTest1,
//嵌套的名空间只能使用内层的名字,外层的名空间要访问,必须添加名字限定符,如:test:test-1:cTest1
内联名空间
//不同处:内联名空间名字可直接被外层名空间直接使用,无需前缀
//文件intest.h
inline namespace InTestName{ //inline必须在名空间第一次出现的地方定义,后续可以不用
}
....
namespace InTestName{ //隐式内联
class Quary{};
//
}
//文件comtest.h
namespace ComTestName{ //非内联空间
class Quary{};
class other{};
//
}
//现假设一个名空间Maintest拥有两个
namespace Maintest{
#include "intest.h"
#include "comtest.h"
}
//因为InTestName是内联的,可以Maintest:Quary直接访问InTestName的成员,
//而如果访问ComTestName,必须要:Maintest:ComTestName:Quary
未命名空间
// namespace后没有名字,直接{}
// 拥有静态生命周期
// 可以在一个文件中不连续,但不能跨文件
// 成员可以直接使用(因为不知道名空间没有名字),也不能用作用域运算符
int i;
namespace{
int i; //二义性
}
// 也可以嵌套
namespace local{
namespace{
int i;
}
}
//这是可以的,直接方位i就是全局的,要名空间的方式: local:i = 123;
名空间的声明与指示using
//using declaration(声明):语句一次引入名空间的一个成员,可以出现在全局/局部作用域,
//名空间作用域及类作用域,类作用域中,只能指向基类成员。
//using directive(指示):一个名空间,可出现全局/局部作用域和名空间,但不能在类作用域,
//使得某个特定名空间所有名字可见,之后就无需添加前缀了。
//using指示具备将名空间提升到包含名空间本身和using指示的最近的作用域的能力
namespace A{
int i,j;
}
void f()
{
using namespace A; //把A的名空间注入到全局作用域中
cout << i*j << endl;
}
//另外一个示例
namespace ntest{
int i=1,j=10,k=100;
}
int j = 20;
void main()
{
using namespace ntest;
++i; //ntest::i设定为2;
++j; //二义性,是全局的,还是ntest不明确
++::j; //全局的,::j = 21
++ntest::j; //
int k=200; //
++k; //
}
//综上,使用using指示可能导致二义性错误,此类错误在使用冲突名字才发现,这是延后检测,
//意味着可能在特定库使用很久后才爆发冲突。
//故使用using申明效果更好,当日,using指示也不是无用,在名空间本身的实现文件中,使用using还是非常好的
成员查找顺序
namespace A{
int i;
int k;
class c1{
public:
c1():i(0),j(0){}; //正确初始化c1类成员i,j
int f1() {return k;}; //返回A:k
int f2() {return h;}; //错误:h未定义
int f3();
private:
int i; //隐藏了A::i
int j;
};
int h = i; //用A::i初始化
}
int A:c1:f3() {return h;} //正确,发回A::h
成员查找范围
std::string s;
std::cin >> s;
//以上的>>实际对应操作符: operate>>(std::cin, s) , 此处是如何调用到了呢
随意重载的隐患
//应用和标准库的冲突大部分是无意的,如move接口,因此,最好使用带限定语的完整版本,如:
std::move // 而不是move
候选函数集
//上述,通过函数的实参所属的名空间,可以找到该函数名空间。这对于确定函数候选集有影响,
//我们将在每个实参类(及实参的基类)所属的名空间搜索候选函数,
//这个空间的所有与被调用函数同名的函数都添加到候选集中,如下例
namespace NS{
class ctest{};
void display(const ctest& ) {};
}
class test_item : public NS::ctest {};
int main()
{
test_item item;
display(item);
return 0;
}
//如上,display函数从哪里来呢?
//首先查找本地(main函数内及本文件),之后实参查item,item属于test_item,item就从test_item及
//基类中查找,同时,NS中的声明的display也添加到候选集中。
重载与using
//using声明,声明的是一个名字,不是一个特定函数,如下:
//当书写声明时,所有版本的函数都引入了当前作用域,故库作者如果选择性发布部分同名函数接口,
//可能导致外部调用异常(外部调用一个未显式提供的接口,结果也能用了)
//注意:如果using引入的函数和已有的同名且形参一样,using将引发错误
//using指示,将名空间成员提升到外层作用域中
namespace lib_us{
extern void print(int);
extern void print(double);
}
//普通声明
void print(const std::string &);
using namespace lib_us; //此时print的候选集包括了如上的3个print
void foo(int a)
{
print("test ok !!");//调用print(const std::string &)
print(a); //调用print(int)
}
//注意:如果此时namespace中的print版本和外部一样,并不出错,使用时指定名空间还是当前作用域就可以
跨越多个namespace函数集
//如果使用多个using指示,则每个名空间的名字都成为候选函数集的一部分,如:
namespace aa{
int print(int);
}
namespace bb{
double print(double);
}
using namespace aa;
using namespace bb;
long double print(long double);
int main()
{
print(1); //调用aa:print
print(1.1); //调用bb:print
return 1;
}