博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
P7-c++内存模型和名称空间-03名称空间
阅读量:4048 次
发布时间:2019-05-25

本文共 8843 字,大约阅读时间需要 29 分钟。

文章目录

1. 为什么会有名称空间?

在C++中,名称可以是变量、函数、结构、枚举、类以及类和结构的成员。当随着项目的增大,名称相互冲突的可能性也将增加。使用多个厂商的类库时,可能导致名称冲突。例如,两个库可能都定义了名为List、Tree和Node的类,但定义的方式不兼容。用户可能希望使用一个库的List类,而使用另一个库的Tree类。这种冲突被称为名称空间问题

C++标准提供了名称空间工具,以便更好地控制名称的作用域。经过了一段时间后,编译器才支持名称空间,但现在这种支持很普遍。

2. 传统的C++名称空间

介绍C++中新增的名称空间特性之前,先复习一下C++中已有的名称空间属性,并介绍一些术语,让读者熟悉名称空间的概念。

第一个需要知道的术语是声明区域(declaration region)。声明区域是可以在其中进行声明的区域。
例如:

  • 可以在函数外面声明全局变量,对于这种变量,其声明区域为其声明所在的文件。
  • 对于在函数中声明的变量,其声明区域为其声明所在的代码块。
    在这里插入图片描述

第二个需要知道的术语是潜在作用域(potential scope)。变量的潜在作用域从声明点开始,到其声明区域的结尾。因此潜在作用域比声明区域小,这是由于变量必须定义后才能使用。

在这里插入图片描述

然而,变量并非在其潜在作用域内的任何位置都是可见的。例如,它可能被另一个在嵌套声明区域中声明的同名变量隐藏。例如,在函数中声明的局部变量(对于这种变量,声明区域为整个函数)将隐藏在同一个文件中声明的全局变量(对于这种变量,声明区域为整个文件)。变量对程序而言可见的范围被称为作用域(scope),前面正是以这种方式使用该术语的。图9.5和图9.6对术语声明区域、潜在作用域和作用域进行了说明。

C++关于全局变量和局部变量的规则定义了一种名称空间层次。每个声明区域都可以声明名称,这些名称独立于在其他声明区域中声明的名称。在一个函数中声明的局部变量不会与在另一个函数中声明的局部变量发生冲突。

3 新的名称空间特性

C++新增了这样一种功能,即通过定义一种新的声明区域来创建命名的名称空间,这样做的目的之一是提供一个声明名称的区域。一个名称空间中的名称不会与另外一个名称空间的相同名称发生冲突,同时允许程序的其他部分使用该名称空间的东西。

例如,下面的代码使用新的关键字namespace创建了两个名称空间: Jack 和 Jill
为了调用带有命名空间的函数或变量,需要在前面加上命名空间的名称,如下所示:

namespace Jack {
double pail; // variable declaration void fetch(); // function prototype int pal; // variable declaration struct well {
...); // structure declaration}namespace Jill {
double bucket (double n) {
...} // function definition double fetch; // variable declaration int pal; // variable declaration struct Hill ( ...); // structure declaration}

名称空间可以是全局的,也可以位于另一个名称空间中,但不能位于代码块中。因此,在默认情况下,在名称空间中声明的名称的链接性为外部的(除非它引用了常量)。

除了用户定义的名称空间外,还存在另一个名称空间——全局名称空间(global namespace)。它对应于文件级声明区域,因此前面所说的全局变量现在被描述为位于全局名称空间中。

任何名称空间中的名称都不会与其他名称空间中的名称发生冲突。因此,Jack中的fetch可以与Jill中的fetch共存,Jill中的Hill可以与外部Hill共存。名称空间中的声明和定义规则同全局声明和定义规则相同。

名称空间是开放的(open),即可以把名称加入到已有的名称空间中。例如,下面这条语句将名称goose添加到Jill中已有的名称列表中:

namespace Jill {
char * goose(const char *);}

