原创笔记,转载请注明出处!

点击【关注】,关注也是一种美德~


第一, 命名空间的意义

命名空间是ANSIC++引入的可以由用户命名的作用域,用来处理程序中常见的同名冲突。

我认识两位叫“A”的朋友,一位是我同学,一位是我同事,两个人的联系方式在手机中保存的时候需要备注一下“同学A”、“同事A”,在名字前面加上额外的信息加以区分,虽然有时候脑袋会不清醒,打电话会打错……

同样的情况也出现在C++编程中。例如,我自己写一个显示函数show(),在另外一个可用的库中也有一个显示函数show(),二者函数名相同。这样我在调用函数时,编译器就无法判断我是用的是哪一个show()函数。

因此引入命名空间(namespace)这个概念,专门用于解决上面的问题,就像在“A”这个名字前面加上额外的附加信息一样(额外的附加信息…..这句是不是病句),命名空间可以用来作为附加信息来区分不同库中相同名称的函数、类、变量等。本质上,命名空间就是定义了一个范围。

再说几句,我们之前看到的代码都有这条语句:using namespace std;即使用命名空间std,规定该文件中使用的标准库函数都是在标准命名空间std中定义的。

为什么需要将这些函数、类等定义在命名空间中呢?这其实是为了避免变量或函数重名的问题。一个项目往往由多个工程师开发完成,有可能出现全局变量或函数重名的现象,而如果每个人都定义了自己的命名空间就可以解决这个问题,即使重名,只要分属不同的命名空间就不会引起问题。

在 C语言中定义了3个层次的作用域,即文件(编译单元)、函数和复合语句。C++又引入了类作用域,类是出现在文件内的。在不同的作用域中可以定义相同名字的变量,互不于扰,系统能够区别它们。

全局变量的作用域是整个程序,在同一作用域中不应有两个或多个同名的实体(enuty),包括变量、函数和类等。

例:如果在文件中定义了两个类,在这两个类中可以有同名的函数。在引用时,为了区别,应该加上类名作为限定: 
class A //声明A类

{

public:

void funl();//声明A类中的funl函数

private:

int i;

};

void A::funl() //定义A类中的funl函数

{…………}

class B //声明B类

{

public:

void funl(); //B类中也有funl函数

void fun2();

};

void B::funl() //定义B类中的funl函数

{ …………}

这样不会发生混淆。

在文件中可以定义全局变量(global variable),它的作用域是整个程序。如果在文件A中定义了一个变量a   int a=3;

在文件B中可以再定义一个变量a   int a=5;

在分别对文件A和文件B进行编译时不会有问题。但是,如果一个程序包括文件A和文件B,那么在进行连接时,会报告出错,因为在同一个程序中有两个同名的变量,认为是对变量的重复定义。

可以通过extern声明同一程序中的两个文件中的同名变量是同一个变量。如果在文件B中有以下声明: extem int a; 表示文件B中的变量a是在其他文件中已定义的变量。由于有此声明,在程序编译和连接后,文件A的变量a的作用域扩展到了文件B。如果在文件B中不再对a赋值,则在文件B中用以下语句输出的是文件A中变量a的值:cout<<a; //得到a的值为3

第二,命名空间的定义

命名空间的定义使用关键字namespace,后跟命名空间的名称,如下所示:

namespace  A

{

void  Fun1(){...};

void  Fun2(){...};

}

上面的组织形式我们将函数的具体实现和声明放到了一起,有时候我们并不想看到函数的具体实现,只希望能一眼看到的全部都是函数的接口界面。我们可以采用如下的方式将函数的界面和具体实现分开。

namespace  A

{

int a;

double b;

void  Fun1();

void  Fun2();

}

void A::Fun1(){/*...*/}

void A::Fun2(){/*...*/}

1、如果一个函数的定义没有在其对应的命名空间里,必须要使用作用域解析符::来指定函数的命名空间。

2、不可以在命名空间以外定义一个命名空间中不存在的新成员。例如:

void A::Fun3(); //错误,A里并没有Fun3()

3、一个良好的程序应该将程序中的所有实体(变量,类,函数)都放到某个命名空间里。当然除了main()函数之外。

