当前位置: 首页 > news >正文

浅谈C++/C命名冲突

前言

在这里我会简要地介绍产生命名冲突的原因,和C++中处理命名冲突的方法,同时和C语言的解决办法进行比较。
相信你在阅读完之后一定会有收获。对于我们来说,了解编译器的编译链接过程才能更好的理解编译器是如何报错的,更能让我们把持细节。
其中有*的部分,是一些补充内容。

1. 编译链接

实际上,我们所写的代码都会经历一个翻译,链接的过程。
计算机是无法识别我们所写的代码的,计算机只能识别二进制的指令,所以我们所写的代码需要经过翻译才能被计算机识别。这个翻译就是编译所做的事情。由于我们写代码的时候大可能是由多个源文件组成的一个程序,需要将多个源文件联系起来就是链接的过程了,最后生成可执行程序。
(注意:多个源文件是分开进行的编译)
其中,编译分为如下阶段:

  1. 预编译

  2. 编译

  3. 汇编

然后进行多个文件的链接,大致流程如图:

在这里插入图片描述

大致一个项目的过程如下:
在这里插入图片描述

1.1 编译环节

下面会大致介绍一下编译过程会发生的事。

  1. 预编译。在预编译中,我们的编译器会干啥事呢?

    a、头文件展开 #include
    b、宏的展开 #define
    c、消除注释
    ……
    在Windows环境下经过预编译的文件会生成一个后缀为 .i 的文件;在Linux环境下经历预编译的环境下生成的后缀为.i的文件

  2. 编译。在编译的时候,编译器要干的事就很多了:
    a、词法分析。这个步骤就是来识别“单词”的,例如:int a; 就会被识别为:“int”关键字,“a”标识符……

    b、语法分析。简单来说就是检查语法是否正确的。

    c、语义分析。重点是检查语法结构是否正确,例如:int num = “hehe”,这样类型不匹配的语义错误。

    d、符号表管理。符号表是一种数据结构,用于记录源文件中出现的全局的变量名、函数名等等,这个符号表后面还会用到。

    在Windows环境下经过编译环境过后会生成一个后缀为.asm的文件;在Linux环境下经历编译过后会生成一个后缀为.s的文件。

  3. 汇编。在这个环节中,编译器会将代码翻译为计算机能识别的二进制指令。然后再进行下一步的处理,但是在这个时候,我们的符号表内还有一些外部符号需要处理,例如:一个外部函数,或者变量等等(等会处理的主题),还需要后面的链接过程进行进一步的处理。

    在Windows环境下经过汇编环境过后会生成一个后缀为.obj的文件;在Linux环境下经历汇编过后生成一个后缀为.o的文件。

在这里插入图片描述

1.2 链接环节

链接过程涉及比较复杂,过多的不再介绍。我们主要介绍:链接环节会进行符号表的合并。那么整个项目中的全局变量和函数都会进入这个符号表,那么在刚才的汇编过程中的一些外部符号就得到处理了。
下面来举个例子来说明:

/* add.c文件 */
int Add(int x, int y)
{return x + y;
}
// .../
/* test.c文件 */
int Add(int x, int y);  //函数的声明int main()
{Add(1, 2);return 0;
}

在上面的例子中,我们在add.c文件中定义了函数Add,在test.c文件中声明了函数Add。
在这里插入图片描述
(上面图片函数地址是假设的)
做个比喻:声明就好像是一个承诺,定义就是兑现承诺。编译过程的不会检查函数是否是实现的,它只会检查是否是承诺过的(声明),直到后面的链接过程后,就会将实现的函数的地址汇总进完整的符号表,函数承诺就相当于获得了兑现。1、那么如果我们使用了未定义的变量或者函数,那会发生什么呢?很显然,在我们刚刚讲过的1.1编译环节就谈到了会进行语法分析、语义分析,所以当我们使用了未定义的变量或者函数名时,就是编译环节就会报错。2、那么如果我们声明了变量或者函数,但是没有定义,那会发生什么呢?在刚刚的链接环节已经介绍了,声明的外部符号会在完整的符号表中去寻找,如果找不到有效地址那么就会出现链接错误。3、那么如果我们重定义了变量或者函数呢?那么在符号表中就会用同名的函数,但是它们的有效地址却不相同,对于编译器来说,就无法分辨调用哪一个变量或者函数了。这就是我们接下来要讨论的重点:命名冲突

2. 处理方式

在介绍C++的处理方式之前,我们先来看C语言的处理方式

2.1 C语言static关键字

