思维导图
1. 命名空间含义
2. 命名空间作用
为防止名字冲突提供了更加可控的机制,命名空间分割了全局命名空间,其中每个命名空间是一个作用域,通过在某个命名空间中定义库的名字,库的作者(以及用户)可以避免全局名字固有对的限制。
3. 命名空间定义
3.1 定义
格式:关键字(namespece)+命名空间名称+{…}
花括号中包含一些声明和定义,主要包括:类、变量(及其初始化操作)、函数(及其定义)、模板和其他命名空间。
namespace TestName
{
class Data
{
/*......*/
};
class Query
{
/*......*/
};
} // 命名空间结束后无分号
3.2 定义位置
3.3 性质
1)命名空间作用域后面无须分号
namespace TestName
{
/*......*/
} // 命名空间结束后无分号
2)每个命名空间都是一个作用域
- 同一命名空间中的名字必须唯一,不同命名空间中的名字可以相同;
- 同一命名空间中的名字可以被该命名空间内的其他成员直接访问,也可以被这些成员内嵌作用域中的任何单位访问;
- 一个命名空间之外的代码访问命名空间内的成员时,必须明确指出命名空间以及成员名。
```cpp
include
namespace SpaceTest {
class NameSpaceTest { public: NameSpaceTest(); ~NameSpaceTest();
public: std::string m_pubName;
private: std::string m_name; // 该名称在NameSpaceTest中 <2> }; // NameSpaceTest
std::string m_name; // 该名称在SpaceTest中 <1>
/ 以下结构体名称与上面类名相同,编译时会报错误/ //struct NameSpaceTest //{ //public: // NameSpaceTest(); // ~NameSpaceTest(); //};
} // SpaceTest
namespace SpaceTestOther {
/不同命名空间中,名字可以相同,此处与SpaceTest中NameSpaceTest相同/ class NameSpaceTest { public: NameSpaceTest(); ~NameSpaceTest();
void setName()
{
// 在SpaceTest命名空间之外使用其成员
// 使用SpaceTest中的m_name <1>
m_name = SpaceTest::m_name;
// 使用SpaceTest中的类NameSpaceTest中的成员变量
SpaceTest::NameSpaceTest nameSpaceTest;
nameSpaceTest.m_pubName = "jack";
m_pubName = nameSpaceTest.m_pubName;
}
private: std::string m_name; std::string m_pubName; }; // NameSpaceTest
} // SpaceTestOther
3)命名空间可以是不连续的
- 命名空间可以定在几个不同的部分,如 namespace nsp {// 相关声明},可能是定义一个名为nsp的新命名空间,也可能为已经存在的命名空间添加一些新成员;
- 可以将几个独立的接口和实现文件组成一个命名空间。
4)命名空间中的某些实体只能定义一次(如:非内联函数、静态数据成员、变量等)
```cpp
// 以下将NameSpaceTest类的接口和实现组成一个SpaceTest命名空间
/*--------------------namespacetest.h----------------------*/
#include <string>
namespace SpaceTest // 定义一个名为SpaceTest的新命名空间
{
class NameSpaceTest
{
public:
NameSpaceTest();
~NameSpaceTest();
public:
std::string m_pubName;
void setName();
// 如果此处定义setName函数,在namespacetest.cpp中也有定义setName函数,编译时会报出错
//void setName()
//{
// m_pubName = "pubName";
// m_name = "name";
// m_otherName = "otherName";
//}
private:
std::string m_name;
std::string m_otherName;
}; // NameSpaceTest
/*--------------------namespacetest.cpp----------------------*/
#include "namespace.h"
namespace SpaceTest // 为NameSpaceTest添加新成员
{
NameSpaceTest::NameSpaceTest()
{
}
NameSpaceTest::~NameSpaceTest()
{
}
// 命名空间中的某些实体(此处为非内联函数)
void setName()
{
m_pubName = "pubName";
m_name = "name";
m_otherName = "otherName";
}
}; // NameSpaceTest
5)模板特例化
- 模板特例化必须定义在原始模板所属的命名空间中。
```cpp
// 必须将模板特例化声明为std成员
namespace std {
template <> struct hash
; }
// 在std中添加了模板特例化的声明后,就可以在命名空间std的外部定义它了
template <> struct std::hash
建议:定义多个类型不相关的命名空间应该使用单独的文件分别表示每个类型(或关联类型构成的集合)。
<a name="65NwK"></a>
# 4. 命名空间分类
1)全局命名空间<br />全局作用域中定义的名字(即:在所有类、函数及命名空间之外定义的名字)也就是定义在**全局命名空间(global namespace)**中,全局命名空间以隐式的方式声明,并且在所有程序中都存在。全局作用域中定义的名字被隐式地添加到全局命名空间中。<br />全局作用域是隐式的,没有名字,其形式如`::member`。<br />2)嵌套的命名空间<br />定义在其他命名空间的命名空间。<br />内层命名空间声明的名字将隐藏外层命名空间声明的同名成员,在嵌套的命名空间定义的名字只在内层命名空间中有效,外层命名空间中的代码要想访问它必须在名字前添加限定符。<br />3)内联命名空间<br />内联命名空间中的名字可以被外层命名空间直接使用。<br />定义内联命名空间的方式为在关键字**namespace**前添加关键字**inline**。<br />关键字**inline**必须出现在命名空间第一次定义的地方,后续再打开命名空间的时候可以写,也可以不写。<br />4)未命名的命名空间<br />未命名的命名空间是指关键字**namespece**后紧跟**花括号**括起来的一系列声明语句。<br />未命名的命名空间中定义的变量拥有静态生命周期:在第一次使用前创建,直到程序结束才销毁。<br />一个未命名的命名空间可以在某个给定的文件内不连续,但是不能跨越多个文件,头文件中定义的未命名的命名空间将在每个包含了该头文件的文件中对应不同实体。<br />未命名的命名空间中的名字可以直接使用,同样的,不能对未命名的命名空间的成员使用域运算符。<br />未命名的命名空间中定义的名字的作用域与该命名空间所在的作用域相同,一个未命名的命名空间也能嵌套在其他命名空间中,未命名的命名空间中的成员可以通过外层命名空间的名字访问。<br />注意:内联命名空间在C++11中引入。
```cpp
/*--------------------primer.h----------------------*/
#include <string>
// 内联命名空间
inline namespace PrimerName
{
std::string primerName;
}
// 未命名的命名空间
namespace
{
int nullPrimerVal = 10;
}
int add(int a, int b);
std::string add(std::string str1, std::string str2);
/*--------------------primer.cpp----------------------*/
#include "primer.h"
// 未命名的命名空间,与primer.h中的未命名的命名空间为不同的实体
namespace
{
int nullNum = 5;
}
int add(int a, int b)
{
// 未命名的命名空间中的名字可以直接使用
int val = nullNum + nullPrimerVal;
return a + b + nullNum;
}
// 未命名的命名空间可以在某个给定的文件内不连续
namespace
{
std::string nullName = "nullName";
}
std::string add(std::string str1, std::string str2)
{
return str1 + str2 + nullName;
}
/*--------------------main.cpp----------------------*/
#include <string>
namespace PrimerPluse
{
#include "primer.h"
}
// 此处类似于
//namespace PrimerPluse
//{
// inline namespace PrimerName
// {
// std::string primerName;
// }
//}
namespace PrimerPluse
{
// 关键字inline必须出现在命名空间第一次定义的地方,后续再打开命名空间的时候可以写,也可以不写
/*inline */namespace PrimerName
{
int primerVal;
}
}
// 全局作用域中定义的名字被隐式的添加到全局命名空间
// 全局命名空间中的变量
int member = 5;
namespace MainSpace
{
int val;
std::string name;
// 嵌套命名空间
namespace SubSpace
{
int val; // 内层命名空间声明的名字将隐藏外层命名空间声明的同名成员
std::string subName;
}
float score;
std::string mainName = SubSpace::subName; // 外层命名空间中的代码要想访问它必须在名字前添加限定符
//std::string mainName = subName; // 此句编译错误
}
namespace MainSpaceNull
{
// 嵌套的未命名的命名空间
namespace
{
int nullMainVal = 5;
}
}
int main(int argc, char *argv[])
{
int member = 12; // 函数内名字隐藏外层命名空间声明的同名成员
int mainVal = member; // mainVal = 12
int globalMember = ::member; // globalMember = 5 使用全局命名空间中的成员
PrimerPluse::primerName = "name"; // 内联命名空间中的名字可以被外层命名空间直接使用
PrimerPluse::primerVal = 12; // // 内联命名空间中的名字可以被外层命名空间直接使用
int val = MainSpaceNull::nullMainVal; // 未命名的命名空间中的成员可以通过外层命名空间的名字访问
return 0;
}
5. 命名空间使用
1)命名空间别名
命名空间别名声明格式为:namespace 别名 = 原命名空间名;。
命名空间别名的声明需要在原命名空间定义之后。
命名空间别名也可以指向一个嵌套的命名空间。
2)using声明
using声明的格式为:using 命名空间::成员;。
using声明作用域有效范围为声明的位置到声明所在作用域结束为止。
using声明可以在全局作用域、局部作用域、命名空间作用域以及类作用域中使用,在类的作用域中时,声明语句只能指向基类成员。
3)using指示
using指示格式为:using namespace 命名空间名;。
using指示作用域有效范围为声明的位置到声明所在作用域结束为止。
using指示开可以在全局作用域、局部作用域和命名空间作用域中使用,不能再类的作用域中使用。
namespace Blip
{
int i = 5;
int j = 6;
int k = 7;
namespace SubBlip
{
int m = 0;
}
}
int j = 0; // 正确:Blip的j隐藏在命名空间中
// 使用命名空间别名
namespace Lip = Blip;
namespace SubLip = Blip::SubBlip; // 命名空间别名可以指向一个嵌套的命名空间
void useAlias()
{
++Lip::i; // Blip::i = 6
++SubLip::m; // Blip::SubBlip::m = 1
}
void useStatement()
{
using Blip::i;
using Blip::j;
++i; // Blip::i = 6
++j; // Blip::j = 7
++::j; // 全局的j = 1
++Blip::j; // Blip::j = 8
int k = 10; // 局部变量k隐藏Blip::k
++k; // 局部变量k = 11
}
void useInstruction()
{
// 使用using指示,Blip中的名字被“添加”到全局作用域中
using namespace Blip;
++i; // Blip::i = 6
//++j; // 二义性错误,是全局的j还是Blip的j?
++::j; // 全局的j = 1
++Blip::j; // Blip::j = 7
int k = 10; // 局部变量k隐藏Blip::k
++k; // 局部变量k = 11
}
6. 命名空间查找规则
1)内部名字查找
查找规则:由内向外一次查找每个外层作用域。外层作用域也可能是一个或多个嵌套的命名空间,直到最外层的全局命名空间查找过程终止。
2)内部类查找
查找规则:当成员函数使用某个名字时,首先在该成员中进行查找,然后在类中查找(包括基类),接着在外层作用域中查找。
3)实参相关的查找与类类型形参
当函数传递一个类类型的对象时,除了在常规的作用域查找外还会查找实参类所属的命名空间。
4)友元声明与实参相关的查找
在一个命名空间中,存在一个类,该类中包含友元函数的申明,而友元函数的形参中包含该类,则该友元对该类本身可见。
说明:当类声明了一个友元时,该友元声明并没有使得友元本身可见(详细参考友元章节)。
namespace A
{
int i;
namespace B
{
int i; // B中隐藏了A::i
int j;
int f1()
{
int j; // j是f1()中的局部变量,隐藏了A::B::j
return i; // 返回A::i
}
} // 命名空间B结束,此后B中定义的名字不可见
int f2()
{
//return j; // 错误:j没有被定义
return 0;
}
int j = i; // 用A::i进行初始化
int k;
class C1
{
// 两个友元,在友元声明之外没有其他声明
// 这些函数隐式地成为命名空间A的成员
friend void ff1(); // 除非另有声明,否则不会被找到
friend void ff2(const C1 &); // 根据实参相关的查找规则可以被找到
public:
C1() :i(0), j(0) {} // 初始化C1::i,C1::j
int f1() { return k; } // 返回A::k
//int f2() { return h; } // 错误:h未定义
int f3(); // 注意此处只是声明
private:
int i; // C1中隐藏了A::i
int j;
};
int h = i; // 用A::i进行初始化
// 限定符A::C1::f3指出了查找类作用域和命名空间作用域的相反次序,函数f3作用域->类C1作用域->命名空间A作用域+f3定义的作用域
int A::C1::f3() { return h; } // 正确:返回A::h
}
void A::ff2(const A::C1 &c1)
{
int val = c1.i; // 调用类C1的私有成员
}
int main(int argc, char *argv[])
{
// 当函数传递一个类类型的对象时,除了在常规的作用域查找外还会查找实参类所属的命名空间
// 查找顺序:当前作用域(main函数中)->外层作用域(全局作用域)-> 形参类所属作用域(cin和s对应类的命名空间)
std::string s;
operator >> (std::cin, s);
A::C1 c1;
//ff1(); // 错误:A::ff1未声明
ff2(c1); // 正确:通过在A::C1中的友元声明找到A::ff2
return 0;
}
7. 重载与命名空间
1)重载与using声明
using声明语句声明的是一个名字,而非一个特定的函数。
一个using声明囊括了重载函数的所有版本以确保不违反命名空间的接口,using声明所在的作用域中已经有一个函数与新引入的函数同名且形参列表相同,编译错误。
一个using声明引入的函数将重载该声明语句所属作用域中已有的其他同名函数。
2)重载与using指示
using指示将命名空间的成员提升到外层作用域中,如果命名空间的某个函数与该命名空间所属作用域的函数同名,则命名空间的函数将被添加到重载集合中。
namespace A
{
extern void print(int);
extern void print(float);
extern void print(const std::string &);
}
namespace B
{
extern void show(int);
extern void show(float);
extern void show(const std::string &);
}
namespace C
{
extern void print(double);
extern void show(int);
}
// 普通的声明
void print(const std::string &);
void show(const std::string &);
using namespace A;
using namespace C;
// print调用此时的候选函数包括
// A的print(int)
// A的print(float)
// C的print(double)
// 显示声明的print(const std::string &)
void printVal(int val)
{
// 在使用using指示时,如果当前作用域中存在与命名空间中引入的相同名字且参数相同的函数,在使用时要指明调用
::print("value"); // 调用全局函数print(const std::string &)
A::print("value"); // 调用A::print(const std::string &)
print(val); // 调用A::print(int)
}
void showVal(int val)
{
using B::show; // 在局部作用域中使用using声明
// show调用此时的候选函数包括
// B的show(int)
// B的show(float)
// B的show(const std::string &))
show("value"); // 调用B::show(const std::string &),隐藏全局函数show(const std::string &)
show(val); // 调用B::show(int)
}
//using B::show; // 错误:using声明所在的作用域中已经有一个函数与新引入的函数同名且形参列表相同(void show(const std::string &))
void showValB_C(int val)
{
// B和C中虽然都有同名且形参列表相同的函数(void show(int)),在使用时要指明调用
using B::show; // 在局部作用域中使用using声明
using C::show; // 在局部作用域中使用using声明
B::show(2); // 调用B::show(int)
C::show(3); // 调用C::show(int)
}
// 函数定义没列出来
参考:
1)《C++ Primer 中文版(第5版)》。