命名空间内部不仅可以声明或定义变量,对于其它能在命名空间以外声明或定义的实体,同样也都能在命名空间内部进行声明或定义,例如变量的声明或定义、函数的声明或定义、typedef等都可以出现在命名空间中。

namespace 是定义命名空间所必须写的关键字,A是用户自己指定的命名空间的名字(可以用任意的合法标识符),在花括号内是声明块,在其中声明的实体称为命名空间成员(namespace member)。现在命名空间成员包括变量a和b,注意二者仍然是全局变量,仅仅是把它们隐藏在指定的命名空间中而已。如果在程序中要使用变量a和b,必须加上命名空间名和作用域分辨符“::”,如A:a,A::b。这种用法称为命名空间限定(qualified),这些名字(如A::a)称为被限定名 (qualified name)。C++中命名空间的作用类似于操作系统中的目录和文件的关系,由于文件很多,不便管理,而且容易重名,于是人们设立若干子目录,把文件分别放到不同的子目录中,不同子目录中的文件可以同名。调用文件时应指出文件路径。

命名空间的作用:是建立一些互相分隔的作用域,把一些全局实体分隔开来。以免产生老师点名叫李华时,3个同名的学生都站起来应答,这就是名字冲突,因为他们无法辨别老师想叫的是哪一个李华,同名者无法互相区分。为了避免同名混淆,学校把3个同名的学生分在3个班。这样,在小班点名叫李华时,只会有一个人应答。也就是说,在该班的范围(即班作用域)内名字是惟一的。如果在全校集合时校长点名,需要在全校范围内找这个学生,就需要考虑作用域问题。如果校长叫李华,全校学生中又会有3人一齐喊“到”,因为在同一作用域中存在3个同名学生。为了在全校范围内区分这3名学生,校长必须在名字前加上班号,如高三甲班的李华,或高三乙班的李华,即加上班名限定。这样就不致产生混淆。

可以根据需要设置许多个命名空间,每个命名空间名代表一个不同的命名空间域,不同的命名空间不能同名。这样,可以把不同的库中的实体放到不同的命名空间中,或者说,用不同的命名空间把不同的实体隐蔽起来。过去我们用的全局变量可以理解为全局命名空间,独立于所有有名的命名空间之外,它是不需要用 namespace声明的,实际上是由系统隐式声明的,存在于每个程序之中。

在声明一个命名空间时,花括号内不仅可以包括变量,而且还可以包括以下类型: 
·变量(可以带有初始化); 
·常量; 
·数(可以是定义或声明); 
·结构体; 
·类; 
·模板; 
·命名空间(在一个命名空间中又定义一个命名空间,即嵌套的命名空间)。