在C语言中,大概就有两种作用域:a、全局域 b、局部域。在全局域中的变量和对象是具有外部链接属性的(extern声明),对于它们来说,会有和其它源文件冲突的风险。比如说:在一个项目中,有人在a.c文件全局定义了Arr变量,另一个人在b.c文件全局定义了Arr变量(文件名和变量名皆是举例子,这种命名风格不可行),但是含义完全不同,这时候根据我们上面所说,编译器就会犯迷糊了,就会链接失败。又比如说:库里实现了一个函数,而程序员又自己写了一个同名的函数,同时包含了这个库函数的头文件,那么编译器应该听谁的呢?……所以在C语言中为了解决命名冲突的问题,我们需要使用关键字:static

2.1.1 static用法

我们首先回顾一下startic用法:

  1. 修饰局部变量时。该变量的存储类型从自动存储类型变为静态存储类型,即从栈区存储的变量变成了静态区存储的变量。

  2. 修饰全局变量时。本来处于全局域的变量是具有外部链接属性的,经过static修饰过后,该全局变量的链接属性变为内部链接属性:即作用域仅局限于本源文件,不可被其它源文件访问。

  3. 修饰函数。同样地,全局函数是具有外部链接属性的,经过static修饰过后的函数,其只能在定义的源文件中使用,对于其它文件来说就是不可访问的。

那么根据第2、3条修饰规则,我们可以发现,对于C语言来说解决命名冲突的方式就是对于仅仅在本源文件使用的全局变量或者函数,或者若干个函数需要共享同一组全局变量,可以将这些函数放在同一个源文件中,把他们所需要的变量也放在该源文件中并用static修饰,即可防止命名冲突。同时,注意:我们可以全局多定义同名函数,但是需要保证的是,没有static修饰的函数数量 <= 1,这样才能保证运行成功。
例如:

/* 源文件 */
static int g(int x)
{//...
}void func()
{//...g(); //本文件调用g();
}
* 2.1.2 extern关键字

说到static关键字,就不乏说到extern关键字了,这个关键字的作用是:声明一个外部对象(可以理解为:处于其它文件的全局变量)
例如:

/* add.c文件 */
int global_num = 10;//此处声明了一个全局变量global_numint Add(int x, int y)
{return x + y;
}
// .../
/* test.c文件 */
int Add(int x, int y);  //函数的声明
//如果想在该文件使用该变量的话,做如下声明:
extern int global_num; //这是一个声明,不是定义int main()
{Add(1, 2);return 0;
}
extern关键字显示说明了global_num的存储空间是在程序的其它地方分配的。
从编译器的角度来看,通过该声明,编译器知道会在链接的过程中找到这个变量的定义地方(地址)。

但是对于这个关键字还有很多细节:
例如,来看下面一个程序

// 说明:已在外源文件声明了arr1 和 arr2
/*
int arr1[3] = { 0 };
int arr2[3] = { 0 };
*/extern int* arr1;
extern int arr2[]; //代码一
extern int arr2[3]; //代码二int main()
{printf("指针类型:%zd\n", sizeof(arr1));printf("数组类型:%zd\n", sizeof(arr2));return 0;
}

该程序运行的结果会让你大吃一惊!
在这里插入图片描述
(这是在VS2022x64坏境下进行的)
实际上,编译器对于代码一的处理是警告的
在这里插入图片描述
在这里插入图片描述
甚至是,将该[ ]的数字改变了也会影响结果。这样的使用会造成什么结果呢?这样的使用会造成许多意想不到的结果。比如说:在原定义中声明为了int型,但是在外部对象中声明为long,在不同环境下由于内存所占的字节不同。这样的话,由于两个这样对于其中一个的赋值,另一个可能得到不如意的结果。这样的结果是防不胜防的。
这样的结果不是我们所希望的。所以日常使用的过程之中,建议就是:少用全局变量!!!

2.2 C++命名空间

2.2.1 基本使用

我们的C++就看到了C语言的命名冲突的问题,灵感乍现之下创建了一个新的语法——命名空间
首先我们先来了解一个常识:
在C++中有四种({ }之中的)

  1. 全局域
  2. 局部域
  3. 命名空间域
  4. 类域

命名空间域就是今天的主题。其中全局域和局部域决定了一个变量的生命周期,而我们的类域和命名空间域不会。
根据我们上面的了解,我们知道了在同一作用域下,同名的变量会发生冲突,命名空间域就相当于设置了一个墙,防止与全局域中的变量发生冲突。那么我们可以在命名空间域中干什么呢?实际上,我们可以把它当作另类的全局域。
关键字:namespace。在这个命名空间域中我们可以定义任意类型,例如:内置类型,自定义类型,函数……

