在我们之中大多数人实现的第一个CPP代码大概率是在控制台中输出一条"hello world!"语句,就像下面这段代码一样。
#include <iostream>
using namespace std;
int main()
{
cout << "hello world!" << endl;
return 0;
}
第一行的 #include <iostream> 可以理解,因为在C语言中也有这样的引头文件的操作,但是第三行的 using namespace std; 是什么意思呢?在CPP的代码练习中我们好像都习惯性的敲上这一行代码,就好像是写CPP代码理所应当带上这行代码一样,实则不然。
本篇博客将和大家一起揭开CPP中 “命名空间 — namespace” 的神秘面纱。
一、命名空间的由来
在介绍命名空间的由来之前不得不提一下C语言中的命名冲突问题。
#include <stdio.h>
#include <stdlib.h>
int rand = 0; // 变量rand与头文件中包含的rand()函数发生命名冲突
int main()
{
printf("%d", rand);
return 0;
}
以上这段代码就是C语言中经典的重命名问题。
在头文件 <stdlib.h> 中已经定义了 rand() 函数了,此时再定义int类型的rand变量就属于重定义,会引发命名冲突问题。在C语言中无法解决这一问题,只能退而求其次修改变量的名称。
而在CPP中,namespace关键字的出现就是针对命名冲突问题的。
一个命名空间就是一个限定域。
可以形象的把命名空间理解成一个围栏。
将要定义的变量放在命名空间中防止自己定义的变量名与库中的变量名或函数名冲突。
将变量rand定义在命名空间N中,将变量rand隔离起来。这时候程序运行起来,控制台中输出的数值是头文件 <stdlib.h> 中 rand() 函数的地址。
编译器查找变量的规则:
默认先在局部域中查找,在局部域中查找完后没找到相应的变量,此时再去全局域中查找,若在全局域中没找到相应的变量,则编译报错。
因为命名空间属于一个限定域,编译器查找变量默认不会在限定域中查找,只会在局部域和全局域中查找。这也很好的解释了为什么将变量rand定义在命名空间N中后,控制台输出的数值是 rand() 函数的地址了。
那现在又出现了另外一个问题,把变量定义在命名空间中后,编译器默认不会到限定域中查找相应的变量,那我要怎么访问定义在命名空间中的变量呢?接下来轮到 域作用限定符(::) 出场了。
作用域限定符 (::)
< 作用域限定符用法 >
作用域 :: 作用域中的变量名
| 例 |
若使用了作用域限定符在指定的作用域中查找相应变量,若作用域中不存在相应的变量,编译器就会直接报编译错误。
( 若作用域限定符的左边不带任何的命名空间名称,则代表在全局域中查找相应的变量名 )
那么定义在命名空间中的变量是局部变量还是全局变量呢?
答案是全局变量!
接下来还是用一段简单的代码来验证一下。
如果定义在命名空间N中的变量x是局部变量,则控制台中不会输出变量x的值,因为出了该作用域,作用域中的变量就销毁了(函数栈帧)。很显然控制台中输出了变量x的值,由此可以证明定义在命名空间中的变量是全局变量。
二、命名空间的定义与使用
2.1. 命名空间的定义
在命名空间中可以定义:① 变量 ② 函数 ③ 类型
一般定义:
| 例 |
namespace N1
{
// 定义变量
int a = 0;
int b = 1;
// 定义函数
int Add(int left, int right)
{
return left + right;
}
// 定义类型
struct Node
{
struct Node* next;
int val;
};
}
嵌套定义:
| 例 |
namespace N2
{
int a;
int b;
int Add(int left, int right)
{
return left + right;
}
// 将命名空间N3嵌套定义在命名空间N2中
namespace N3
{
int c;
int d;
int Sub(int left, int right)
{
return left - right;
}
}
}
※ 特需注意!
同一个工程中允许存在多个相同名称的命名空间,编译器最后会将同名的命名空间中的内容合并到一个命名空间中。
( 只有同级别的同名称的命名空间才会合并,若是嵌套定义的同名称的命名空间则不会合并,因为不是同一级别 )
2.2. 命名空间的使用
命名空间的使用有三种方式。
● 带命名空间的名称及作用域限定符
namespace N1
{
int a = 0;
int b = 1;
int Add(int left, int right)
{
return left + right;
}
}
int main()
{
printf("%d\n", N::a);
return 0;
}
● 使用 using 将命名空间中某个成员引入
namespace N1
{
int a = 0;
int b = 1;
int Add(int left, int right)
{
return left + right;
}
}
using N::b;
int main()
{
printf("%d\n", N::a);
printf("%d\n", b);
return 0;
}
● 使用 using namespace+命名空间名称 引入
namespace N1
{
int a = 0;
int b = 1;
int Add(int left, int right)
{
return left + right;
}
}
using namespce N;
int main()
{
printf("%d\n", N::a);
printf("%d\n", b);
Add(10, 20);
return 0;
}
※ 使用嵌套定义的命名空间中的变量
namespace N2 { int a = 1; int b = 10; int Add(int left, int right) { return left + right; } namespace N3 { int c = 20; int d = 5; int Sub(int left, int right) { return left - right; } } } int main() { printf("%d\n", N2::N3::d); // 访问命名空间N2中的命名空间N3中的变量d return 0; }
通过上面的学习,对 using namespace std; 这行代码中的using和namespace多少有了一定的认识,其中的std也是一个命名空间的名称,CPP标准库中的函数或者对象都是定义在命名空间std中的。
在CPP代码中使用命名空间可以对标识符的名称进行本地化,以避免命名冲突或名字污染,从而解决了C语言中无法解决的命名冲突问题。
本次与大家一起探讨CPP中的命名空间 — namespace到这就已经接近尾声了,期待下次与你相遇。
< 你的关注,点赞,评论,收藏都是对我创作最大的鼓励 >
( 若本篇博客存在错误,望指出,感谢! )