问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

C++ 命名空间详解:从基础概念到实际应用

创作时间:
作者:
@小白创作中心

C++ 命名空间详解:从基础概念到实际应用

引用
CSDN
1.
https://blog.csdn.net/2401_89733454/article/details/144531502

C++的命名空间(namespace)是C++语言中的一个重要概念,主要用于解决命名冲突问题。本文将详细介绍命名空间的定义、特性以及使用方法,并通过对比C语言的命名冲突问题来说明命名空间的重要性。

一、前言

博主在学习过C语言后,开始学习新的语言——C++。在学习C语言和数据结构基础之后,博主将通过博客的方式,详细讲解C++的各个知识点,与读者一起见证成长。

什么是C++

C语言是结构化和模块化的语言,适合处理较小规模的程序。对于复杂的问题、规模较大的程序,需要高度的抽象和建模时,C语言则不合适。为了解决软件危机,20世纪80年代,计算机界提出了面向对象编程(OOP)思想,支持面向对象的程序设计语言应运而生。

1982年,Bjarne Stroustrup博士在C语言的基础上引入并扩充了面向对象的概念,发明了一种新的程序语言。为了表达该语言与C语言的渊源关系,命名为C++。因此:C++是基于C语言而产生的,它既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行面向对象的程序设计。

二、关键字

C++总计63个关键字,C语言32个关键字,先看个总览,具体每一个对应的是什么在后面的博文中会进行剖析

三、命名空间(重点)

  • 可能诸位在看别人写的C++代码中,在一开始会包这个头文件:
#include<iostream>

这个头文件等价于我们在C语言学习到的 #include<stdio.h>,它是用来跟我们的控制台输入和输出的,这里简要提下,后续详谈。

  • 除了上面这个头文件,还有这样一行代码:
using namespace std;

namespace就是我们要接触C++的第一个关键字,它就是命名空间

  • 作用如下:

在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。

C语言–命名冲突

在正式引入namespace前,再来回顾下C语言的命名冲突问题:

#include<stdio.h>
#include<stdlib.h>
// 命名冲突
// rand 是库中的一个函数
// 自己又定义了一个与其名字相同的一个变量
int rand = 0;
int main()
{
    printf("%d", rand);
    return 0;
}

如上的代码中,头文件我们只用了intclude<stdio.h>,暂无其它。我们定义了全局变量rand,并且代码可以正常编译没有任何错误。但是要知道C语言存在一个库函数正是rand,但是要包上头文件#include<stdlib.h>,包上了这个头文件,再运行试试:

这里很明显发生了命名冲突了,我们定义的全局变量rand和库里的rand函数冲突

想要解决此问题也非常简单,可能有人会说我修改变量名就可以了,确实可以,但并不是长久之计,如若我在不知情的状态下使用该变量超过100次,难道你要一个一个修改吗,这就充分体现了C语言的命名冲突

  • 在C++中,引入的命名空间namespace就很好解决了C语言的命名冲突问题。

C++-命名空间的定义

定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{ }即可,{} 中即为命名空间的成员。

如下:

同一个作用域不能出现两个相同变量,此时的 rand 被关在 n1 的命名空间域里了,跟其它东西进行了隔离。所以在 stdlib.h 头文件展开时并不会发生命名冲突。此时 rand 的打印均是库函数里rand的地址,rand就是一个函数指针,打印的就是地址

此段代码更充分的体现了加上命名空间,不仅可以避免命名冲突,而且还告诉我们,此时再访问变量m、c、f,均是在全局域里访问的,而xzy这个命名空间域里的变量与全局域建立了一道围墙,互不干扰。不过这里c和m依旧是全局变量,命名空间不影响生命周期。

命名空间有三大特性:

  • 1、命名空间可以定义变量,函数,类型
//1. 普通的命名空间
namespace N1 // N1为命名空间的名称
{
    // 命名空间中的内容,既可以定义变量,也可以定义函数,也可以定义类型
    int a; //变量
    int Add(int left, int right) //函数
    {
        return left + right;
    }
    struct ListNode //类型
    {
        int val;
        struct ListNode* next;
    }
}
  • 2、命名空间可以嵌套