namespace Er
{int b = 10;typedef struct STNode{int val;struct STNode* next;}STNode, *PSTNode;int ADD(int x, int y){return x + y;}char c = '*';
}

访问方法也很简单:操作符 ::
例如:

namespace Er
{int a = 10;
}
int a = 1;int main()
{int a = 2;cout << a << endl; //局部域cout << ::a << endl;//全局域cout << Er::a << endl;//命名空间域
}
上面的示例给出了a的三种域的访问方式,我们可以看到全局域可以通过空命名空间的方式在局部域中访问。

实际上,对于C++标准库定义的命名空间就是std。我们需要使用里面的函数,类对象都是需要访问这个命名空间的(例如cout、cin),我们每次都去声明命名空间域就太过于麻烦了,所以我们采用展开命名空间的方式使用其中的变量。这里的展开命名空间又分为两种:1、部分展开 2、全部展开。
对于展开来说:关键词using

  1. 部分展开。例如:
//	例如: using std::cout
namespace Er
{int a = 1;int b = 2;
}
using Er::b; //部分展开的方法
  1. 全部展开
//	例如: using namespace std;
namespace Er
{int a = 1;int b = 2;
}
using namespace Er;

对于展开的变量或者命名空间来说,就相当于暴露在了全局域中,这样还是存在命名冲突的风险的,所以建议,在项目中对于标准库的命名空间,尽量展开几个常用的函数或者对象。

2.2.2 使用细节
  1. 同一项目下不同文件的同名命名空间是合并的。
namespace Er
{int a = 10;
}namespace Er
{int b = 5;
}
int main()
{cout << Er::a << endl; //在源文件2中可以访问到源文件1的Er命名空间变量a
}

上面的例子说明我们仍然有可能在我们不之情的情况下,在同一项目源文件同名命名空间定义了相同的变量,这个时候第二细节排上用场了。

同一源文件的命名空间合并通常是发生在编译阶段。同时需要注意:在使用分文件的命名空间的时候,通常需要注意使用头文件来声明命名空间。以确保各个源文件中对命名空间的使用是一致的。如果不使用头文件,还记得我们上面讲的吗?编译阶段每个源文件是各自编译各自的,所以,如果不采用头文件,我们就会发现编译器找不到合适的命名空间内的变量。而上面的例子中是在同一源文件下的操作。如果还是需要分源文件,不使用头文件,就可以使用extern声明。

  1. 命名空间可以嵌套。

举个例子:

namespace Cc
{int a = 1;namespace Kk{int b = 0;int a = 2;}
}int main()
{printf("Cc中的 a == %d\n", Cc3::a);printf("Cc中的Kk中的 a == %d\n", Cc3::Kk::a);printf("Cc中的Kk中的 b == %d\n", Cc3::Kk::b);return 0;
}

上面的示例也给出了如何去合理的访问嵌套命名空间的方法。
对比C语言和C++解决命名空间的方法,我们看到,C++采用命名空间的方式是比C语言好上太多了,避免了C语言的诸多问题,极大的提高了程序员编写代码的灵活性。

至此对于命名冲突发生的原因、解决办法已经谈论的差不多了。如果有问题欢迎指出,作者接受大家的批评教育。

本文章有参考:《C陷阱与缺陷》

相关文章:

浅谈C++/C命名冲突

前言 在这里我会简要地介绍产生命名冲突的原因&#xff0c;和C中处理命名冲突的方法&#xff0c;同时和C语言的解决办法进行比较。 相信你在阅读完之后一定会有收获。对于我们来说&#xff0c;了解编译器的编译链接过程才能更好的理解编译器是如何报错的&#xff0c;更能让我们…...

【语音编解码】常用的基于神经网络的语音编解码方案对比

引言 随着实时通信与多媒体应用的爆炸式增长&#xff0c;传统语音编解码技术正面临带宽效率与音质保真的双重挑战。近年来&#xff0c;基于深度学习的神经编解码器突破性地将端到端架构、动态码率控制与可解释信号处理相结合&#xff0c;在3kbps以下超低码率场景仍能保持自然语…...

PVE 配置显卡直通

博客地址&#xff1a;PVE 配置显卡直通 配置 Device: Dell PowerEdge T630CPU: Intel Xeon E5-2696 v4 x2GPU 1: Matrox Electronics Systems Ltd. G200eR2GPU 2: NVIDIA GeForce GTX 1060 3GBOS: Proxmox VE bookworm 8.3.1 x86_64 注意事项 硬件需支持并在 BIOS 中开启 I…...