同样,原来的Jack名称空间为fetch()函数提供了原型。可以在该文件后面(或另外一个文件中)再次使用Jack名称空间来提供该函数的代码:

namespace Jack {
void fetch() {
... }}

当然,需要有一种方法来访问给定名称空间中的名称。最简单的方法是,通过作用域解析运算符::,使用名称空间来限定该名称:

Jack::pail = 12.34; // use a variableJill::Hill mole;	// create a type Hill structureJack::fetch(); // use a function

未被装饰的名称(如 pail)称为未限定的名称(unqualified name);包含名称空间的名称(如Jack:pail) 称为限定的名称(qualified name)。

1. 一个简单演示命名空间的例子

namespace_demo.cpp

#include 
/* author:梦悦foundation 公众号:梦悦foundation 可以在公众号获得源码和详细的图文笔记*/using namespace std;void func(){
cout << "func()" << endl;}namespace first_space {
void func() {
cout << "first_space::func()" << endl; }}namespace second_space {
void func() {
cout << "second_space::func()" << endl; }}int main(int argc, char *argv[]) {
cout << "---------------开始--->公众号:梦悦foundation---------------" << endl; func(); first_space::func(); second_space::func(); cout << "---------------结束--->公众号:梦悦foundation---------------" << endl; return 0;}

程序的运行结果!通过结果可以看出来,各个命名空间的同名函数是互相不影响的!

---------------开始--->公众号:梦悦foundation---------------func()first_space::func()second_space::func()---------------结束--->公众号:梦悦foundation---------------

4.using声明和 using编译指令

我们并不希望每次使用名称时都对它进行限定,因此C++提供了两种机制(using声明和using编译指令)来简化对名称空间中名称的使用。

using声明使特定的标识符可用,
using编译指令使整个名称空间可用。
using声明由被限定的名称和它前面的关键字 using组成:

// a using declarationusing Ji11::fetch;

using声明将特定的名称添加到它所属的声明区域中。例如main()中的using声明Jill::fetch 将fetch添到main()定义的声明区域中。完成该声明后,便可以使用名称fetch代替Jill::fetch。下面的代码段说明这几点:

namespace01.cpp