例如 
namespace nsl 
{

const int RATE=0.08; //常量 
double pay; //变量 
double tax() //函数 
{

return a*RATE;


namespace  ns2    //嵌套的命名空间 
{

int age;

}

}

如果想输出命名空间nsl中成员的数据,可以采用下面的方法:

cout<<nsl::RATE<<endl;

cout<<nsl::pay<<endl;

cout<<nsl::tax()<<endl;

cout<<nsl::ns2::age<<endl;  //需要指定外层的和内层的命名空间名

可以看到命名空间的声明方法和使用方法与类差不多。但它们之间有一点差别:在声明类时在右花括号的后面有一分号,而在定义命名空间时,花括号的后面没有分号。

第三,命名空间使用

第一种:命名空间::标识符名称

为了调用带有命名空间的函数或变量,需要在前面加上命名空间的名称,我们看到,使用“命名空间::标识符名称”的方式就可以访问命名空间中的变量或函数了,而且即使是重复命名也可以正确访问。

让我们来看看命名空间如何为变量或函数等实体定义范围:

定义两个命名空间,在使用函数func()时,用first_space::func()和second_space::func()来指明使用的是哪个func函数,cout和endl属于std命名空间。

#include<iostream>

//using namespace std;

//第一个命名空间

namespace first_space

{

void func()

{

std::cout<<"Inside first_space"<<std::endl;

}

}

//第二个命名空间

namespace second_space

{

void func()

{

std::cout<<"Inside second_space"<<std::endl;

}

}

int main()

{

//调用第一个命名空间中的函数

first_space::func();

//调用第二个命名空间中的函数

second_space::func();

system("pause");

return 0;

}

执行结果

Inside first_space

Inside second_space

请按任意键继续. . .

第二种:using指令之声明整个命名空间

可以使用using namespace *;指令,这里的*可以表示任何命名空间,其作用是释放命名空间*中的变量或函数等,使之在被访问时可以不必加“命名空间::”,访问方法与一般的变量或函数无异。这样在使用命名空间中的变量、函数、类等时就不需要在前面加上命名空间的名称,这个指令会告诉编译器,后续的代码将使用指定的命名空间中的名称。

#include<iostream>

//using namespace std;

//第一个命名空间

namespace first_space

{

void func()

{

std::cout<<"Inside first_space"<<std::endl;

}

}

//第二个命名空间

namespace second_space

{

void func()

{

std::cout<<"Inside second_space"<<std::endl;

}

}

using namespace first_space;

int main()

{

//调用第一个命名空间中的函数

func();

system("pause");

return 0;

}

在main函数上面已经用using namespace *;指令释放了first_space命名空间中的函数,因此main函数中func()函数就知道使用哪个命名空间中的func()函数了。

运行结果:

Inside first_space

请按任意键继续. . .

using namespace *;会给我们书写程序带来方便,但也要慎用,如果释放了多个命名空间中的东西后,它们又可能会引起命名冲突。下面演示同时释放first_space和second_space命名空间的情况。

#include <iostream>

using namespace std;

// 命名空间 first_space

namespace first_space

{

char *Url = "Autocodes";

}

// 命名空间 second_space

namespace second_space

{

char *Url = "Autocodes Autocodes";

}

// 释放命名空间 first_space 和 second_space

using namespace first_space;

using namespace second_space;

int main()

{

cout << Url << endl;

system("pause");

return 0;

}

上例中,编译器会提示编译错误,因为它不知道该访问哪个命名空间中的字符串变量Url。这时要想正确访问,还需在Url前面加上命名空间修饰。

而如果main函数中又定义了一个局部变量Url呢?

#include <iostream>

using namespace std;

// 命名空间 first_space

namespace first_space

{

char *Url = "Autocodes";

}

// 命名空间 second_space

namespace second_space

{

char *Url = "Autocodes Autocodes";

}

// 释放命名空间 first_space 和 second_space

using namespace first_space;

using namespace second_space;

int main()

{

char *Url = "Autocodes Autocodes Autocodes";

cout << Url << endl;

system("pause");

return 0;

}

运行后我们发现,Url访问正确,可见,这种情况下,编译器优先访问局部变量。

 

第三种:using指令之声明命名空间特定的项目

using不仅仅可以用于声明整个命名空间,也可以针对命名空间中的一个名称。

using指令也可以用来指定命名空间中的特定项目。例如,如果只打算使用命名空间中的cout,可以使用如下语句:

using std::cout;

随后的代码中,在使用cout时就可以不用加上命名空间名称作为前缀,但是命名空间std中的其他项目仍然需要加上命名空间名称作为前缀,如下所示:

#include <iostream>

using std::cout;

int main ()

{

cout << "std::endl is used with std!" << std::endl;

return 0;

}

using 指令引入的名称遵循正常的范围规则。名称从使用 using 指令开始是可见的,直到该范围结束。此时,在范围以外定义的同名实体是隐藏的。

第四不连续的命名空间

命名空间可以定义在几个不同的部分中,因此命名空间是由几个单独定义的部分组成的。一个命名空间的各个组成部分可以分散在多个文件中。

所以,如果命名空间中的某个组成部分需要请求定义在另一个文件中的名称,则仍然需要声明该名称。下面的命名空间定义可以是定义一个新的命名空间,也可以是为已有的命名空间增加新的元素:

namespace  namespace_name

{

//代码声明

}

第五嵌套的命名空间

命名空间可以嵌套,可以在一个命名空间中定义另一个命名空间,如下所示:

namespace  namespace_name1

{

//代码声明

namespace  namespace_name2

{

//代码声明

}

}

可以使用::运算符来访问嵌套的命名空间中的成员:

//访问namespace_name2中的成员

using  namespace  namespace_name1::namespace_name2;

//访问namespace_name1中的成员

using  namespace  namespace_name1;

在上面的语句中,如果使用的是namespace_name1,那么在该范围内namespace_name2中的元素也是可用的,如下所示:

第六,多重界面

有时候我们同一个命名空间在面向不同的用户时,可能需要提供不同的界面。比如我们有一个命名空间里面定义了关于串口的一些实体。我们给一个开发中的程序提供的接口可能包括:打开串口,设置波特率,设置校验位等。但是面向一个最终用户时,我们可能只需要给他提供一个打开串口接口就够了。这便是使用多重界面的意义。

1、实现多重界面的方法有很多,首先可能想到的是使用不同的命名空间。

namespace A

{

void Fun1();

void Fun2();

void Fun3();

}

namespace A_Interface1

{

using A::Fun1;

}

2、上面界面实现的过程中,A_Interface1和A有着非常强的关联,修改A中的Fun1会使A_Interface1中的Fun1也修改。有时候我们可能不需要这么强的关联性,让A_Interface1中的函数有着一定的可控性,可以使用下面界面实现的方式。

namespace A

{

void Fun1();

void Fun2();

void Fun3();

}

namespace A_Interface1

{

void Fun1()

{

A::Fun1();

}

}

第七,无名命名空间

我们知道不同命名空间的变量名可以重复,这有助于第三方将两个不同人写的代码进行整合。有时候我们并不想我们的某些代码被其他人进行整合,但是也想利用命名空间的优势——可以让变量名重复。这时候使用无名命名空间就很有价值了:第一没有名字,其他地方无法引用进去;第二因为是命名空间它里面的变量可以和其他命名空间中变量的名字重复。

无名命名空间可以在本编译单元(所在文件)处调用,没有这一规则就永远都用不到了。需要注意的是,不同编译单元中的无名命名空间不同。

第八,命名空间的别名

我们在给命名空间取名字的时候,如果太短(比如上面的A)很可能出现冲突。名太长又太麻烦。这时候我们将长名字的命名空间在合适的地方取个别名可能会更好些。格式如下:

namespace A = LongNameNamespaceA;

这种替换的方式和C语言中的宏定义非常相似,因此别名另外一个特别用于的地方就是使代码对命名空间的依赖降低。比如我们有一个大型程序依赖于一个命名空间LibA,现在需要版本升级将所有引用LibA中的代码替换为LibA_Plus.如果使用起别名的方式,我们不用再代码中逐个查找将LibA::替换为LibA_Plus.而是简单的在命名空间的别名定义处稍作修改即可。

namespace A = LibA;

//替换为

namespace A = LibA_Plus;

但也需要注意过多的使用命名空间别名也容易造成混乱。

再如 :

namespace Television     //声明命名空间,名为Television 
{ … }

可以用一个较短而易记的别名代替它。如: 
namespace TV=Television; //别名TV与原名Television等价

也可以说,别名TV指向原名Television,在原来出现Television的位置都可以无条件地用TV来代替。


原创笔记,转载请注明出处!

更多精彩请关注微信公众号:依法编程


C++笔记008:C++对C的扩展——命名空间 namespace基础的更多相关文章

