• C++的命名空间namespace详解及特殊情况分析


    历史来源

    最开始的C++ 头文件仍然以.h为后缀,它们所包含的类、函数、宏等都是全局范围的。后来 C++ 引入了命名空间,计划重新编写库将类、函数、宏等都统一纳入一个命名空间std
    但改版后的c++库致使旧c++库无法使用,在当时产生了巨大的反响,于是,C++ 开发人员想了一个好办法,保留原来的库和头文件,它们在 C++ 中可以继续使用,然后再把原来的库复制一份,把类、函数、宏等纳入命名空间 std 下,就成了新版 C++ 标准库。这样共存在了两份功能相似的库,使用了老式 C++ 的程序可以继续使用原来的库,新开发的可以使用新版的 C++ 库。
    为了避免头文件重名,新版 C++ 库也对头文件的命名做了调整,去掉了后缀.h而对于原来C语言的头文件,也采用同样的方法,但在每个名字前还要添加一个c字母,stdio.h变成了cstdio,stdlib.h变成了cstdlib

    所以,命名空间(namespace)std是C++标准库(Standard Library)中定义的,其中一系列标准库提供的类、函数和对象都在c++标准库中,准确来说在标准库的std当中。

    意义

    提到命名空间就不得不提到C语言了,由于C++是C语言的扩展,所以C++的开发人员就注意到C语言中命名冲突,例如一个团队的多个人员参与了一个文件管理系统的开发,他们都定义了一个全局变量 a,用来指明当前打开的文件,将他们的代码整合在一起编译时,很明显编译器会提示 重复定义(Redefinition)错误。

    有一个常用的经典案例

    #include
    #include
    int rand = 0;//全局变量
    int main()
    {
    	printf("%d\n", rand);
    	return 0;
    }       
    // 出现了编译错误,error C2365: “rand”: 重定义;以前的定义是“函数”
    //因为rand是一个C语言标注库中的函数,在编译时发现你使用的库中函数一样的名字就报错了
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    这个案例我们会在文章末尾进行再度分析

    结论😗: 没有命名空间的话,在C/C++中,标识符(变量、函数和类)都是大量存在的,并且都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染.

    定义

    为了解决合作开发时的命名冲突问题,C++ 引入了命名空间Namespace的概念。
    当你要定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{ }即可,{ }中即为命名空间的成员

    ①普通定义:

    //项目中成员分别以自己的名字定义了命名空间
    namespace zs//张三的变量定义
    {  
        int a = 1;
    }
    namespace ls //李四的变量定义
    {  
        int a = 2;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    ②嵌套定义

    
    namespace demo1
    {
    	int a = 0;
    	int Add(int left, int right)
    	{
    		return left + right;
    	}
    	namespace demo2
    	{
    		int b = 0;
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    ③不同文件同名命名空间合并

    // test.cpp
    namespace N1
    {
    	int a;
    	int b;
    	int Add(int left, int right)
    	{
    		return left + right;
    	}
    }
    
    // test.h
    namespace N1
    {
    	int Mul(int left, int right)
    	{
    		return left * right;
    	}
    }
    //test.cpp 和 test.h中的 N1 最终会合并为一个
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    使用

    正因为有了命名空间,所以使用对于的变量函数等,就要指明是具体哪个命名空间
    在C++ 中 using 用于声明命名空间至全局命名空间(说白了就是解开std的束缚,让命名空间std完全暴露出来),使用命名空间也可以防止命名冲突。
    :: 是一个新符号,称为域解析操作符,在C++中用来指明要使用的命名空间

    1.命名空间名称+域解析操作符

    //对应上面第一个普通定义的例子
    zs::a = 10;
    ls::a = 20;
    
    • 1
    • 2
    • 3

    2.using + 命名空间 :: 一个成员

    using zs::a; //它的意思是,using 声明以后的程序中如果出现了未指明命名空间的 a
    			 //就使用 zs::a;但是若要使用ls定义的 a,仍然需要 ls::a。
    
    a = 10; //此时的a时zs命名空间
    ls::a = 20;//ls命名空间
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3.using namespace +命名空间

    using namespace zs; //在 using 声明后,如果有未具体指定命名空间变量产生了命名冲突
    			        //那么默认采用命名空间 zs 中的变量。
    a = 10; //此时的a时zs命名空间
    ls::a = 20;//ls命名空间
    
    • 1
    • 2
    • 3
    • 4

    对于上面的那个经典案例,我们就可以用到命名空间的知识进行解决

    #include
    namespace hk
    {
    	int rand = 0;
    };
    int main()
    {
    	printf("%d", hk::rand); //用到第一种方法进行解决
    	return 0; 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    但是如果你使用第二种,第三种方法,就会出错
    在这里插入图片描述
    在这里插入图片描述

    接下来我们就着重对这个例子进行分析
    首先可能会有人有疑惑为什么 头文件是< iostream > ,并没有< cstdio >为什么也会找到< cstdio >里面的rand()函数

    那是因为头文件的引用时进行了层层包含
    iostraeam 里面引用了
    #include < istream >
    istream
    引用了
    #include < ostream >
    引用了
    #include < ios >
    引用了
    #include < xlocnum >
    引用了
    #include < cstdlib >
    里面有如下定义
    在这里插入图片描述

    通过上图就可以发现,在使用C++的模式下使用C语言库里面的,其实头文件的里面就使用using打开了命名空间的束缚,所以就出现了上面经典案例中用第二种和第三种方法时,即使你根本没有手动使用using去声明这个命名空间(即命名空间暴露出来),但是仍然能够使用std里面的rand()函数.因为头文件里面自己就打开了!!!

    并且按照 C++ 的方式来使用C语言的头文件,即#include < cstdio >这种形式,那么符号可以位于命名空间 std 中,也可以位于全局范围中.
    ①.使用命名空间std

    #include 
    int main()
    {
        std::printf("kklovecode");
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    ②.不使用命名空间 std

    #include 
    int main()
    {
        printf("kklovecode");
        return 0;
    }   //在大部分编译器中都能通过
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    你也能在头文件中找到使用using打开了std的束缚
    在这里插入图片描述

    using namespace std弊端

    相信大家在看许多C++资料时,都会发现很多都是直接在头文件下面写上 using namespace std;用的就是第三种方法使用std命名空间
    如:

    #include
    using namespace std;
    int main()
    {
      ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    因为标准库可能会升级,这样升级编译使用的C++版本的时候有可能因为引入了新的符号跟自己代码里的命名冲突,这样做增加了命名冲突的风险.
    并且C++设置std命名空间就是不想你直接使用,而当你直接用using namespace std; 将其暴露出来,也多少有点违背C++本意.
    总结😗:但一般来说,升级C++版本最多几年也就做一次,冲突的可能性也并不大,所以很多教材中的代码像那样使用节约时间成本也并非没有道理,但在中大型项目开发中是不被推荐的,适合使用第二种或者第三种方式

  • 相关阅读:
    内网渗透知识 ——(一)、工作组、域、域控、活动目录
    grav安装教程
    单目标应用:基于成长优化算法(Growth Optimizer,GO)的微电网优化调度MATLAB
    npm 命令
    js 回到顶部逻辑实现和elementUI源码解析
    OAuth 2.0 (第三方登录)前端流程实现
    分布式存储系统之Ceph集群MDS扩展
    玩一玩 Ubuntu 下的 VSCode 编程
    刷爆力扣之非递减序列
    RocketMQ(五)RocketMQ集群架构
  • 原文地址:https://blog.csdn.net/kklovecode/article/details/132718478