【C++】揭开“引用”的庐山真面目

news2024/10/7 8:30:45

目录

一、引用的概念

二、引用的应用

1.特性

2.使用场景

2.1 引用作为函数参数

2.2 引用作为函数返回值

三、引用的权限问题

四、引用和指针的区别


一、引用的概念

        引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。

        比如,水浒传里面的李逵,绰号“黑旋风”、“铁牛”。那么 “李逵” 是他的本名,但是叫他 “黑旋风” 也可以,叫他 “铁牛” 也可以,这三个名号说的都是同一个人。

        引用的语法如下,值得注意的是,引用类型和引用实体必须是同一种类型才可以:

类型& 引用变量名(对象名) = 引用实体

         如下,就是引用的一个例子, ra 是 a 的一个别名。我们也可以从内存的角度来理解引用,下方图片就很好地解释了什么是引用,原本只有一个变量a, ra 是 a 的引用,那么 ra 和 a一样,也代表这一块空间,并没有新开辟一个空间,ra也不是一个指针。

int a = 10;
int& ra = a;

二、引用的应用

1.特性

        要使用引用,首先要了解它的一些特性,熟悉使用规则。

1. 引用在定义时必须初始化。
2. 一个变量可以有多个引用。
3. 引用一旦引用一个实体,再不能引用其他实体。

2.使用场景

2.1 引用作为函数参数

        如下,再写 Swap 函数的时候,就可以使用引用了。下方代码,left 、right 虽然是形式参数,但都是实参的别名,即使是在函数内部改变了这两个变量的值,实参也会相应地改变。

void Swap(int& left, int& right)
{
  int temp = left;
  left = right;
  right = temp;
}

        当然了,既然把引用作为形参,在函数内部可以直接改变实参的值,那么对于无头链表,之前插入数据的时候要考虑:如果链表没有数据,插入就要改变指针的值,所以要传二级指针。在这里直接 引用 就可以不用二级指针,如下代码,phead 是 Node* 类型的一个引用。

void PushBack(Node*& phead, int x)
{
  Node* newhead = (Node*)malloc(sizeof(Node));
  if (!phead)
  {
	 phead = newhead;
  }
}

2.2 引用作为函数返回值

        引用作函数返回值可是有点说头了,比如,下面代码输出结果会是什么?

int& Add(int a, int b)
{
  int c = a + b;
  return c;
}
int main()
{
  int& ret = Add(1, 2);
  Add(3, 4);
  cout << "Add(1, 2) is :"<< ret <<endl;
  return 0;
}

        答案是:不确定。为什么?(要先有预备知识,明白函数调用的过程中发生了什么:函数栈帧的创建和销毁)我们可以通过下面这张图来加深理解:首先,调用Add函数的时候(下图左边),在Add函数栈帧的内部创建了 c 这个变量,为其开辟了一块空间。当 Add 函数调用结束(下图右边),Add的函数栈帧自然也销毁了,对应的内存空间还给了操作系统,但是,这块空间并不是不存在了,它依然在那里,地址也没有改变,只是这块空间里面的数据是否被操作系统设置成随机值了,我们并不知道。所以这一块地址里的数据,有可能和销毁前一样,也有可能是随机值。这就是答案是 “不确定” 的原因。

        由于这块空间的地址是没有改变的,所以只要知道变量 c 的位置,就可以得到原本变量c 那一块空间的值。Add 函数返回了 c 的引用,由 ret 接收,那么 ret 和 c 代表的其实是同一块空间,所以可以通过 ret 来查看 c 那一块空间的值。(如果不了解 函数的返回值是如何传递的,可以看上面所说的预备知识,里面有讲。)

        而由于 main 函数中,两次调用了 Add 函数,所以第二次调用的时候, ret 对应空间的值被修改成了 7 ,调用结束之后,那块空间是否被设置成随机值要看操作系统。所以输出结果有可能是 7,也有可能是随机值。 

        因为这样的问题,所以我们就不可以使用 引用 作为函数的返回值了吗?当然不是,上面讲到的变量 c 不适合作返回值,是因为变量 c 对应的空间,在函数调用结束之后,就不受控制了,还给操作系统了。那如果函数内部定义了一个变量 x ,在该函数调用结束之后,x 依然存在,且不会被还给操作系统,不会有任何修改,那么我们就可以将 x 作为返回值。

        比如下面一段代码,n 虽然是在Count 函数内部创建,但是却被 static 修饰n 在静态区,所以即使函数调用结束, n 依然存在,将其作为返回值,不会造成bug。

