命名引发的“战争”

2023-12-15 16:36:21

目录

前言

C语言中的命名冲突

命名空间?

定义

使用

命名空间展开?

全部展开

指定展开(部分展开)

补充


前言

在我们刚开始学习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++ 中,合并同名的命名空间是在编译器进行链接阶段完成的。在链接阶段,编译器将所有编译单元中的符号进行合并(符号表是在编译阶段形成的),包括函数、变量和同名的命名空间。这确保了在最终生成的可执行文件中,只有一个命名空间被保留,避免了重复定义和冲突。

文章来源:https://blog.csdn.net/STUs1mpl/article/details/135013872
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。