//2. 命名空间可以嵌套
namespace N2
{
    int a;
    int b;
    int Add(int left, int right)
    {
        return left + right;
    }
    namespace N3
    {
        int c;
        int d;
        int Sub(int left, int right)
        {
            return left - right;
        }
    }
}
  • 3、同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中
//3. 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。
namespace N1
{
    int Mul(int left, int right)
    {
        return left * right;
    }
}

C++-命名空间的使用

我们都清楚在C语言中,存在局部优先规则,如下:

int a = 0; //全局域
int main()
{
    int a = 1; //局部域
    printf("%d\n", a); // 1 局部优先
    return 0;
}

我们都清楚这里的结果是1,但是如若我非要打印全局域里的a呢?

这里引出域作用限定符 (::),效果如下:

加上了(::) ,此时访问的a,就是全局域,这里是全局域的原因是“::”的前面是空白,如若是空白,那么访问的就是全局域,这么看的话,我在“::”前面换成命名空间域,不就可以访问命名空间域里的内容了吗。其实“::”就是命名空间使用的一种方式。

比如我们定义了如下的命名空间:

namespace n1 
{
    int f = 0;
    int rand = 0;
}

现在该如何访问命名空间域里的内容呢?其实有3种方法:

  1. 加命名空间名称及作用域限定符“ ::
  2. 使用using namespace 命名空间名称全部展开
  3. 使用using将命名空间中成员部分展开
  • 1、加命名空间及作用域限定符“ ::
int main()
{
    printf("%d\n", n1::f); //0
    printf("%d\n", n1::rand);//0
    return 0;
}

为了防止定义相同的变量或类型,我们可以定义多个命名空间来避免

namespace ret
{
    struct ListNode
    {
        int val;
        struct ListNode* next;
    };
}

namespace tmp
{
    struct ListNode
    {
        int val;
        struct ListNode* next;
    };
    struct QueueNode
    {
        int val;
        struct QueueNode* next;
    };
}

当我们要使用它们时,如下:

int main()
{
    struct ret::ListNode* n1 = NULL;
    struct tmp::ListNode* n2 = NULL;
    return 0;
}

针对命名空间的嵌套,如下:

可以这样进行访问:

int main()
{
    struct tx::List::Node* n1; //访问List.h文件中的Node
    struct tx::Queue::Node* n2;//访问Queue.h文件中的Node
}

但是上述访问的方式有点过于麻烦,可不可以省略些重复的呢?比如不写tx::,这里就引出命名空间访问的第二种方法:

  • 2、使用using namespace 命名空间名称全部展开
using namespace tx;

这句话的意思是把 tx 这个命名空间定义的东西放出来,所以我们就可这样访问:

int main()
{
    struct List::Node* n1; //访问List.h文件中的Node
    struct Queue::Node* n2;//访问Queue.h文件中的Node
}

当然,我还可以再拆一层,如下:

using namespace tx;
using namespace List;
int main()
{
    struct Node* n1; //访问List.h文件中的Node
    struct Queue::Node* n2;//访问Queue.h文件中的Node
}

展开时要注意tx和List的顺序不能颠倒

这种访问方式是可以达到简化效果,但是也会存在一定风险:命名空间全部释放又重新回到命名冲突。

所以针对某些特定会出现命名冲突问题的,需要单独讨论:

(重点,后期常用)由此我们得知:全部展开并不好,我们需要按需索取,用什么展开什么,由此引出第三种使用方法

  • 3、使用using将命名空间中成员展开

针对上述代码,我们只放f出来

namespace n1
{
    int f = 0;
    int rand = 0;
}
using n1::f;
int main()
{
    f += 2;
    printf("%d\n", f);
    n1::rand += 2;
    printf("%d\n", n1::rand);
}
  • 学到这,我们来看下C++的标准库命名空间:
#include<iostream>
using namespace std; //std 是封C++库的命名空间
© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号