int& Count()
{
  static int n = 0;
  n++;
  return n;
}

        所以,对于引用作为函数的返回值,可以有一下一点点总结:

        如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用
引用返回,如果已经还给系统了,则必须使用传值返回。

三、引用的权限问题

        并不是什么情况下都可以对变量进行引用的,比如下方情况,编译器就会报错。 c 是被 const 修饰的 int 类型的变量,但是 d 这个引用并没有用 const 修饰,假设引用没有问题,那么改变 d 的值也就是改变 c 的值,但是 c 被const修饰,不可以改变,所以矛盾。
        这就是权限放大,将原本不可以改的变量(权限小),引用后可以修改(权限大),这样使用肯定是不可以的。(指针也是同理)

        所以,在引用的时候,只可以权限平移或者权限缩小,不可以进行权限放大。

// 权限平移
  int a = 10;
  int& b = a;

// 权限缩小
  int x = 10;
  const int& y = x; 

        但是这里要进行概念区分,权限的放大和缩小,只适用于引用和指针,因为引用是起一个别名,b的改变会影响a,指针也会改变指向的内存里的数据。但是如下设计,m、n是两块不同的空间,也就不存在上述问题。

const int m = 10;
int n = m; // 把 m 的值赋给 n

        我们再来理解一个例子,首先要清楚下方代码中,(double)i 并不是将变量 i 转化成double 类型的变量,而是把 i 放到一个 double 类型的临时变量里面,而 i 依然是 int 类型的变量。例如 double k = (double) i ;  这样子,k是double 类型的变量,但是 i 不是。

int i = 10;
cout << (double)i << endl; 

        再如下方,这两行代码是没有问题的,只不过把 i 放到临时变量里面的过程省略了而已。

int i = 10;
double dd = i; 

        但是,如下图,如果设计一个 double 类型的引用,会报错,这是为什么?是因为double  和  int 是不同类型吗?如果是, i 不是会进行强制类型转换,放到临时变量里面吗?考虑到这里,其实已经八九不离十,就是因为临时变量的存在!临时变量有一个性质——常性,这个“常”是常量的“常”,也就是说,临时变量也是不可修改的,由于等号右边是不可修改的临时变量,左边是可修改的引用,那就相当于权限放大,所以编译错误了

        所以,让左边的引用也不可修改就好,加上一个const 就可以通过了,如下图的代码。同时,我们还可以用调试来证明 i 是被放到一个double类型的临时变量里的,下图监视窗口可以看出, i 和 ll  的地址是不一样的,引用应该是同一块空间才对,所以这里的引用并不是引用 i ,而是对临时变量进行引用

四、引用和指针的区别

        引用在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。但是引用在底层实现上实际是有空间的,因为引用是按照指针方式来实现的(我们使用的时候不必关心这点)。可以通过汇编代码来验证,如下,VS环境下将C语言代码转换成汇编代码,可以看到对 a 进行引用和设计指针,其底层实现都是一样的。

引用和指针的不同点:

1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
2. 引用在定义时必须初始化,指针没有要求。
3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体。
4. 没有NULL引用,但有NULL指针。
5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)。
6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小。
7. 有多级指针,但是没有多级引用。
8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理。
9. 引用比指针使用起来相对更安全。

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

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

相关文章

【数据结构之二叉树简介·顺序存储·应用:堆·堆排序·TOPK问题】

