一、名称空间
1、引入名称空间的背景
在C++中的名称可以是变量,函数,类以及类的成员。随着项目的增大,名称相互冲突的可能性也在增大。使用多个厂商的类库时,可能导致名称冲突。例如,两个库都定义了名为List,Tree和Node的类,但是定义的方式不兼容。用户可能希望使用一个库的List类,而是用另一个库的Tree类,这种冲突称为名称空间问题。C++标准提供了名称空间工具,以便更好的控制名称的作用域。
2、创建名称空间
可以使用namespace关键字创建名称空间,如下:
namespace Jack {
int age;
string name;
}
3、名称空间特点
3.1、名称空间可以是全局的,也可以位于另一个名称空间中,但是不能位于代码块中
在默认的情况下,在名称空间声明的名称的连接性为外部的。除了用户定义的名称空间外,还存在另一个名称空间–全局名称空间,它对应于文件级声明区域。因此、前面所说的全局变量现在被描述为位于全局名称空间中。下面是一个名称空间的举例,例如:
#include <iostream>
using namespace std;
// 第一个命名空间
namespace first_space{
void func(){
cout << "Inside first_space" << endl;
}
}
// 第二个命名空间
namespace second_space{
void func(){
cout << "Inside second_space" << endl;
}
}
using namespace first_space;
int main ()
{
// 调用第一个命名空间中的函数
func();
return 0;
}
3.2、命名空间可以嵌套
可以在一个命名空间中定义另一个命名空间,例如:
namespace namespace_name1 {
// 代码声明
namespace namespace_name2 {
// 代码声明
}
}
可以通过使用 :: 运算符来访问嵌套的命名空间中的成员
// 访问 namespace_name2 中的成员
using namespace namespace_name1::namespace_name2;
// 访问 namespace:name1 中的成员
using namespace namespace_name1;
3.3、名称空间是开放的,即可以把名称加入到已有的名称空间
这个特性可以理解成名称空间可以定义在几个不同的部分中,可以在几个不同的头文件中声明函数原型,在.ccp文件中提供函数定义。例如:
// my_header1.h
namespace Jack{
void display();
}
// my_header2.h
namespace Jack{
void backtrace();
}
// my_func.cpp
#include "my_header1.h"
#include "my_header2.h"
namespace Jack{
void backtrace() {
// ...
}
void display() {
// ...
}
}
3.4、未命名的名称空间
可以通过省略名称空间的名称来创建未命名的名称空间,例如:
namespace {
int count;
}
这就像后面跟着using指令一样,也就是说,在该名称空间中声明的名称的潜在作用域为:从声明点到该声明区域的末尾。从这个方面来看与全局变量类似。然而,由于这种名称空间没有名称,因此,不能显示的使用using编译指令或using声明来使它在其他位置都可以使用。具体来说,不能在未命名名称空间所属文件之外的其他文件中,使用该名称空间中的名称。这提供了连接性为内部的静态变量的替代品,例如:
namespace {
int count;
}
void main() {
// ...
}
// 上面的声明内部静态变量的方法与下面的方法等价
static int count;
void main() {
// ...
}
4、名称空间的使用
可以通过下面三种方式,访问名称空间的符号,如下:
- 使用标识符的完全限定名称(例如:
std::vector<std::string> vec;
) - 使用单个标识符的
using
声明(例如:using std::string
) - 使用
using
指令(例如:using namespace std;
)导入一个命名空间的所有符号
下面的示例演示了一个命名空间声明和命名空间之外的代码可访问其成员的三种方法,如下:
namespace ContosoData
{
class ObjectManager
{
public:
void DoSomething() {}
};
void Func(ObjectManager) {}
}
使用完全限定名:
ContosoData::ObjectManager mgr;
mgr.DoSomething();
ContosoData::Func(mgr);
使用 using 声明,以将一个标识符引入范围:
using ContosoData::ObjectManager;
ObjectManager mgr;
mgr.DoSomething();
使用 using 指令,以将命名空间中的所有内容引入范围:
using namespace ContosoData;
ObjectManager mgr;
mgr.DoSomething();
Func(mgr);
5、避免将using指令放置在头文件
using 指令可以放置在 .cpp 文件的顶部(在文件范围内),或放置在类或函数定义内。一般情况下,避免将 using 指令放置在头文件 (*.h) 中,因为任何包含该标头的文件都会将命名空间中的所有内容引入范围,这将导致命名冲突和名污染全局命名空间,在头文件中,应始终使用完全限定名。下面举一个例子,来讲解在头文件中放置using指令带来的问题,例如:
main.cpp
依赖A.h
,A.h
依赖B.h
,代码如下:
// B.h
#pragma once
#include <iostream>
#include <string>
namespace jams{
void display(std::string &str);
}
// B.cpp
#include "B.h"
namespace jams {
void display(std::string &str)
{
std::cout << str << std::endl;
}
}
// A.h
#pragma once
#include "B.h"
using namespace jams;
namespace jack {
void output(std::string &str);
}
// A.cpp
#include "A.h"
namespace jack {
void output(std::string &str)
{
display(str);
}
}
// main.cpp
#include <iostream>
#include <string>
#include "A.h"
void display(std::string &str)
{
std::cout << str << std::endl;
}
int main()
{
std::string str = "hello world";
display(str);
system("PAUSE");
return 0;
}
B模块声明了命名空间jams,B模块定义符号display。A模块使用B模块时,在A的头文件直接使用using指令,把B模块所有的符号导进来。main.cpp依赖A模块,同时main.cpp定义了跟B模块同名的函数display,在main函数里面调用display函数时,编译器会报错,如下:
结论:避免将using指令放到头文件,在头文件中,应始终使用完全限定名。