· CSDN的uu们,大家好。这里是C++入门的第二讲。
· 座右铭:前路坎坷,披荆斩棘,扶摇直上。
· 博客主页: @姬如祎
· 收录专栏:C++专题
目录
1. 为什么要有命名空间
2. 命名空间的定义
3. 访问命名空间域中成员的三种方式
3.1 使用using namespace 命名空间名称 引入
3.2 使用using将命名空间中某个成员引入
3.3 加命名空间名称及作用域限定符
4. 命名空间的其他知识
4.1 命名空间的合并
4.2 命名空间的嵌套
5. 总结
我们通过了解C++的发展史知道了C++的诞生的原因:为了弥补C语言的不足。因此在C++的入门阶段我们主要学习的就是C++是如何弥补C语言的缺陷的。
1. 为什么要有命名空间
我们先来看这样一段代码:
#include<stdio.h>
#include<stdlib.h>
int rand = 0;
int main()
{
printf("%d\n", rand);
return 0;
}
运行之后会报错:
这是为什么呢?因为头文件中有一个 rand() 函数的定义,我们知道头文件的包含在预处理阶段会将其里面的代码copy过来 。在编译阶段,既有一个全局变量rand,又有一个rand()函数的定义,显然就会报错啦!报错提示的C2365中的C就是Compile 编译的意思,代表编译阶段出错。
到这里可能就有人会说,我知道头文件 stdlib.h 中有rand的定义,我就不会去定义一个全局的rand了。但是呢,头文件这么多,保不齐你哪天包含一个头文件之后,代码就出现了99+的错误。还有就是一个大的项目不可能是由一个人完成的撒,不同的人对函数和变量的命名很有可能是相同的,在各自的项目进行合并是,又可能出现一对错误。这就是C语言的缺陷。因此C++中有了命名空间的概念。
在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。
2. 命名空间的定义
定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名空间的成员。
在这里还得提到一个域的概念:在C语言中我们学习了全局域,局部域。我们在C++中还会接触到命名空间域,类域等等。一个变量所处域的不同会影响其声明周期。
我们来看C++是如何解决上述面命名冲突的问题的。大家可以猜猜看这段代码在终端的输出。
int a= 0;
namespace Tchey_Y
{
int a= 1;
}
int main()
{
int a= 2;
printf("%d\n", rand);
return 0;
}
没错就是2,原因是编译器在查找变量时有先从局部域中搜索,然后才是全局域。这里值得注意的是:不加任何限定的话,编译器是不会在命名空间域中去搜索的。
那我们怎么才能做到局部域有这个变量的同时还能去访问到全局的同名变量呢?这里就得使用我们的作用域限定符啦:::。就是两个英文状态下的分号,加上这个域作用限定符,就能实现在该作用域下搜索变量。如果该作用域下没有这个变量就会报错。
int a = 0;
namespace Tchey_Y
{
int a = 1;
}
int main()
{
int rand = 2;
printf("%d\n", a); //访问局部域的变量a
printf("%d\n", ::a); //访问全局域下的a
printf("%d\n", Tchey_Y::a); //访问命名空间域下的a
return 0;
}
域作用限定符的左边什么都不写就代表在全局域下搜索。
3. 访问命名空间域中成员的三种方式
3.1 使用using namespace 命名空间名称 引入
我们在看C++的代码时,代码开头是不是都有这么一句:
using namespace std;
这便是将命名空间域进行展开。将命名空间进行展开之后,如果你的全局域中有与该命名空间域同名的变量和函数依旧时会报错的。
为什么会这样呢?编译器默认是不会去命名空间域搜索a这个变量的,当我们展开命名空间之后,对于上面的程序编译器就会在命名空间域中和全局域中同时搜索变量a。全局域与命名空间域中都有一个a,自然就会报错啦!
这样也显示出使用 using namespace 的坏处。因此在实际的工程中我们是不会使用这种方式去访问命名空间域中的成员的。
在C++中,C++的标准库,以及STL都是在std这个命名空间下的,因此在using namespace std 之后我们就能使用std命名空间域中的成员啦。但是这些东东不是在同一个头文件中的,使用时我们还需要包含对应的头文件。
3.2 使用using将命名空间中某个成员引入
直接展开命名空间不好,那有没有什么更加好的方式呢?显然是有的,我们可以 单独引入命名空间下的某个成员。这样就不容易发生冲突啦!
#include<iostream>
using std::cout;
using std::endl;
int main()
{
cout << "hello world" << endl;
return 0;
}
这里我们引入了命名空间中的cout和endl,这样我们就能使用std命名空间中的cout和endl了,但是这样就不能使用std命名空间中的其他成员,比如cin。cout,cin是分别是ostream和istream的对象,关于这点我们后面会学习到。智力只需要知道cout是输出,cin是输入,endl是换行即可。
这么做是不错,可是当我们使用std命名空间下的很多成员之后,就和直接展开命名空间没啥区别了,因此我们可以和第三种访问命名空间成员的方法配合使用。
3.3 加命名空间名称及作用域限定符
这种方法我们上面就已经使用过了。
#include<iostream>
#include<vector>
using std::cout;
using std::endl;
int main()
{
cout << "hello world" << endl;
cout << "hello world" << endl;
cout << "hello world" << endl;
cout << "hello world" << endl;
cout << "hello world" << endl;
std::vector<int> array;
return 0;
}
程序中使用较多的成员我们选择第二种方法来访问,程序中使用较少的成员我们选择使用命名空间加上作用域限定符来访问。
4. 命名空间的其他知识
4.1 命名空间的合并
上面提到了C++的标准库和STL(标准模板库)都是在std命名空间下的,但是他们却在不同的头文件,这就是因为相同的命名空间会进行一个合并操作。
我们在Test.h 中有一个同名的命名空间,展开之后,我们能够同时访问到这两个同名的命名空间中的成员。可以证明进行了相同命名空间的合并。
4.2 命名空间的嵌套
命名空间是可以进行嵌套的。
#include<iostream>
using namespace std;
namespace test1
{
int a = 10;
namespace test2
{
int a = 20;
}
}
int main()
{
cout << test1::a << endl;
cout << test1::test2::a << endl;
return 0;
}
应用场景:在做一个大型的项目时,我们一般会以这个项目的名称定下一个命名空间。这个项目的完成可能是由不同的工程师完成的,工程师在做自己的那部分工程时,可能又会在这个项目的命名空间之下创建一个新的命名空间。
5. 总结
std命名空间的使用惯例:
std是C++标准库的命名空间,如何展开std使用更合理呢?1. 在日常练习中,建议直接using namespace std即可,这样就很方便。
2. using namespace std展开,标准库就全部暴露出来了,如果我们定义跟库重名的类型/对
象/函数,就存在冲突问题。该问题在日常练习中很少出现,但是项目开发中代码较多、规模
大,就很容易出现。所以建议在项目开发中使用,像std::cout这样使用时指定命名空间 +
using std::cout展开常用的库对象/类型等方式。