​ &#x1f57a;作者&#xff1a; 迷茫的启明星 &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f383;相关文章 【数据结构从0到1之树的初识】 &#x1f3c7;家人们&#xff0c;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64…

Kotlin SharedFlowStateFlow 热流到底有多热?

前言 协程系列文章&#xff1a; 一个小故事讲明白进程、线程、Kotlin 协程到底啥关系&#xff1f;少年&#xff0c;你可知 Kotlin 协程最初的样子&#xff1f;讲真&#xff0c;Kotlin 协程的挂起/恢复没那么神秘(故事篇)讲真&#xff0c;Kotlin 协程的挂起/恢复没那么神秘(原理…

50条必背JAVA知识点(二)

16.强制类型转换&#xff1a;将容量大的数据类型转换为容量小的数据类型&#xff0c;但可能造成精度降低或溢出。 17.字符串不能直接转换为基本类型&#xff0c;但通过基本类型对应的包装类则可以实现把字符串转换成基本类型。 18.计算机底层都以二进制补码的方式来存储数据。…

将现实问题转换为编程问题

将现实问题转换为编程问题需要转换思维&#xff0c;不过孰能生巧&#xff0c;见多了就自然懂如何做了&#xff0c;所以动起手来是决没错的。1.猜名次问题改进一&#xff1a;改进二&#xff1a;改进三&#xff1a;2.猜凶手问题总结&#xff1a;1.猜名次问题 每个选手都说了两句话…

深入浅出学习透析Nginx服务器的架构分析及原理分析「底层技术原理+运作架构机制」

Nginx再次回顾 也许你已经忘记了Nginx是做什么的&#xff1f;我来再次给你夯实一下概念。 多协议反向代理 Nginx是个高性能的Web和反向代理服务器及HTTP服务器&#xff0c;它能反向代理HTTP&#xff0c;HTTPS和邮件相关(SMTP&#xff0c;POP3&#xff0c;IMAP)的协议链接&am…

四十、Kubernetes1.25中安全认证详解

1、访问控制概述Kubernetes作为一个分布式集群的管理工具&#xff0c;保证集群的安全性是其一个重要的任务。所谓的安全性其实就是保证对Kubernetes的各种客户端进行认证和鉴权操作。客户端在Kubernetes集群中&#xff0c;客户端通常有两类&#xff1a;User Account&#xff1a…

视频剪辑必备的6个免费素材库~

视频剪辑必备素材&#xff0c;那自然是视频、配乐、音效啦&#xff0c;但最重要的还是内容&#xff0c;这些素材只是点缀。 那要如何获取素材&#xff1f;很多朋友应该都知道&#xff0c;网上很多素材版权不明确&#xff0c;使用不当就会造成侵权&#xff0c;找素材成为了一大…

电脑重装系统装不了如何解决

重装系统装不了如何解决&#xff1f;当电脑出现故障时&#xff0c;大部分人都会选择重装系统来解决这个问题&#xff0c;但是有人出现系统重装不了&#xff0c;下面小编就来为大家解决系统重装不了的问题。 工具/原料&#xff1a; 系统版本&#xff1a;win7 品牌型号&#xff…

为什么 B 站的弹幕可以不挡人物?

那天在 B 站看视频的时候&#xff0c;偶然发现当字幕遇到人物的时候就被裁切了&#xff0c;不会挡住人物&#xff0c;觉得很神奇&#xff0c;于是决定一探究竟。 高端的效果&#xff0c;往往只需要采用最朴素的实现方式&#xff0c;忙碌了两个小时&#xff0c;陈师傅打开了 F1…

Spring Boot(二):第一种导入依赖方式的实战案例

文章目录 第一种导入依赖方式的实战案例 一、导入依赖 二、依赖传递结构图 三、开发案例代码 第一种导入依赖方式的实战案例 一、导入依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0…

Android开发——HOOK技术【解析】

