目 录
- 1 命名空间
- 1.1 命名空间定义
- 1.2 命名空间使用
1 命名空间
在C/C++中,变量、函数和类都是大量存在的,这些变量、函数、类的名称将都存在于全局作用域中,可能会导致很多冲突。 使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。
C语言中命名冲突示例:
如上图所示:在左部分代码中,我们没有包含头文件stdlib.h,运行后正常输出了全局变量 rand
的值;而在右部分代码中,我们包含了头文件stdlib.h,此时再运行程序,报错说明 rand
发生了重定义,之前的 rand
是被定义为一个函数。显然,这是因为 rand
是stdlib.h中声明的库函数,当我们在包含了stdlib.h头文件的同时又声明了与函数同名的 rand
全局变量时就会发生命名冲突,进而导致程序无法正常运行。
命名冲突通常发生于以下几种情况:
- 我们自己定义的变量、函数、类与库里的名字发生命名冲突。
- 在项目组中,多个人之间定义的名字发生冲突。可能单个人运行自己的代码时是正常的,可当将多人的代码进行合并后,运行就发生了命名冲突。
命名冲突问题是C语言的一个缺陷,在C语言中没有办法很好解决这个问题。因此,C++在C语言的基础上增加了命名空间的概念以解决这个问题。
在认识命名空间之前,我们先了解一个相对于C语言C++中新增的操作符:域操作符(域作用符) ::
(双冒号),当全局变量与某个函数中的局部变量重名时,即可用 ::
来区分,表示要使用哪个域中的同名变量。如下示例:当函数中定义有与全局变量同名的变量时,默认情况下,调用函数会优先使用函数中的同名局部变量;如果想要在该函数中使用同名全局变量,则需要用域作用符 ::
来修饰变量(格式如下图代码中所示),当域作用符左边无其它名称时(如命名空间),则默认表示全局域,使用全局变量。
1.1 命名空间定义
定义一个命名空间实际上就是定义一个域,命名空间中的所有内容都局限于该空间中。命名空间的定义只影响使用,不影响生命周期。定义命名空间需要使用
namespace
关键字。后跟空间名字,然后接一对{}
即可,{}
中即为命名空间中的成员。
定义方式:
- 正常命名空间定义
namespace (域名或空间名)
{
{\kern 8pt} //命名空间中的成员:可以定义变量、函数、类。如下例:
{\kern 8pt} int rand = 10; //全局变量
{\kern 8pt} //函数
{\kern 8pt} int Add(int num1, int num2)
{\kern 8pt} {
{\kern 16pt} retrun num1 + num2;
{\kern 8pt} }
{\kern 8pt} //结构体类型
{\kern 8pt} struct Node
{\kern 8pt} {
{\kern 16pt} struct Node* next;
{\kern 16pt} int val;
{\kern 8pt} };
}- 命名空间嵌套定义
//test.cpp
namespace N1
{
{\kern 8pt} int a;
{\kern 8pt} int b;
{\kern 8pt} int Add(int num1, int num2)
{\kern 8pt} {
{\kern 16pt} return num1 + num2;
{\kern 8pt} }
{\kern 8pt} namespace N2
{\kern 8pt} {
{\kern 16pt} int c;
{\kern 16pt} int d;
{\kern 16pt} int Sub(int num1, int num2)
{\kern 16pt} {
{\kern 20pt} return num1 - num2;
{\kern 16pt} }
{\kern 8pt} }
}- 同一个工程中,允许存在多个相同名称的命名空间,编译器最后会将同名的命名空间合并为一个空间。(如第二点中的test.cpp中的
N1
空间与下面的test.h中的N1
空间,假设test.cpp与test.h属于同一个工程,则最后者这两个同名空间将会被合并成一个空间)
namespace N1
{
{\kern 8pt} int Mul(int num1, int num2)
{\kern 8pt} {
{\kern 16pt} return num1 * num2;
{\kern 8pt} }
}
1.2 命名空间使用
那命名空间中的成员该如何使用呢?如下图所示,我们定义一个命名空间 N1
时,当不加任何说明,直接使用命名空间中的成员时,程序发生编译报错:说明使用的该成员是未声明的标识符。
事实上,当我们使用某个变量、类型等时,编译器会默认先在局部域查找,如果找不到再到全局域查找,如果全局也没有就将发生报错。默认情况下编译器不会到命名空间中进行查找,因此,如果我们想要使用某个命名空间中的成员时,应当对该成员所属空间进行说明,使得编译器能够到指定的域中找到它。
命名空间有以下三种使用方式:
- 在标识符前加其所在命名空间名称及作用域限定符
::
int main(){
{\kern 8pt} struct N1::Node node;
{\kern 8pt} printf(“%d\n”, N1::a);
{\kern 8pt} return 0;
}
需要注意的是:空间名及域作用符须直接加在标识符前,使用如N1::struct Node node;
的写法是错误的;如果使用的成员在嵌套定义的命名空间中,则应以如N::N1::a
嵌套的方式进行说明(其中N1空间嵌套在N空间中,a是N1空间中的成员)。
- 使用关键字
using
将命名空间中的某个成员引入(相当于将命名空间中部分成员展开到全局域中)
using N1::b;
int main(){
{\kern 8pt} printf(“%d\n”, N1::a);//没有展开的成员仍需使用空间名及域作用符进行说明
{\kern 8pt} printf(“%d\n”, b);
{\kern 8pt} return 0;
}
- 使用
using namespace
将命名空间名称引入(相当于将命名空间中的成员全部展开到全局域中,或者说告知编译器将会使用到该空间中的成员,使得编译器会到该空间中进行查找)
using namespace N1;
int main(){
{\kern 8pt} printf(“%d\n”, a);
{\kern 8pt} printf(“%d\n”, b);
{\kern 8pt} Add(10, 20);
{\kern 8pt} return 0;
}
在C++的代码中,我们常看到这样一句:using namespace std
,这是由于为了避免我们自己定义的某个标识符与标准库中的标识符重名,C++中标准库中的东西都被放到了名为 std
的命名空间中,如果我们仅是包含了相关头文件而没有将标准库对应空间中的内容展开,那依旧无法使用标准库中的成员,这句代码则表示将标准库中的东西全部展开到全局域中。
值得注意的是: 早期标准库将所有功能在全局域中实现,声明在 .h
后缀的头文件中,使用时只需包含对应的头文件即可,后来将其实现在 std
命名空间下,为了和C头文件区分,也为了正确使用命名空间,规定C++头文件不带 .h
;旧编译器还支持 <iostream.h>
格式,后续编译器已不支持,因此推荐使用 < iostream >+std 的方式。
std命名空间使用惯例:
- 在日常练习中,建议直接使用
using namespace std
进行全局展开即可,这样比较方便使用。 - 使用
using namespace std
进行全局展开,标准库就全部暴露出来了,如果我们定义跟库重名的类型、对象、函数,就会存在命名冲突问题,该问题在日常练习中很少出现,但在实际项目开发中代码较多、规模较大,就很容易出现这个问题。所以建议在实际项目开发中使用如std::cout
这样使用时指定命名空间或using std::cout
展开常用的库对象、类型等的方式。
以上是我对C++中命名空间相关的一些学习记录总结,如有错误,希望大家帮忙指正,也欢迎大家给予建议和讨论,谢谢!