命名空间的定义、使用和存在的意义

命名空间



前言

编译工具: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 例程1运行结果

  例程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;
}

运行结果:
在这里插入图片描述

图2 例程5运行结果

   例程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;
}

运行结果
在这里插入图片描述

图3 例程6运行结果

   例程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;
}

运行结果:
在这里插入图片描述

图4 例程7运行结果

   可以看到使用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,在扩展阅读中你可以进一步理解命名空间的意义,在相关问题中提出了两个阅读中可能会产生的问题,并给出了相关回答。

  以上就是本文的全部内容,文章通过查看网上的资料整理而成,有些部分是我经过思考得来,有可能不对,所以本文只能供大家参考,如果你全部都相信,不如没有看到这篇文章。如您发现错误,还请批评,如有疑问也可在评论中留言。


参考资料


  1. C++ 命名空间 | 菜鸟教程 (runoob.com). ↩︎

  2. 请问:名字空间std是在哪个文件里定义的?-CSDN论坛. ↩︎

  3. using namespace std中的std定义在哪个文件_loudyten的专栏-CSDN博客. ↩︎

  4. (笔记)什么是命名空间 为什么C++头文件有的要加.h有的不用加.h_*的专栏-CSDN博客. ↩︎

  5. C++std命名空间详解_rioalian的博客-CSDN博客. ↩︎

  6. 由string头文件和std命名空间说开去:为什么引入头文件的同时还需要使用namespace? - 知乎 (zhihu.com). ↩︎

  7. C++标准库和标准模板库_迂者-贺利坚的专栏-CSDN博客_c++模板库. ↩︎

  8. 为什么在C++中使用printf时不使用命名空间std不会报错 - 沃锋问答-赞臣社区旗下知识互动平台 (zanchen.net). ↩︎

  9. c++中printf函数在哪几个头文件中有?_百度知道 (baidu.com). ↩︎

评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值