1. 什么是 Hook Hook 英文翻译过来就是「钩子」的意思&#xff0c;那我们在什么时候使用这个「钩子」呢&#xff1f;在 Android 操作系统中系统维护着自己的一套事件分发机制。应用程序&#xff0c;包括应用触发事件和后台逻辑处理&#xff0c;也是根据事件流程一步步地向下执…

前端算法之二分查找

在数组中查找指定元素,如果存在就返回它的位置,如果不存在,就返回-1。 这是一道非常经典的算法题&#xff0c;考的就是二分查找算法&#xff0c;首先分析二分查找的思路&#xff1a; 假设一个数组为 [3,5,19,22,25,33,45,47,57,66,71,78]&#xff08;已经从小到大排好序&…

dapr本地托管的服务调用体验与Java SDK的Spring Boot整合

1 简介 之前在文章《dapr入门与本地托管模式尝试》中介绍了dapr和本地托管&#xff0c;本文我们来介绍如果在代码中使用dapr的服务调用功能&#xff0c;并把它整合到Spring Boot中。 Dapr服务调用的逻辑如下&#xff1a; 本次实验会创建两个服务&#xff1a; pkslow-data&am…

2023华数杯B题社会稳定预警首版思路

文章目录2023华数杯B题社会稳定预警首版思路B题题目如下&#xff1a;2023华数杯B题社会稳定预警首版思路 这个思路对下面这五问有了非常详细的思路&#xff0c;并且提供了支持材料。对本次的比赛进度有很大的帮助。 思路下载&#xff1a; https://math.jobpig.top/?p237 B题题…

CentOS7升级OpenSSH9.2编译rpm包

以下步骤在联网的Centos 7环境下执行 1、 下载用于编译openssh的rpm包的工具 yum install -y rpm-build gcc gcc-c glibc glibc-devel openssl-devel openssl prce pcre-devel zlib zlib-devel make wget krb5-devel pam-devel libX11-devel xmkmf libXt-devel initscripts lib…

二、第二天

977.有序数组的平方力扣题目链接给你一个按 非递减顺序 排序的整数数组 nums&#xff0c;返回 每个数字的平方 组成的新数组&#xff0c;要求也按 非递减顺序 排序。示例 1&#xff1a; 输入&#xff1a;nums [-4,-1,0,3,10] 输出&#xff1a;[0,1,9,16,100] 解释&#xff1a;…

jackon.date-format 不生效

文章目录前言一、可能原因二、解决方案2.1、升级nacos&#xff08;可忽略&#xff09;2.2. 自定义日期反序列化格式总结前言 springboot 默认json 序列化使用jackon 正常配置jackon.date-formatyyyy-MM-dd HH:mm:ss 即可格式化日期格式&#xff0c;但是由于项目引用各种jar和拦…

直播 | StarRocks 实战系列第一期--部署导入

你今年的 Flag 定了吗&#xff1f;兔年开工&#xff0c;仪式感就从立 Flag 开始&#xff01;在技术上&#xff1a;兔飞猛进&#xff0c;能力吊打同行&#xff1b;在工作上&#xff1a;兔步青云&#xff0c;升职加薪获得领导赏识&#xff1b;在学习上&#xff1a;前兔无量&#…

总结继承和多态的一些问题

在学习了继承和多态后&#xff0c;本人有以下容易造成混乱的点以及问题&#xff1a; 1.区分虚表和虚基表 虚表即虚函数表&#xff0c;存储的是虚函数的地址。另外&#xff1a;虚表是在编译阶段就生成的&#xff0c;一般存在于常量区&#xff08;代码段&#xff09;。 虚基表…

Apache Spark 机器学习 特征转换 2

PCA&#xff08;Principal Component Analysis&#xff09; 该转换器是主成分分析方法&#xff0c;是统计学领域中对数据样本的正相关的转换与分析方法&#xff0c;在一批具有相关性的数据样本的数据集中&#xff0c;删除多余的重复的相关变量&#xff0c;得到少量具有信息代表…