Kronecker分解(K-FAC):让自然梯度在深度学习中飞起来

Kronecker分解&#xff08;K-FAC&#xff09;&#xff1a;让自然梯度在深度学习中飞起来 在深度学习的优化中&#xff0c;自然梯度下降&#xff08;Natural Gradient Descent&#xff09;是一个强大的工具&#xff0c;它利用Fisher信息矩阵&#xff08;FIM&#xff09;调整梯度…...

ArcGIS Pro技巧实战:高效矢量化天地图地表覆盖图

在地理信息系统&#xff08;GIS&#xff09;领域&#xff0c;地表覆盖图的矢量化是一项至关重要的任务。天地图作为中国国家级的地理信息服务平台&#xff0c;提供了丰富且详尽的地表覆盖数据。然而&#xff0c;这些数据通常以栅格格式存在&#xff0c;不利于进行空间分析和数据…...

React + TypeScript 数据模型驱动数据字典生成示例

React TypeScript 数据模型驱动数据字典生成示例 引言&#xff1a;数据字典的工程价值 在现代化全栈开发中&#xff0c;数据字典作为业务实体与数据存储的映射桥梁&#xff0c;直接影响系统可维护性与团队协作效率。传统手动维护字典的方式存在同步成本高和版本管理混乱两大痛…...

道可云人工智能每日资讯|深圳将设立人工智能和机器人产业基金

道可云元宇宙每日简报&#xff08;2025年2月26日&#xff09;讯&#xff0c;今日元宇宙新鲜事有&#xff1a; 上海青浦发布国际产业协作元宇宙平台 近日&#xff0c;“2025出海企业与跨境专业服务论坛”在上海青浦区徐泾镇举行。论坛上重磅发布三大全球化服务平台&#xff0c…...

[2024年下半年架构师考试真题之论文]

2024论文真题试题一(架构) 论面向服务的架构设计 Web service 是一种通过互联网协议(如 HTTP)来提供服务的软件系统,它允许不同的应用程序之间进行交互,而无需考虑它们所使用的操作系统、编程语言或硬件平台。其本质是将应用程序的功能以服务的形式暴露出来,使得其他应…...

神经网络 - 激活函数(Sigmoid 型函数)

激活函数在神经元中非常重要的。为了增强网络的表示能力和学习能力&#xff0c;激活函数需要具备以下几点性质: (1) 连续并可导(允许少数点上不可导)的非线性函数。可导的激活函数可以直接利用数值优化的方法来学习网络参数. (2) 激活函数及其导函数要尽可能的简单&#xff0…...

阿里云 | 快速在网站上增加一个AI助手

创建智能体应用 如上所示&#xff0c;登录阿里云百炼人工智能业务控制台&#xff0c;创建智能体应用&#xff0c;智能体应用是一个agent&#xff0c;即提供个人或者企业的代理或中间件组件应用&#xff0c;对接阿里云大模型公共平台&#xff0c;为个人或者企业用户提供大模型应…...

【操作系统】处理机调度

处理机调度 一、调度的概念、层次1.1 三个层次1.2 七状态模型 二、调度算法的评价指标2.1 CPU利用率2.2 系统吞吐率2.3 周转时间2.4 等待时间2.5 响应时间 三、进程调度&#xff08;低级调度&#xff09;的时机3.1 需要进程调度的情况3.2 不能进程调度的情况3.3 闲逛进程 四、进…...

mysql服务层介绍,NOSQL+SQL接口(nosql介绍),语法分析器,预处理器,优化器(优化的必要性,基于成本的优化器),缓存(弊端)

目录 mysql服务层 介绍 服务管理和公共组件 备份 NOSQL,SQL接口 介绍 nosql Parser模块(语法分析器) 介绍 词法分析 语法分析 示例 预处理器 引入 介绍 优化器 介绍 优化的必要性 基于成本的优化器 缓存 介绍 弊端 mysql服务层 介绍 数据库服务层是整个…...

将DeepSeek接入vscode的N种方法

接入deepseek方法一:cline 步骤1:安装 Visual Studio Code 后,左侧导航栏上点击扩展。 步骤2:搜索 cline,找到插件后点击安装。 步骤3:在大模型下拉菜单中找到deep seek,然后下面的输入框输入你在deepseek申请的api key,就可以用了 让deepseek给我写了一首关于天气的…...

【算法与数据结构】Dijkstra算法求单源最短路径问题

