C++数据类型(一):一文看懂引用的本质

一.引言

        函数的参数传递方式主要有传值和传指针。

1.传值

      在函数域中为参数分配内存,而把实参的数值传递到新分配的内存中。它的优点是有效避免函数的副作用。  

        例如:

#include <iostream>void swap_val(int x,int y)
{int tmp;tmp = x;x = y;y = tmp;
}int main(int argc, char** argv) 
{int a = 20;int b = 30;swap_val(a,b);return 0;
}

2.传指针

       这里有两种传递方式。

(1)指针传递

        例如:

#include <iostream>void swap_pointer(int *x,int *y)
{int tmp;tmp = *x;*x = *y;*y = tmp;
}int main(int argc, char** argv) 
{int a = 20;int b = 30;swap_pointer(&a,&b);return 0;
}

(2)引用传递。

        例如:

#include <iostream>void swap_ref(int &x,int &y)
{int tmp;tmp = x;x = y;y = tmp;
}int main(int argc, char** argv) 
{int a = 20;int b = 30;swap_ref(a,b);return 0;
}

        这里将引用也归类为指针,是有依据的。下面详细分析、寻找引用的本质。

二.什么是引用?

        引用(reference)是C++中一种新的导出型数据类型,它又称别名(alias)。
        引用不是定义一个新的变量,而是给一个已经定义的变量重新起一个别名,也就是C++系统不为引用类型变量分配内存空间。引用主要用于函数之间的数据传递。

        定义的格式为:

类型 &引用变量名 = 已定义过的变量名;

        例如:

int main(void)
{int a;int &b = a;
}

三.指针传参和引用传参比较

        引用本质上也是指针。

int val = 10;
int  *a = &val;
int  &b = val;

        对于上面代码,大家可能会有这样的疑惑:指针变量a保存了变量val的地址,而声明为引用类型的变量b也保存了val的地址,它们不是一样的吗?究竟区别在哪?引用又称为别名,别名又如何理解?

        有这种疑惑,是因为我们的思维一直停留在高级语言的层面。要想彻底明白它们的区别,我们的思维要下沉到汇编语言层面。

3.1 调试分析

3.1.1 引用类型变量的大小

        测试代码如下:

3.1.2 变量的地址

        为了更好区别,调试代码中没有使用重载,而是在函数名上做了区分。

3.1.2.1 指针传参

        代码如下:

#include <iostream>using namespace std;void swap_pointer(int *x,int *y)
{int tmp;tmp = *x;*x = *y;*y = tmp;
}void swap_ref(int &x,int &y)
{int tmp;tmp = x;x = y;y = tmp;
}int main(int argc, char** argv) 
{int a = 20;int b = 30;swap_pointer(&a,&b);return 0;
}

        调试结果如下图所示。

        左侧窗口中显示了实参a、b的地址和函数域内x、y的地址,由图可知,它们地址不同。

3.1.2.2 引用传参

        代码如下:

#include <iostream>using namespace std;void swap_pointer(int *x,int *y)
{int tmp;tmp = *x;*x = *y;*y = tmp;
}void swap_ref(int &x,int &y)
{int tmp;tmp = x;x = y;y = tmp;
}int main(int argc, char** argv) 
{int a = 20;int b = 30;swap_ref(a,b);return 0;
}

         调试结果如下图所示。

        左侧窗口中显示了实参a、b的地址和函数域内x、y的地址,由图可知,它们地址相同。

3.1.2.3 结论

        从地址来看,指针传参与引用传参的确有区别。从这一角度来看,引用的确就是已存在变量的别名。

        但要注意,调试器显示的地址是C++语言级别的地址,即它是虚拟地址。即引用在C++开发人员看来,a和x、b和y使用的是同一个虚拟地址。

        至此,可以回答以下两个问题。

1.为什么称引用为别名?

        这是C++语言层面的概念。因为引用类型变量与被引用的对象使用同一个虚拟地址空间,所以称为别名。

2.为什么说C++系统不为引用类型变量分配内存空间?

        这也是在C++语言层面的概念。与第1个问题一样,因为引用类型变量与被引用的对象使用同一个虚拟地址空间,不需要重新开辟空间。

        但是,引用真的不用分配空间吗?下面继续分析。

3.1.3 汇编代码

