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

这里写目录标题

  • 历史来源
  • 意义
  • 定义
  • 使用
  • using namespace std弊端

历史来源

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

意义

提到命名空间就不得不提到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语言标注库中的函数,在编译时发现你使用的库中函数一样的名字就报错了

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

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

定义

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

①普通定义:

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

②嵌套定义


namespace demo1
{
	int a = 0;
	int Add(int left, int right)
	{
		return left + right;
	}
	namespace demo2
	{
		int b = 0;
	}
}

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

// 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 最终会合并为一个

使用

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

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

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

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

using zs::a; //它的意思是,using 声明以后的程序中如果出现了未指明命名空间的 a
			 //就使用 zs::a;但是若要使用ls定义的 a,仍然需要 ls::a。

a = 10; //此时的a时zs命名空间
ls::a = 20;//ls命名空间

3.using namespace +命名空间

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

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

#include
namespace hk
{
	int rand = 0;
};
int main()
{
	printf("%d", hk::rand); //用到第一种方法进行解决
	return 0; 
}

但是如果你使用第二种,第三种方法,就会出错
C++的命名空间namespace详解及特殊情况分析_第1张图片
C++的命名空间namespace详解及特殊情况分析_第2张图片

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

那是因为头文件的引用时进行了层层包含
iostraeam 里面引用了
#include < istream >
istream
引用了
#include < ostream >
引用了
#include < ios >
引用了
#include < xlocnum >
引用了
#include < cstdlib >
里面有如下定义
C++的命名空间namespace详解及特殊情况分析_第3张图片

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

#include 
int main()
{
    std::printf("kklovecode");
    return 0;
}

②.不使用命名空间 std

#include 
int main()
{
    printf("kklovecode");
    return 0;
}   //在大部分编译器中都能通过

由此说明大部分编译器在实现时并没有严格遵循C++标准,它们对两种写法都支持,程序员可以使用 std 也可以不使用。
①写法是标准的,②不标准,虽然它们在目前的编译器中都没有错误,依然推荐使用①写法,因为标准写法会一直被编译器支持,非标准写法可能会在以后的不再支持。

所以由此可见,在使用C++的模式下使用C语言库里面的,编译器并没有严格按照C++的规范,所以就出现了上面经典案例中用第二种和第三种方法时,即使你根本没有手动使用using去声明这个命名空间(即命名空间暴露出来),但是仍然能够使用std里面的rand()函数.

using namespace std弊端

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

#include
using namespace std;
int main()
{
  ...
}

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

你可能感兴趣的:(c++,开发语言,c语言)