#include 
/* author:梦悦foundation 公众号:梦悦foundation 可以在公众号获得源码和详细的图文笔记*/using namespace std;namespace Jill {
double bucket(double n) {
}; double fetch; struct Hil1 {
};}char fetch;int main(int argc, char *argv[]) {
cout << "---------------开始--->公众号:梦悦foundation---------------" << endl; using Jill::fetch; // put fetch into local namespace double fetch; // Error! Already have a local fetch// read a value into Jill::fetch cin >> fetch; // read a value into global fetch cin >>::fetch; //全局变量 fetch cout << "Jill::fetch:" << fetch << ", ::fetch:" << ::fetch << endl; cout << "---------------结束--->公众号:梦悦foundation---------------" << endl; return 0;}

由于using声明将名称添加到局部声明区域中,因此这个示例避如果有另一个局部变量也命名为fetch,就会报错。此外,和其他局部变量一样,fetch也将覆盖同名的全局变量。

运行结果:

meng-yue@ubuntu:~/MengYue/c++/memory_model_namespace/02$ g++ -o namespace01 namespace01.cppnamespace01.cpp: In function ‘int main(int, char**)’:namespace01.cpp:20:9: error: redeclaration of ‘double fetch’  double fetch; // Error! Already have a local fetch// read a value into Jill::fetch         ^~~~~namespace01.cpp:11:9: note: previous declaration ‘double Jill::fetch’  double fetch;         ^~~~~meng-yue@ubuntu:~/MengYue/c++/memory_model_namespace/02$

在函数的外面使用using声明时,将把名称添加到全局名称空间中:

void other();namespace Jill {
double bucket(double n) {
...} double fetch; struct Hi11 {
...};} using Jill::fetch; // put fetch into global namespaceint main()int main(){
cin >> fetch; // read a value into Jill::fetch other();}void other(){
cout << fetch; // display Jill::fetch}

using声明使一个名称可用,而 using编译指令使所有的名称都可用。

using编译指令由名称空间名和它前面的关键字using namespace组成,它使名称空间中的所有名称都可用,而不需要使用作用域解析运算符::

using namespace Jack; // make all the names in Jack available

在全局声明区域中使用using编译指令,将使该名称空间的名称全局可用。这种情况已出现过多次:

#include 
// places names in namespace stdusing namespace std;// make names available globally

在函数中使用using编译指令,将使其中的名称在该函数中可用,下面是一个例子:

int main(){
using namespace jack;// make names available in main()}

有关using编译指令和using声明,需要记住的一点是,它们增加了名称冲突的可能性。也就是说,如果有名称空间jack和jill,并在代码中使用作用域解析运算符,则不会存在二义性:

jack::pal=3;jill::pal =10;

变量jack::pal和jill::pal是不同的标识符,表示不同的内存单元。然而,如果使用using声明,情况将发生变化:

using jack::pal;using jill::pal;pal=4; // which one? now have a conflict

事实上,编译器不允许您同时使用上述两个using声明,因为这将导致二义性。

5.using编译指令和 using声明之比较

namespace02.cpp

#include 
/* author:梦悦foundation 公众号:梦悦foundation 可以在公众号获得源码和详细的图文笔记*/using namespace std;namespace Jill{
double bucket(double n){
}; double fetch; struct Hill {
};}char fetch; // global namespaceint main() {
cout << "---------------开始--->公众号:梦悦foundation---------------" << endl; using namespace Jill; // import all namespace names Hill Thrill; // create a type Jill::Hill structure double water = bucket(2); // use Jill::bucket(); double fetch; // not an error; hides Jill::fetch cin >> fetch; // read a value into the local fetch cin >>::fetch; // read a value into global fetch cin >>Jill::fetch; // read a value into Jill::fetch cout << "---------------结束--->公众号:梦悦foundation---------------" << endl;}int foom(){
Hill top;// ERROR Jill::Hill crest; // valid}

在main()中,名称Jill::fetch被放在局部名称空间中,但其作用域不是局部的,因此不会覆盖全局的fetch。然而,局部声明的fetch将隐藏Jill::fetch和全局fetch。然而,如果使用作用域解析运算符,则后两个fetch变量都是可用的。读者应将这个示例与前面使用using声明的示例进行比较。

在这里插入图片描述

需要指出的另一点是,虽然函数中的using编译指令将名称空间的名称视为在函数之外声明的,但它不会使得该文件中的其他函数能够使用这些名称。因此,在前一个例子中,foom()函数不能使用未限定的标识符Hill。

总结

注意:假设名称空间声明区域定义了相同的名称。

如果试图使用using声明将名称空间的名称导入该声明区域,则这两个名称会发生冲突,从而出错。

如果使用 using编译指令将该名称空间的名称导入该声明区域,则局部版本将隐藏名称空间版本。

一般说来,使用using声明比使用using编译指令更安全,这是由于它只导入指定的名称。

如果该名称与局部名称发生冲突,编译器将发出指示。
using编译指令导入所有名称,包括可能并不需要的名称。
如果与局部名称发生冲突,则局部名称将覆盖名称空间版本,而编译器并不会发出警告。另外,名称空间的开放性意味着名称空间的名称可能分散在多个地方,这使得难以准确知道添加了哪些名称。

  1. using 编译指令把所有名称都导入进来,方便,而且不会与本地的局部变量发生冲突。
  2. using 声明比较麻烦,用到一个导入一个,但是安全性比较高!

6. 名称空间的嵌套和名称空间的别名

1 嵌套名称空间

可以将名称空间进行嵌套

namespace elements{
namespace fire {
int flame; } float water;}namespace myth{
using Jill::fetch; using namespace elements; using std::cout;}

namespace fire 是包含在 namespace elements当中的。

这里,flame指的是element::fire::flame
同样,可以使用下面的using编译指令使内部的名称可用:
using namespace elements::fire;
另外,也可以在名称空间中使用using编译指令和 using声明,如下所示:

namespace myth {
using Jill::fetch; using namespace elements; using std::cout; using std::cin;}

假设要访问Jill::fetch。由于Jill::fetch现在位于名称空间myth(在这里,它被叫做fetch)中,因此可以这样访问它:

std::cin >> myth::fetch;

当然,由于它也位于Jill名称空间中,因此仍然可以称作Jill::fetch

Jill::fetch:

std::cout << Jill::fetch; // display value read into myth::fetch

如果没有与之冲突的局部变量,则也可以这样做:

using namespace myth;cin >> fetch;

现在考虑将using编译指令用于myth名称空间的情况。

using编译指令是可传递的。如果A op B且B op C,则A op C,则说操作op是可传递的。例如 > 运算符是可传递的(也就是说,如果A>B且B>C,则 A > C)。
在这个情况下,下面的语句将导入名称空间myth和elements:
using namespace myth;
这条编译指令与下面两条编译指令等价:

using namespace myth;using namespace elements;

2. 名称空间的别名

可以给名称空间创建别名。例如,假设有下面的名称空间:

namespace my_very_favorite_things {
...);

则可以使用下面的语句让 mvft成为my_very_favorite_things的别名:

namespace mvft = my_very_favorite_things;
可以使用这种技术来简化对嵌套名称空间的使用:
namespace MEF = myth::elements::fire;
using MEF::flame;

3.未命名的名称空间

可以通过省略名称空间的名称来创建未命名的名称空间:

// unnamed namespacenamespace {
int ice; int bandycoot;}

这就像后面跟着using编译指令一样,也就是说,在该名称空间中声明的名称的潜在作用域为:从声明点到明区域末尾。

从这个方面看,它们与全局变量相似。然而,由于这种名称空间没有名称,因此不能显式地使用using 编译指令或using声明来使它在其他位置都可用。具体地说,不能在未命名名称空间所属文件之外的其他中,使用该名称空间中的名称。这提供了链接性为内部的静态变量的替代品。例如,假设有这样的代码:
在这里插入图片描述

7. 去哪获取笔记和详细的资料

代码资料路径

在这里插入图片描述

1 微信公众号,梦悦foundation

在这里插入图片描述

2 公众号里点击IT资源,有链接

3 后台回复 c++资料

4 加up主微信,进学习交流群,大家一起带你飞!

在这里插入图片描述

8. 获取到的笔记怎么使用

1 蚂蚁笔记

2 印象笔记

3 html格式文件

4 pdf文件

在这里插入图片描述

转载地址:http://xhyci.baihongyu.com/

你可能感兴趣的文章
linux irqdebug
查看>>
git 常用命令
查看>>
linux位操作API
查看>>
uboot.lds文件分析
查看>>
uboot start.s文件分析
查看>>
没有路由器的情况下,开发板,虚拟机Ubuntu,win10主机,三者也可以ping通
查看>>
本地服务方式搭建etcd集群
查看>>
安装k8s Master高可用集群
查看>>
忽略图片透明区域的事件(Flex)
查看>>
忽略图片透明区域的事件(Flex)
查看>>
AS3 Flex基础知识100条
查看>>
Flex动态获取flash资源库文件
查看>>
flex4 中创建自定义弹出窗口
查看>>
01Java基础语法-16. while循环结构
查看>>
01Java基础语法-18. 各种循环语句的区别和应用场景
查看>>
01Java基础语法-19. 循环跳转控制语句
查看>>
Django框架全面讲解 -- Form
查看>>
socket,accept函数解析
查看>>
今日互联网关注(写在清明节后):每天都有值得关注的大变化
查看>>
”舍得“大法:把自己的优点当缺点倒出去
查看>>