命名引发的“战争”
目录
前言
在我们刚开始学习C++的时候,总是从打印“hello world”开始。
#include <iostream>
using namespace std;
int main()
{
cout << "hello world\n" <<endl;
return 0;
}
那么你知道这里的 using namespace std; 起到什么作用吗?还是说只是把它记了下来,当成和头文件一样的必写项呢?看完这篇文章,我相信你一定能对它有更深刻的认识。
C语言中的命名冲突
#include <stdio.h>
#include <stdlib.h>
int rand = 1;
int main()
{
printf("%p\n", rand);
return 0;
}
运行下这段代码,可以发现编译器会报一个重定义的错误,说 rand 以前的定义是一个函数。因为我们包含了 stdlib 这个头文件,在这个头文件里 rand 被定义为一个函数。从这里就可以看出在C语言中的命名冲突问题了,这还只是和库中的命名冲突,还有程序员之间的命名冲突。当我们在一个大项目开发时,我们需要定义的变量和函数非常多,不同程序员之间难免会用到相同的名字,这时候就出问题了,只能让其中一方更改名字,非常麻烦。C++引入了命名空间来解决这个问题,使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。
命名空间?
定义
namespace xxx // xxx 是这个命名空间的名字,可以随便起
{
int rand = 1; // 命名空间定义变量
int Add(int left, int right) // 命名空间定义函数
{
return left + right;
}
struct Node // 命名空间定义结构体,包括类也是可以在这里定义的
{
struct Node* next;
int val;
};
namespace N1 // 命名空间可以嵌套
{
int Mul(int left, int right)
{
?? return left * right;
}
}
}
定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名空间的成员,可以是变量、函数或者类型,并且命名空间可以嵌套使用。这时候我们再去运行C语言中冲突的那段代码,就不会报错了。因为这个命名空间定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中,默认情况下不会去访问该命名空间里的内容。可以理解为把里面的内容围起来了,这样就很好的做到了名字的隔离,解决了C语言命名冲突的问题。
注意:在命名空间域内定义的成员可以在全局范围内使用,只需使用域作用符来指明其所属的命名空间。
使用
int main()
{
printf("%d\n", xxx::rand);
printf("%d\n", xxx::Add(1, 2));
struct xxx::Node node;
// 事实上这里的struct可以省略,学到类和对象那部分就能理解了
// Node就是类型的名字
printf("%d\n", xxx::N1::Mul(2, 2)); // 嵌套命名空间的使用
return 0;
}
: :? 是域作用限定符,用于指定命名空间、类、结构体或枚举的范围,以明确标识特定成员的所属关系。通过 :?: ,我们能够访问全局命名空间下的变量、函数,或者类中的静态成员。这个操作符提供了一种明确的方式来解决命名冲突,同时允许我们在不同的作用域中使用相同的标识符。
printf("%d\n", Add(1, 2));
// 这里就报错了,因为Add函数是定义在域里的
在没有使用域作用符指定命名空间时,默认情况下不会在命名空间中查找。因此,我们可以将需要定义的变量、函数等放入相应的命名空间域中。
命名空间展开?
全部展开
想象一下,我们定义了一个命名空间,在里面定义了程序中所需要的一切变量、函数等。在我们的代码中,每次使用它们都要用域作用符来指定命名空间,是不是非常的麻烦。(当然,我这里指的是它们不会存在命名冲突的情况下)这时候我们可以展开命名空间。
using namespace xxx // xxx 是命名空间的名字
这时候就默认了优先从 xxx 命名空间里搜索,接下来使用该命名空间的成员时,就不需要再加域作用符限定符了。如下所示:
int main()
{
printf("%d\n", Add(1, 2));
return 0;
}
注意:不要轻易的展开命名空间,除非确定不会发生命名冲突。
现在你应该明白为什么要包含 using namespace std;?这句代码了。这里的目的是展开 std?这个命名空间,它是C++官方库定义的命名空间。在工程项目中,最好不要展开 std,以免发生命名冲突。然而,在日常学习C++的过程中,展开 std?可以更方便地使用库中的功能。
指定展开(部分展开)
当我们只需要使用命名空间的一些成员时,可以明确指定展开,这有助于避免将整个命名空间的内容全部引入,从而有效防止命名冲突的发生。下面以 std 命名空间来举例:
using std::cout;
using std::endl;
cout 和 endl 都是定义在 std 命名空间中的,后续会介绍它们的功能。这里只需要知道我们将 std?命名空间中的 cout?和 endl?展开了,这样在后续使用时就无需添加域作用限定符,可以直接使用这两个成员。
补充
当命名空间中的函数声明和定义分离时,应该如何编写呢?接下来,我们通过代码演示一下:
假设下面这个是在 .h 文件中的:
namespace xxx
{
typedef struct Stack
{
int* a;
int top;
int capacity;
}Stack; // 这里其实无需 typedef,同理学到类和对象就明白了
void StackInit(Stack* ps, int n = 4);
void StackPush(Stack* ps, int x);
}
下面这个是??.cpp 文件中的:
#include "Stack.h"
namespace xxx
{
void StackInit(Stack* ps, int n)
{
ps->a = NULL;
ps->top = 0;
ps->capacity = n;
}
void StackPush(Stack* ps, int x)
{
// ...
}
}
在同一个工程中,如果存在多个同名的命名空间,编译器会将它们合并成一个。因此,在声明和定义分离时,只需编写一个同名的命名空间,并在其中正常编写定义即可。
注意:同一个变量不能在多个同名的命名空间中重复定义,否则在合并时会导致重定义错误。
合并:在 C++ 中,合并同名的命名空间是在编译器进行链接阶段完成的。在链接阶段,编译器将所有编译单元中的符号进行合并(符号表是在编译阶段形成的),包括函数、变量和同名的命名空间。这确保了在最终生成的可执行文件中,只有一个命名空间被保留,避免了重复定义和冲突。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!