3.1.3.1 指针传参

         C++源码见3.1.2.1节。汇编代码如下。

   0x0000000000401598 <+0>:	push   %rbp0x0000000000401599 <+1>:	mov    %rsp,%rbp0x000000000040159c <+4>:	sub    $0x30,%rsp0x00000000004015a0 <+8>:	mov    %ecx,0x10(%rbp)0x00000000004015a3 <+11>:	mov    %rdx,0x18(%rbp)0x00000000004015a7 <+15>:	callq  0x40e7b0 <__main>0x00000000004015ac <+20>:	movl   $0x14,-0x4(%rbp)0x00000000004015b3 <+27>:	movl   $0x1e,-0x8(%rbp)0x00000000004015ba <+34>:	lea    -0x8(%rbp),%rdx0x00000000004015be <+38>:	lea    -0x4(%rbp),%rax0x00000000004015c2 <+42>:	mov    %rax,%rcx0x00000000004015c5 <+45>:	callq  0x401530 <swap_pointer(int*, int*)>0x00000000004015ca <+50>:	mov    $0x0,%eax0x00000000004015cf <+55>:	add    $0x30,%rsp0x00000000004015d3 <+59>:	pop    %rbp0x00000000004015d4 <+60>:	retq  0x0000000000401530 <+0>:	push   %rbp0x0000000000401531 <+1>:	mov    %rsp,%rbp0x0000000000401534 <+4>:	sub    $0x10,%rsp0x0000000000401538 <+8>:	mov    %rcx,0x10(%rbp)0x000000000040153c <+12>:	mov    %rdx,0x18(%rbp)0x0000000000401540 <+16>:	mov    0x10(%rbp),%rax0x0000000000401544 <+20>:	mov    (%rax),%eax0x0000000000401546 <+22>:	mov    %eax,-0x4(%rbp)0x0000000000401549 <+25>:	mov    0x18(%rbp),%rax0x000000000040154d <+29>:	mov    (%rax),%edx0x000000000040154f <+31>:	mov    0x10(%rbp),%rax0x0000000000401553 <+35>:	mov    %edx,(%rax)0x0000000000401555 <+37>:	mov    0x18(%rbp),%rax0x0000000000401559 <+41>:	mov    -0x4(%rbp),%edx0x000000000040155c <+44>:	mov    %edx,(%rax)0x000000000040155e <+46>:	add    $0x10,%rsp0x0000000000401562 <+50>:	pop    %rbp0x0000000000401563 <+51>:	retq   
 3.1.3.2 引用传参

         C++源码见3.1.2.2节。汇编代码如下。

   0x0000000000401598 <+0>:	push   %rbp0x0000000000401599 <+1>:	mov    %rsp,%rbp0x000000000040159c <+4>:	sub    $0x30,%rsp0x00000000004015a0 <+8>:	mov    %ecx,0x10(%rbp)0x00000000004015a3 <+11>:	mov    %rdx,0x18(%rbp)0x00000000004015a7 <+15>:	callq  0x40e7b0 <__main>0x00000000004015ac <+20>:	movl   $0x14,-0x4(%rbp)0x00000000004015b3 <+27>:	movl   $0x1e,-0x8(%rbp)0x00000000004015ba <+34>:	lea    -0x8(%rbp),%rdx0x00000000004015be <+38>:	lea    -0x4(%rbp),%rax0x00000000004015c2 <+42>:	mov    %rax,%rcx0x00000000004015c5 <+45>:	callq  0x401564 <swap_ref(int&, int&)>0x00000000004015ca <+50>:	mov    $0x0,%eax0x00000000004015cf <+55>:	add    $0x30,%rsp0x00000000004015d3 <+59>:	pop    %rbp0x00000000004015d4 <+60>:	retq   0x0000000000401564 <+0>:	push   %rbp0x0000000000401565 <+1>:	mov    %rsp,%rbp0x0000000000401568 <+4>:	sub    $0x10,%rsp0x000000000040156c <+8>:	mov    %rcx,0x10(%rbp)0x0000000000401570 <+12>:	mov    %rdx,0x18(%rbp)0x0000000000401574 <+16>:	mov    0x10(%rbp),%rax0x0000000000401578 <+20>:	mov    (%rax),%eax0x000000000040157a <+22>:	mov    %eax,-0x4(%rbp)0x000000000040157d <+25>:	mov    0x18(%rbp),%rax0x0000000000401581 <+29>:	mov    (%rax),%edx0x0000000000401583 <+31>:	mov    0x10(%rbp),%rax0x0000000000401587 <+35>:	mov    %edx,(%rax)0x0000000000401589 <+37>:	mov    0x18(%rbp),%rax0x000000000040158d <+41>:	mov    -0x4(%rbp),%edx0x0000000000401590 <+44>:	mov    %edx,(%rax)0x0000000000401592 <+46>:	add    $0x10,%rsp0x0000000000401596 <+50>:	pop    %rbp0x0000000000401597 <+51>:	retq   
