命名空间
文章目录
前言
编译工具:visual studio 2019
语言:C++
命名空间概念引入
当C++工程较大时,程序内存在大量的变量、函数和类,如果这些变量、函数和类都存在于同一个作用域中,一不小心就可能会导致命名冲突。为了解决这个问题,C++引入了命名空间(namespace)这个概念,这样在不同的命名空间中允许存在同名的标识符。有了命名空间我们就可以在程序中有效地避免命名冲突,下面举一个利用命名空间解决命名冲突问题的例子。
例程1:
#include <iostream>
namespace N1 //定义命名空间N1
{
int a = 10;
}
namespace N2 //定义命名空间N2
{
int a = 30;
}
int main()
{
int a = 50;
printf("N1::a = %d\n", N1::a); //输出命名空间N1中的成员变量a
printf("N2::a = %d\n", N2::a); //输出命名空间N2中的成员变量a
printf("a = %d\n",a);//输出主函数中的变量a
return 0;
}
输出结果:
例程1中定义了两个命名空间N1和N2,两个命名空间和主函数中都有a变量,但是并没有产生命名冲突,这就是使用namespace的好处。命名空间其实是对标识符的名称进行本地化,以避免命名冲突或名字污染。下面说一下命名空间的定义方式。
1.1 命名空间定义
例程1中已经展示使用命名空间的场景,下面我们详细说一下定义命名空间的几种方式。
1.1.1 命名空间的一般定义方式
例程2
namespace N3 //定义命名空间N3
{
int a;
int Add(int x, int y)//成员函数
{
return x + y;
}
}
例程2中定义的命名空间N3,称为普通命名空间,C++还有一个自带的标准命名空间std,下文会提到。可以看到,在命名空间中既可以定义变量,也可以定义函数。
1.1.2 命名空间的嵌套定义
例程3
namespace N4
{
int a;
int b;
int Add(int x, int y)
{
return x + y;
}
namespace N5
{
int c;
int d;
int Sub(int x, int y)
{
return x - y;
}
}
}
例程3中定义的两个命名空间N4和N5,其中N4包含N5,这说明N4的作用域比N5的大。
1.1.3 不连续的命名空间
例程4
namespace N6
{
int a;
int Add(int x, int y)
{
return x + y;
}
}
namespace N6
{
int Mul(int left, int right)
{
return left * right;
}
}
命名空间可以定义在几个不同的部分中,即命名空间可以由几个单独定义的部分组成1。换句话说,同一个工程中允许存在多个相同名称的命名空间,编译器最后会将同名的命名空间合为一个。例程3中,两个命名空间会被合成一个命名空间N6,N6中包括变量a、函数Add和函数Mul。
以上讲述三种方式命名空间的定义方式,需要注意的一点是:定义一个命名空间就是定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中,在命名空间之外不能直接调用。
1.2 命名空间使用
前面我们学会了如何定义命名空间,那命名空间中成员该如何使用呢?其实命名空间有三种使用方式。
1.2.1 第一种:利用命名空间标识符及作用域限定符引入命名空间成员
例程5
#include <iostream>
namespace N {
int a = 10;
}
int main()
{
int a = 30;
printf("命名空间N中的变量a = %d\n", N::a); //打印命名空间N中的变量a
printf("main函数中的变量a = %d\n", a); //打印主函数中的变量a
return 0;
}
运行结果:
例程5中,我们想使用变量a,则可以通过命名空间标识符“N”和作用域限定符“::”引入变量a,可以看到命名空间中的成员变量a和主函数中的局部变量a,输出值不同。
1.2.2 第二种:使用using引入命名空间的成员
例程6
#include <iostream>//输入输出流头文件,注意头文件不带后缀.h
namespace N {
int a = 10;
int b = 20;
}
using N::a;// 引入变量a
using N::b;// 引入变量b
int main()
{
int a = 30;
printf("命名空间N中的变量b = %d\n", b); //打印命名空间N中的变量b
printf("main函数中的变量a = %d\n", a); //打印主函数中的变量a
printf("命名空间N中的变量a = %d\n", N::a); //打印命名空间N中的变量a
return 0;
}
运行结果
例程6中,通过using引入b,使用命名空间中成员变量时,不用再添加命名空间标识符“N”和作用域限定符“::”,这样在写程序时会方便不少。另外可以注意到,主函数中的局部变量会屏蔽命名空间中的同名变量(比如a),虽然我们使用using引入a,但如果想使用命名空间中的a,还是需要利用命名空间标识符“N”和作用域限定符“::”。
1.2.3 第三种:使用using namespace 引入命名空间所有成员
例程7
#include <iostream>
namespace N {
int a = 10;
int b = 20;
int Add(int left, int right)
{
return left + right;
}
}
using namespace N;//使用using namespace 引入命名空间成员
int main()
{
printf("a = %d\n", a);
printf("b = %d\n", b);
int c = Add(a, b);
printf("a+b = %d\n", c);
return 0;
}
运行结果:
可以看到使用using namespace可以把命名空间N中的成员全部引入,这样就能直接使用命名空间中的成员变量了。
1.3 标准命名空间std
在1.1节讲到命名空间的定义可以是不连续的,标准命名空间std也不例外,std的定义被分散在多个头文件里2,比如标准输入/输出头文件 <iostream> 中就定义了一个标准命名空间3,<iostream>里面的函数或类被放在std命名空间中,比如cin和cout这两个函数的名字就放在命名空间std里面,如果我们想使用cout函数,那就必须使用std命名空间引入这个函数。在上文中我们已经知道了命名空间的三种使用方式,下面就利用这三种方式使用标准命名空间std。
第一种:命名空间标识符+作用域限定符
#include<iostream>
int main()
{
std::cout << "Hello world!!!\n";
return 0;
}
第二种:使用using引入某个成员(例如引入cout函数)
#include<iostream>
using std::cout;
int main()
{
cout << "Hello world!!!\n";
return 0;
}
第三种:使用using namespace引入所有成员
#include<iostream>
using namespace std;
int main()
{
cout << "Hello world!!!\n";
return 0;
}
以上程序中,展示了使用函数cout的方法。cout存在于头文件<iostream>的标准命名空间std(局部作用域)中,故需要利用标准命名空间引入。
1.4 扩展阅读
早期标准库将所有功能函数都在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应头文件即可。比如在早期,如果我们想使用<iostream.h>中的函数,只需在程序开头加上#include<iostream.h>即可(c++标准已经明确表示不支持这种用法了,所以理论上来说这种用法是非法的,但有些老旧编译器还支持这种用法4),不用考虑命名空间(事实上,那时候还没有命名空间)。
后来,为了使程序更加模块化,最主要的是防止命名冲突,人们创造了命名空间这个概念(1998年)。为了使用命名空间,大佬对<iostream.h>头文件进行改造,改造好的头文件起名为<iostream>,在<iostream>中,定义了标准命名空间std,然后把<iostream.h>的内容封装到了std命名空间中5。这样如果我们想使用cout,必须先添加头文件<iostream>,有了这个头文件,我们还只能访问该文件的全局作用域,要想访问该文件中std命名空间(局部作用域)的内容6,就需要利用命名空间的使用规则处理,上面的程序已经展示了三种使用标准命名空间的方法,诸君请往上看。
1.5 相关问题
看了以上内容,有的人可能要问以下几个问题:
问题:到底是标准命名空间存在于标准库中,还是标准库的东西存在于命名空间中?
回答:C++有很多标准库,可以分为10类7。每个标准库中都会存在一个标准命名空间std,库中的变量、函数、类存在于std中。在第一节命名空间定义中我们已经讲过,编译器最后会把相同名称的命名空间合成为一个。因此无论我们调用了几个标准库,标准命名空间最后都会合并成一个。因此以上两种说法,在不同的角度上看都对。另外细心的看官可能已经发现,在例程1中,使用printf函数时,并没有使用命名空间进行限定,但是程序可以正常运行,这说明printf函数存在于头文件<iostream>的全局作用域中,而像cin、cout等存在于<iostream>的局部作用域中8。因此有的人说标准库的一切都放在了std中应该是不对的吧?顺便说一下,在visual studio 2019编译器中,cstdio和stdio.h这两个标准输入输出头文件里面也有printf函数9。
问题:为什么不把标准库中所有的东西全部放到std当中,这样我们在使用的时候直接添加一句using namespace std;就完事了,何必又多一句#include<iostream>呢?
回答:确实可以把标准库的东西都放在标准命名空间中,但是这又存在了一个新的问题,以前程序猿写的C++代码都依赖于旧的头文件,如果把所有东西放在std中,会导致以前的代码无法运行。所以为了兼容以前的C++代码,人们把标准命名空间分裂,各个部分的代码再重新包装,形成了新的头文件。比如把包含了<iostream.h>头文件内容的标准命名空间部分放到新版头文件<iostream>中。这样#include<iostream.h>相当于#include<iostream>和using namespace std;的组合,如此以前的程序只需要把头文件的.h后缀去掉,再加上一句using namespace std;就能在新标准下运行了。
总结
本文介绍了命名空间的相关概念,说明了为什么要创建命名空间的原因,并举例展示了命名空间的定义和使用的三种方式。文中还提到了标准命名空间std,在扩展阅读中你可以进一步理解命名空间的意义,在相关问题中提出了两个阅读中可能会产生的问题,并给出了相关回答。
以上就是本文的全部内容,文章通过查看网上的资料整理而成,有些部分是我经过思考得来,有可能不对,所以本文只能供大家参考,如果你全部都相信,不如没有看到这篇文章。如您发现错误,还请批评,如有疑问也可在评论中留言。