  1. [笔记]记录原开发工作在base命名空间下扩展的属性与方法

    前言 该笔记只是为了记录以前开发使用的方式. 处理命名空间namespace /** * 处理命名空间 * @param {string} 空间名称,可多个 * @return {object} 对象 ...

  2. Java程序猿的JavaScript学习笔记(10—— jQuery-在“类”层面扩展)

    计划按例如以下顺序完毕这篇笔记: Java程序猿的JavaScript学习笔记(1--理念) Java程序猿的JavaScript学习笔记(2--属性复制和继承) Java程序猿的JavaScript ...

  3. 编写Postgres扩展之一:基础

    原文:http://big-elephants.com/2015-10/writing-postgres-extensions-part-i/ 编译:Tacey Wong Postgres提供了广泛的 ...

  4. ASP.Net MVC开发基础学习笔记:二、HtmlHelper与扩展方法

    一.一个功能强大的页面开发辅助类—HtmlHelper初步了解 1.1 有失必有得 在ASP.Net MVC中微软并没有提供类似服务器端控件那种开发方式,毕竟微软的MVC就是传统的请求处理响应的回归. ...

  5. python学习笔记三 深浅copy,扩展数据类型(基础篇)

    深浅copy以及赋值 对于字符串和数字而言,赋值.浅拷贝和深拷贝无意义,因为其永远指向同一个内存地址. import copy n1 = #n1 = 'hahahaha' #赋值n2 = n1#浅co ...

  6. JavaScript学习笔记--ES6学习(四) 字符串的扩展

    ES6对字符串进行了一些扩展,主要表现在对Unicode 大于\uFFFF的字符的处理上. 1. ES6中字符的Unicode表示方法 在ES5中,字符串的Unicode表示方法: \uxxxx . ...

  7. JavaScript学习笔记--ES6学习(五) 数值的扩展

    ES6 对于数值类型 (Number) 进行了一下扩展: 1.对于二进制和八进制提供了新的写法 ES6对于二进制和八进制的数值提供了新的写法,分别用0b (或者0B) 和0o (或者0o) 表示.例如 ...

  8. objective-C学习笔记(十一)类别和扩展

    类别 类别是对外的,外部都可以访问 类别是在没有源代码或者基于某些特定场合的情况下,为一个类增加功能(方法).或者用于给一个特别大的类进行分割. 命名规则:类名+扩展方法,如NSString 可以添加 ...

  9. PHP7 学习笔记(五)安装event扩展(libevent)

    一.描述:有效安排I/O,时间和信号的扩展 使用可用于特定平台的最佳I/O通知机制的事件,是PHP基础设施的libevent端口. 二.下载地址:http://pecl.php.net/package ...

随机推荐

  1. C/C++ OpenCV读取视频与调用摄像头

    原文:http://blog.csdn.net/qq78442761/article/details/54173104 OpenCV通过VideoCapture类,来对视频进行读取,调用摄像头 读取视 ...

  2. layui 设计资源——2.0 版本的 Axure 组件包,产品交互设计利器

    大家好,很久不见,这次为大家分享的是 layui_2.0版本的axure组件包,在去年发布的 layui Axure 1.0 中(见:http://fly.layui.com/jie/9842/ )赢 ...

  3. antd Icon

    引入 : import { Icon } from 'antd'; <Icon type = "home" //图标样式 theme = "filled" ...

  4. linux误删文件导致系统无法启动

    因虚拟机RedHat误删了/etc/inittab文件导致系统无法启动启动系统提示enter runlevel尝试输入0-5都不好使.因为实验环境直接忽略了错误,重新搭建了虚拟机. 如果想尝试修复,可 ...

  5. 转:Recsys2013论文导读

    月中在香港参加recsys2013会议,文章不少,对我有价值的并不算多,再跟目前工作相关的就更少了.这里过滤了几篇我觉得比较有意思的文章,加上了自己的理解,作为导读. A Fast Parallel ...

  6. linux自动备份oracle数据库

    #此脚本只备份数据表,而且为了方便恢复数据是做的单表逐个备份#在写脚本过程中遇到的报错均加入了解决方案的链接(虽然错误代码没有贴出来)#最终将在脚本所在目录生成年月日-时分的目录,目录下为表名.dmp ...

  7. 关于一篇对epoll讲的比较好的一篇文章

    原文地址http://www.cnblogs.com/lojunren/p/3856290.html 前言 I/O多路复用有很多种实现.在linux上,2.4内核前主要是select和poll,自Li ...

  8. 使用combineReducers注意事项

    一.从‘redux’包中引入combineReducers方法: import { combineReducers } from 'redux'; 二.针对state的不同属性写不同的reducer, ...

  9. GCD学习(五) dispatch_barrier_async

    先看段代码 dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISP ...

  10. Pollard_rho 因数分解

    Int64以内Rabin-Miller强伪素数测试和Pollard 因数分解的算法实现 选取随机数\(a\) 随机数\(b\),检查\(gcd(a - b, n)\)是否大于1,若大于1则\(a - ...