3.1.3.3 汇编代码比较

        我们使用Compare工具比较一下两者的汇编代码。如下图所示。

        可以看到,它们的汇编代码实现方法是一样的。

3.1.3.4 结论

        由汇编代码比较结果可知,指针传参和引用传参在汇编实现上是一样的。

        所以,引用本质上也是指针,为了实现它也要分配空间存储变量地址。

       因为C++并没有规定汇编语言如何实现引用,它只是提出一个逻辑上的概念,具体实现不在C++语言本身。

四.引用的本质

        引用本质上是对一个const类型指针的封装,如下:

int  a = 10;int &b = a;
等价于
int *const b = &a;

         引用有如下特点:

1.引用没有定义,是一种关系型声明。声明它和原有某一变量(实体)的关系。
2.引用的类型与原类型保持一致,且不分配内存。与被引用的变量有相同的地址。
3.声明的时候必须初始化,一经声明,不可变更。
4.可对引用,再次引用。多次引用的结果,是某一变量具有多个别名。

        总的来说,引用的特征要放在C++语言层面去理解,由编译器负责实现这些特征。不能将引用放在其对应的汇编实现里去理解,否则会产生困惑。

       也可以换一个角度理解:引用传参时分配的空间是给一个临时变量的,而不是给引用类型变量的(这就对应了不分配内存的特征)。编译器为了实现引用,自动产生了一个临时指针变量。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/783947.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

P6学习:Oracle Primavera P6 OBS/责任人解析

前言 Primavera P6 EPPM 责任人用于管理 P6 企业项目组合管理 (EPPM) 系统中的项目所有权和权限。 Primavera P6 EPPM 中的所有项目都至少围绕三个结构进行组织&#xff1a;称为企业项目结构 (EPS) 的用于组织项目的结构、称为工作分解结构 (WBS) 的用于组织项目内活动的结构…

08:HAL---通用定时器功能(输入捕获功能)

前言&#xff1a; 下面的以通用定时器为例&#xff0c;当然高级定时器具有通用定时器的全部功能 ICP1S&#xff1a;上面经过分频后的信号&#xff1b;这里的捕获指的是产生一个捕获事件。 一&#xff1a;输入捕获功能 1:简历 IC&#xff08;Input Capture&#xff09;输入…

Multi-task Lung Nodule Detection in Chest Radiographs with a Dual Head Network

全局头增强真的有用吗&#xff1f; 辅助信息 作者未提供代码

媒体偏见从何而来?--- 美国MRC(媒体评级委员会)为何而生?

每天当我们打开淘宝&#xff0c;京东&#xff0c;步入超市&#xff0c;逛街或者逛展会&#xff0c;各种广告铺天盖地而来。从原来的平面广告&#xff0c;到多媒体广告&#xff0c;到今天融合AR和VR技术的数字广告&#xff0c;还有元宇宙虚拟世界&#xff0c;还有大模型加持的智…

LangChain入门:2.OpenAPI调用ChatGPT模型

引言 在本文中&#xff0c;我们将带您深入探索如何通过OpenAPI与ChatGPT模型进行高效交互&#xff0c;实现智能文本问答功能。通过LangChain库的实践&#xff0c;您将学习构建一个能够与用户进行自然语言对话的系统的关键步骤。 准备步骤 在动手编码之前&#xff0c;请确保您…

八、大模型之Fine-Tuning(1)

1 什么时候需要Fine-Tuning 有私有部署的需求开源模型原生的能力不满足业务需求 2 训练模型利器Hugging Face 官网&#xff08;https://huggingface.co/&#xff09;相当于面向NLP模型的Github基于transformer的开源模型非常全封装了模型、数据集、训练器等&#xff0c;资源…

[Windows]修改默认远程端口3389

文章目录 注册表编辑器找到注册信息找到端口配置修改端口重启远程连接服务远程连接 因为不想使用windos默认远程3389端口&#xff0c;所以考虑换成其他的端口。保证安全&#xff08;虽然windows不是那么安全&#xff09;。 注册表编辑器 windos搜索注册表编辑器 找到注册信息…

网上国网App启动鸿蒙原生应用开发,鸿蒙开发前景怎么样?

从华为宣布全面启动鸿蒙生态原生应用一来&#xff0c;各种各样的新闻就没有停过&#xff0c;如&#xff1a;阿里、京东、小红书……等大厂的加入&#xff0c;而这次他们又与一个国企大厂进行合作&#xff1a; 作为特大型国有重点骨干企业&#xff0c;国家电网承担着保障安全、经…

HAL库 USART通讯

