相信哪怕是从没有接触过编程语言的人也可以看出来,C和C++有某些神秘的联系(毕竟从名字来看就显而易见),本人在学习了一段时间的C和C++后,可以给出一个C和C++比较准确的联系:C++以C为基础,并解决了一些C常见的问题(最重要的引入了类的概念,但这并不是这篇文章的重点),可以说是C的升级版,这篇文件介绍的命名空间,就是C++对C的改进之一。

  1.命名空间的价值

如果你有用C语言开发大型项目的经验,那么命名冲突一定是非常常见并且恶心的问题:

#include<stdlib.h>

int rand = 10;

int main()
{
    printf("%d",rand);
    return 0;
}

就比如以上的一段代码,乍一看好像没什么问题,但其实就出现了命名冲突,原因是在<stdlib.h>库中,有一个名为rand的函数,在预处理阶段展开该库后,rand函数同样位于全局,因此与定义变量rand发生了命名冲突。可见,在某些时候,命名冲突问题是比较难找到的,而在大型项目中,动辄几十万行代码,又分为许多不同的文件,当所有文件合并的时候,可能一下就会出现几百个命名冲突 。

                                          

为了解决这个问题,C++就提出命名空间的概念。

2.什么是命名空间

    要理解命名空间,首先要理解域的概念,在C语言中存在两个域:局部域与全局域,在局部域和全局域中定义同名的变量是不会冲突的,编译器会默认先在局部域中查找,再去全局域中查找。基于C中的这种思想,C++对其进行了扩充,加入了命名空间域和类域。

     域本身是一个比较抽象的概念,其作用是:域影响了编译时语法查找一个变量/函数/类型出处(声明或定义)的逻辑,有了域隔离,命名冲突就解决了。命名空间域可以理解为在全局域中划分出了一片区域,在区域内和区域外定义同名的变量不会产生命名冲突。

并且局部域和全局域除了会影响编译查找变量的逻辑,还会影响变量的生命周期,命名空间域和类域不影响变量⽣命周期。

   简单的介绍一下生命周期的概念:

int a = 10;

void fun()
{
    int b = 100;
}

生命周期可以简单的理解为变量的使用时限,在以上代码中变量a位于全局,它的使用时限与项目等同(即项目结束,变量a才会结束),而变量b位于函数域fun中,它在函数fun被调用时被创建,函数调用结束就会被销毁。而命名空间域和类域中的变量本身不存在生命周期的概念(也可以理解成其中的变量的生命周期与项目的存在时间等同)

3.命名空间的具体使用方法

定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接⼀对{}即可,{}中
即为命名空间的成员,命名空间中可以定义函数/变量/类型等。

int a = 100;

namespace t
{
    int a = 10;
    float b = 2.4;

    void fun()
    {}
}

      可以看到,在命名空间t和全局中同时出现了变量a,此时如果访问变量a,是不会出现命名冲突的。要注意的是:命名空间(namespace)本质是定义出⼀个域,这个域跟全局域各自独立,不同的域可以定义同名变量,所以就不存在命名冲突了。 然后很自然的就会提出一个问题:那么怎么确定访问变量的规则呢?

首先先看只有局部域和全局域时的访问规则:先在局部找,再去全局找。

int a = 10;
int main()
{
    int a = 100;
    int b = a;
    return 0;
}

如上方代码,b会被赋值为100,而不是10,编译器优先在局部main函数中查找,找到a的值后就不会再去全局找了,但是如果在main函数中没有变量a,那么b就会被赋值为10。还要注意的是:小范围可以查找大范围的,大范围不能查找小范围。

//1
int b = a;
int main()
{
    int a = 10;
    return 0;
}



//2
int main()
{
    int b = a;
    {
        int a = 10;
    }
    
}

如以上两段代码就都是错误的,在1中大范围的全局变量b无法找到小范围的局部变量a,在2中大范围{}中的b同样无法找到小范围{}中的a。

    在加入命名空间namespace后,为了在查找变量时将命名空间域与全局域区分开来,C++加入了一个新的符号,即::(域作用限定符),使用方法就是在::前方指定作用域,在::后方指定要查找的变量或函数,

//1
int a = 10;
int f = 0;
namespace T
{
    int a = 20;
    int e = 3;
}