目录 Dijkstra算法 算法简介&#xff1a; 该算法的核心思想&#xff1a; 算法特点&#xff1a; 算法示例演示&#xff1a; 算法实现&#xff1a; 邻接矩阵存图 邻接表存图&#xff1a; 时间复杂度分析&#xff1a; Dijkstra算法 算法简介&#xff1a; Dijkstra算法&am…...

.CSV file input into contact of outlook with gibberish. .csv文件导入outlook, 出现乱码

workaround : 清理excel或者csv文件的格式, 使用手动先输入几个常规字, 然后使用格式刷...

StableDiffusion打包 项目迁移 项目分发 0

StableDiffusion项目迁移 0 先看了几个其他人的本地部署文章和视频&#xff0c;对别人的步骤做记录。&#xff08;写的很潦草&#xff0c;只是注意一下有什么点需要注意&#xff09; 虽然秋叶大佬有整合包&#xff0c;但是我是为了项目分发学习的&#xff0c;还是想自己配环境…...

关于Postman自动获取token

在使用postman测试联调接口时&#xff0c;可能每个接口都需要使用此接口生成的令牌做Authorization的Bearer Token验证&#xff0c;最直接的办法可能会是一步一步的点击&#xff0c;如下图&#xff1a; 在Authorization中去选择Bearer Token&#xff0c;然后将获取到的token粘贴…...

LSTM长短期记忆网络-原理分析

1 简介 概念 LSTM&#xff08;Long Short-Term Memory&#xff09;也称为长短期记忆网络&#xff0c;是一种改进的循环神经网络&#xff08;RNN&#xff09;&#xff0c;专门设计用于解决传统RNN的梯度消失问题和长程依赖问题。LSTM通过引入门机制和细胞状态&#xff0c;能够更…...

sql server笔记

创建数据库 use master gocreate database stuuuuu//删除数据库if db_id ($$$) is not nullDrop database [$$$] go//新建表USE [studyTest] GOSET ANSI_NULLS ON GOSET QUOTED_IDENTIFIER ON GOCREATE TABLE [dbo].[Table_1]([id] [int] NULL,[name] [varchar](10) NULL ) ON…...

AI Video Composer:基于Qwen2.5-Coder的简易开源视频创作利器

系列篇章&#x1f4a5; No.文章1短视频开源项目MoneyPrinterTurbo&#xff1a;AI副业搞起来&#xff0c;视频制作更轻松&#xff01;2【FunClip】阿里开源AI视频剪辑神器&#xff1a;全面体验与教程3Tailor&#xff1a;免费开源 AI 视频神器&#xff0c;创作者必备利器4Clappe…...

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...

工业安全零事故的智能守护者:一体化AI智能安防平台

前言&#xff1a; 通过AI视觉技术&#xff0c;为船厂提供全面的安全监控解决方案&#xff0c;涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面&#xff0c;能够实现对应负责人反馈机制&#xff0c;并最终实现数据的统计报表。提升船厂…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案

问题描述&#xff1a;iview使用table 中type: "index",分页之后 &#xff0c;索引还是从1开始&#xff0c;试过绑定后台返回数据的id, 这种方法可行&#xff0c;就是后台返回数据的每个页面id都不完全是按照从1开始的升序&#xff0c;因此百度了下&#xff0c;找到了…...

1688商品列表API与其他数据源的对接思路

将1688商品列表API与其他数据源对接时&#xff0c;需结合业务场景设计数据流转链路&#xff0c;重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点&#xff1a; 一、核心对接场景与目标 商品数据同步 场景&#xff1a;将1688商品信息…...

基础测试工具使用经验

背景 vtune&#xff0c;perf, nsight system等基础测试工具&#xff0c;都是用过的&#xff0c;但是没有记录&#xff0c;都逐渐忘了。所以写这篇博客总结记录一下&#xff0c;只要以后发现新的用法&#xff0c;就记得来编辑补充一下 perf 比较基础的用法&#xff1a; 先改这…...

Nuxt.js 中的路由配置详解

Nuxt.js 通过其内置的路由系统简化了应用的路由配置&#xff0c;使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...

OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别

OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...

【Java学习笔记】BigInteger 和 BigDecimal 类

BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点&#xff1a;传参类型必须是类对象 一、BigInteger 1. 作用&#xff1a;适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...

基于 TAPD 进行项目管理

起因 自己写了个小工具&#xff0c;仓库用的Github。之前在用markdown进行需求管理&#xff0c;现在随着功能的增加&#xff0c;感觉有点难以管理了&#xff0c;所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD&#xff0c;需要提供一个企业名新建一个项目&#…...