1、UASRT简介 串口通讯 (Serial Communication) 是一种设备间非常常用的串行通讯方式&#xff0c;因为它简单便捷&#xff0c;因此大部分电子设备都支持该通讯方式&#xff0c;电子工程师在调试设备时也经常使用该通讯方式输出调试信息。 按位发送和接收的接口。如RS-232/422/…

C语言-文件操作

&#x1f308;很高兴可以来阅读我的博客&#xff01;&#x1f31f;我热衷于分享&#x1f58a;学习经验&#xff0c;&#x1f3eb;多彩生活&#xff0c;精彩足球赛事⚽&#x1f517;我的CSDN&#xff1a; Kevin ’ s blog&#x1f4c2;专栏收录&#xff1a;C预言 1. 文件的作用 …

【核弹级软安全事件】XZ Utils库中发现秘密后门,影响主要Linux发行版,软件供应链安全大事件

Red Hat 发布了一份“紧急安全警报”&#xff0c;警告称两款流行的数据压缩库XZ Utils&#xff08;先前称为LZMA Utils&#xff09;的两个版本已被植入恶意代码后门&#xff0c;这些代码旨在允许未授权的远程访问。 此次软件供应链攻击被追踪为CVE-2024-3094&#xff0c;其CVS…

基于SSM远程同步课堂系统

基于SSM远程同步课堂系统的设计与实现 摘要 在这样一个网络数据大爆炸的时代&#xff0c;人们获取知识、获取信息的通道非常的多元化&#xff0c;通过网络来实现数据信息的获取成为了现在非常常见的一种方式&#xff0c;而通过网络进行教学&#xff0c;在网络上进行远程的课堂…

vue3 视频播放功能整体复盘梳理

回顾工作中对视频的处理&#xff0c;让工作中处理的问题的经验固化成成果&#xff0c;不仅仅是完成任务&#xff0c;还能解答任务的知识点。 遇到的问题 1、如何隐藏下载按钮&#xff1f; video 标签中的controlslist属性是可以用来控制播放器上空间的显示&#xff0c;在原来默…

视觉里程计之对极几何

视觉里程计之对极几何 前言 上一个章节介绍了视觉里程计关于特征点的一些内容&#xff0c;相信大家对视觉里程计关于特征的描述已经有了一定的认识。本章节给大家介绍视觉里程计另外一个概念&#xff0c;对极几何。 对极几何 对极几何是立体视觉中的几何关系&#xff0c;描…

LeetCode刷题【链表,图论,回溯】

目录 链表138. 随机链表的复制148. 排序链表146. LRU 缓存 图论200. 岛屿数量994. 腐烂的橘子207. 课程表 回溯 链表 138. 随机链表的复制 给你一个长度为 n 的链表&#xff0c;每个节点包含一个额外增加的随机指针 random &#xff0c;该指针可以指向链表中的任何节点或空节…

搜索与图论——Floyd算法求最短路

floyd算法用来求多源汇最短路 用邻接矩阵来存所有的边 时间复杂度O(n^3) #include<iostream> #include<cstring> #include<algorithm>using namespace std;const int N 20010,INF 1e9;int n,m,k; int g[N][N];void floyd(){for(int k 1;k < n;k ){f…

C++从入门到精通——引用()

C的引用 前言一、C引用概念二、引用特性交换指针引用 三、常引用保证值不变权限的方法权限的放大权限的缩小权限的平移类型转换临时变量 四、引用的使用场景1. 做参数2. 做返回值 五、传值、传引用效率比较值和引用的作为返回值类型的性能比较 六、引用和指针的区别引用和指针的…

AI算法中的关键先生 - 反向转播与戴维莱姆哈特

0. 引言 机器学习的自动推导过程中有一个关键步骤&#xff0c;就是自动求解过程的参数反向传播过程&#xff0c;这个工作据说是这个人做的&#xff1a; Remembering David E. Rumelhart (1942-2011) – Association for Psychological Science – APSAPS Fellow and Charter …

CDR2024软件免费版 CDR绘制矢量图形插画 平面设计CDR教程 cdr2024注册机 cdr2024安装包百度云 cdr快捷键

CDR2024软件免费版是由加拿大Corel公司研发的平面设计类软件。CDR2024软件免费版应用的行业非常广泛&#xff0c;影视动画、网页设等行业都可以应用此软件。CDR2024软件免费版为专业工作者提供了更加精细的操作功能&#xff0c;用户可以更加精确的对图片进行修改&#xff0c;降…

【MySQL】DQL-查询语句全解 [ 基础/条件/分组/排序/分页查询 ](附带代码演示&案例练习)

前言 大家好吖&#xff0c;欢迎来到 YY 滴MySQL系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C Linux的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的…