int main()
{
    int b = T::a;
    int c = a;
    
    int d = e;
    int g = T::f;
    
    return 0;
}

如上方代码,变量b指定查找了作用域T中的a,因此b被赋值为20,而c没有指定作用域,遵循先在局部找,再去全局找的规则,因此c被赋值为10,要注意的是变量d,其同样没有用::指定作用域,但是在局部和全局中都找不到变量e,此时是不会去命名空间T中查找的,因此在d处的赋值会报错,对于变量g,其指定要去T中查找,但是T中并没有变量f,此时编译器是不会再去全局找到f给g赋值的,因此g的赋值也是错误的。

总结一下查找的规则:在没有指定作用域时,先在局部查找,再去全局查找,不会去命名空间中查找,并且小范围的可以找到大范围的,大范围的找不到小范围的。在指定作用域时,就只会在指定的作用域中进行查找,不会再去全局查找。

注:但是有一个例外:

//1
int a = 10;
int main()
{
    int a = a;
    int b = a;
    return 0;
}


//2
namespace T
{
    int a = 10;
}

int main()
{
    int a = T::a;
    reutrn 0;
}

上方的1代码中,main中第一处给变量a的赋值会报错,第二处给变量b的赋值是正确的,即在局部中不能使用同名的全局变量进行赋值。但是在2代码处,在指定了作用域后,main中给变量a的赋值又是可以的。即在局部中可以使用命名空间中同名的变量进行赋值(本人初学C++不久,还没有搞清楚原因,或许只是某种特性?望解答)

4.命名空间的展开

   在某些时候,你可以确定某个命名空间中的变量绝对不会与全局中的变量产生命名冲突,并且你又懒得指定这个命名空间的作用域,那么你就可以把这个命名空间给展开,具体方法是使用using namespace + 命名空间的名字。

namespace T
{
    int a = 10;
    int b = 20;
    int c = 30;
}

using namespace T;

int main()
{
    printf("%d", a);
    int d = b;
    return 0;
}

如以上代码,命名空间T被展开了,就相当于这个命名空间T被炸掉了,里面的变量都回到了全局,因此在main函数就可以直接对其中的变量进行使用了。(在大型项目中,非常不推荐使用这种方法)

   如果可以确定某个命名空间中的某个变量一定不会产生命名冲突,那么就可以使用部分展开,具体语法是:using 命名空间::具体变量

namespace T
{
    int a = 10;
    int b = 20;
}

using T::a;

int main()
{
    int c = a;
    int d = T::b;
    return 0;
}

如上方代码,命名空间T中的变量a被展开,因此在main中可以直接使用,而b没有被展开,因此在使用时依旧要指定命名空间。

5.命名空间的嵌套

    设想一个场景,你作为一个资深程序猿,被分配到了小组a进行项目开发,这个小组a属于大组A,而在大组A中还存在小组b和小组c,此时,为了防止组内的代码出现命名冲突,并且也为了代码的管理,就可以用到嵌套的命名空间

namespace A
{
    int t = 10;
    namespace a
    {
        int t = 20;
    }

    namespace b
    {
        int t = 30;
    }

    namespace c
    {
        int t = 40;
    }
}

int main()
{
    cout << A::t << endl;
    cout << A::a::t << endl;
    cout << A::b::t << endl;
    return 0;
}

     此时为了访问某个具体的变量,就要用到如main函数中的方法,即:命名空间::命名空间::变量,当然,命名空间还可以继续嵌套下去,但是并不推荐这样做(因为访问起来会非常麻烦)。嵌套的规则非常简单,但是有一点需要注意:命名空间中嵌套的命名空间可以重名,但是命名空间的名称不能与变量的名称相同(因为在查找命名空间中的变量时会出现歧义)。

//1(可以)
namespace a
{
    namespace a
    {}
}


//2(不行)
int a = 10;
namespace a
{
    int a = 10;
    namespace a
    {}
}

     这篇文章到这里就结束了,介绍了一些命名空间的基本知识,本人作为一个萌新程序猿,并且是第一次写博客,可能在文章中会出现一些纰漏或是没有介绍清楚,如果有哪些地方出错了,还请指出,最后希望这篇文章有给读者提供一些帮助。感谢!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