【C++】初识引用

embedded/2024/9/20 7:36:31/ 标签: c++, java, android

目录

  • 概念
  • 引用的五大特性
    • 引用在定义时必须初始化
    • 一个变量可以有多个引用
    • 一个引用可以继续有引用
    • 引用了一个实体就不能再引用另一个实体
    • 可以对任何类型做引用(包括指针)
  • 引用使用的两种使用场景
    • 做参数
      • 交换两数
      • 单链表头结点的修改
    • 做返回值
    • 优化传递返回值
  • 常引用
    • 权限放大
    • 这时候进行权限持平
    • 权限缩小
    • 注意
    • 临时变量具有常属性
  • 引用与指针的区别
  • 引出引用的目的

概念

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

比如:李逵,在家称为"铁牛",江湖上人称"黑旋风"。【水浒108将各个有称号】

在这里插入图片描述

引用的操作符和c语言中的按位与或者取地址操作符相同

格式:

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

他的基本用法如:

int a = 10;
int& b = a;

在这里插入图片描述

  • 这里就突出我们前面说的,引用变量并没有为被引用对象重新开辟空间,而是同用一块空间

引用的五大特性

引用在定义时必须初始化

  • 首先来看第一个,若是定义了一个引用类型的变量int&,那么就必须要去对其进行一个初始化,指定一个其引用的对象,否则就会报错
int a = 10;
int& b = a;
int& c;

在这里插入图片描述

一个变量可以有多个引用

  • 对于第二个特定,通俗一点来说就是b引用了a,那么b等价于a;此时c也可以引用a,那么c也等价于a,此时a == b == c
int a = 10;
int& b = a;
int& c = a;

在这里插入图片描述
其实就相当是一个人有很多小名,比如我们在家里父母叫我们儿子,外面老师叫我们同学,你兄弟叫你兄弟,但是这些名字对应的都是一个人。

一个引用可以继续有引用

  • 对于第三个特性而言,其实就是一个传递性。当一个变量引用了另一个变量之后,其他变量还可以再对其进行一个引用。通过运行就可以看出它们也都是属于同一块空间
int a = 10;
int& b = a;
int& c = b;

在这里插入图片描述

引用了一个实体就不能再引用另一个实体

  • 我们对一个变量进行引用后就不能进行另外一个变量也进行引用了,所以我们引用必须初始化,初始化引用的变量就不能改变
int a = 10;
int c = 20;int& b = a;
int& b = c;

在这里插入图片描述

可以对任何类型做引用(包括指针)

int a = 10;
int* p = &a;int*& q = p;
  • 指针p存储了a的地址,q原本是一个指针加了一个&就是表示引用,对p这个指针就行引用,所以p就是q,q就是p,p指向a,q也指向a
    在这里插入图片描述

int*&的这个写法要认识一下

在这里插入图片描述

引用使用的两种使用场景

做参数

交换两数

  • 我们在C语言中学的交换两个数字通常需要在调用这个函数的时候传地址才能通过形参改变实参,那么在设计参数的时候就要把参数类型设计为指针才行
  • 但是在C++中我们有了引用就可以直接把参数类型设计为引用,然后调用的时候传变量就行了(这也算使用引用的目的)
  • 有人就问了,指针不一样吗?干嘛要弄些这些东西出来,是因为,指针变量也是需要开辟空间来存储地址的。但是引用不用,他是同一块地址。

指针:

void swap1(int* px, int* py)
{int t = *px;*px = *py;*py = t;
}

引用:

void swap2(int& x, int& y)
{int t = x;x = y;y = t;
}
swap2(a,b)//只需要进行传值调用

单链表头结点的修改

在讲解引用的特性时,我说到了引用的类型不仅仅限于普通变量,还可以是指针。但上面说的是普通指针,接下去我们来说说结构体指针,也涉及到了引用类型在做参数时的场景

typedef struct SingleNode {struct SingleNode * next;int val;
}SLNode;void PushFront(SLNode** SList, int x)
{SLNode* newNode = BuyNode(x);newNode->next = *SList;*SList = newNode;
}int main(void)
{SLNode* slist;PushFront(&slist, 1);return 0;
}
  • 在以前学习单链表的时候,要想改变实参,我们必须要使用二级指针来接受一级指针来改变
  • 但现在学习了引用之后,我们就不需要去关心传入什么指针的地址了,只需要将这个链表传入即可,在函数形参部分对其做一个引用,那么内部的修改也就一同带动了外部的修改
void PushFront(SLNode*& SList, int x);
  • 此时PushFront()内部我们也可以去做一个修改,直接使用形参SList即可,无需考虑到要对二级指针进行解引用变为一级指针
void PushFront(SLNode*& SList, int x)
{SLNode* newNode = BuyNode(x);newNode->next = SList;SList = newNode;
}

最后再补充一下:很多教材书并不是上面用引用的写法而是:

typedef struct SingleNode {struct SingleNode * next;int val;
}SLNode, *PNode;

这里的*PNode就是:

struct SingleNode*做了一个typedef,也就是对这个结构体指针的类型做了一个重命名叫做【PNode】,那后面如果要使用这个结构体指针的话直接使用的【PNode】即可

typedef struct SingleNode* PNode

于是对于头插的形参部分又可以写成下面这种形式,与SLNode*& SList是等价的

void PushFront(PNode& SList, int x);

做返回值

我们在设计函数的时候,有很多函数要进行返回一个值。那么他是如何返回一个值的呢?

进入正题前我们要补充一点:
只有是建立函数就要开辟函数栈帧,他一般是存放在栈区。当调用完一个函数后就要对这块函数栈帧进行销毁。销毁了还怎么返回呢?所以他返回是先把返回值存储到一个临时变量中假设这个临时变量是ret

如:

int Count()
{int n = 0;n++;// ...return n;
}

在这里插入图片描述
还需要注意一点的是,编译器他不管你是存储在栈,或者是存储在静态区里,他都是用临时变量进行返回

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

在这里插入图片描述
注意:

  • 当我们定义变量 / 创建函数 / 申请堆内存的空间时,系统会把这块空间的使用权给到你💪,那么这块空间你在使用的时候是被保护的🛡,被人无法轻易来访问、入侵你的这块空间。但是当你将这个空间销毁之后,它并不是不存在了、被粉碎了,只是你把对于这块空间的使用权还给操作系统了,不过这块空间还是存在的,因此你可以通过某种手段访问到这块空间🗡,由于操作系统又收回了这块空间的使用权,继而它便可以对其进行再度分配给其他的进程,那它就可能又属于别人了
  • 所以你通过某种手段去访问这个空间的时候其实属于一种非法访问⚠,可是呢这种非法访问又不一定会报错,就像之前我们说到过的数组越界、访问野指针都不一定会存在报错。为什么?因为编译器对于程序的检查是一种【抽查行为】,不一定能百分百查到,所以你在通过某些手段又再次访问到这块空间后所做的一些事都是存在一种【随机性】的

注意:

若是返回空间小一点的变量时使用的就是【寄存器】
若是返回空间大一点的内容时使用的就是【临时变量】,这个临时变量会提前在main函数栈帧中提前开好

  • 总结:当需要将函数中的临时变量返回时,无论这个变量是在栈区、堆区或者静态区开辟空间,都会通过一个临时变量去充当返回值【小一点的话可能是寄存器eax,大一点可能是在上一层栈帧开好的】然后再返回给外界的值做接受

优化传递返回值

有人说,这样编译器太傻了,静态区的变量是整个程序结束才销毁,编译器还要使用临时变量来存储。真是浪费空间。

  • 那有什么办法可以免去这种拷贝的过程,直接将得出的结果返回回去呢?那就是引用返回
int& Count()
{static int n = 0;n++;// ...return n;
}
  • 这样写编译器就不会把返回值存储到一个临时变量中进行返回了,而是直接返回n的别名。又因为引用是和被引用的对象同用一块地址,所以说他不会进行内存的拷贝。(这也是用引用的目的之一)
    在这里插入图片描述

对于传引用返回除了可以减少拷贝之外,我们还可以通过去接收这个返回值去修改返回的对象

  • 这里举一个例子,对于顺序表而言我们在数据结构中有学习过,现在是要去修改固定位置上的值,这里如果使用C语言来实现的话就会比较繁琐,首先我们要先去获取到这个位置上的值,然后再对这个值进行修改,分别为SLGet()和SLModify()函数
typedef struct SqList {int a[100];size_t sz;
}SeqList;// 获取当前位置上的数据
int SLGet(SeqList* ps, int pos)
{assert(pos >= 0 && pos < 200);return ps->a[pos];
}// 修改当前位置上的值
void SLModify(SqList* ps, int pos, int x)
{assert(pos >= 0 && pos < 200);ps->a[pos] = x;
}
  • 那么此时当我们需要将0这个位置上的值 + 5的话就需要像下面这样去进行调用,你是否觉得这样非常繁琐呢?
SqList s;
// 对第0个位置的值 + 5
int ret = SLGet(&s, 0);
SLModify(&s, 0, ret + 5);

此时当我们学习了引用之后就可以将代码修改成下面这样,将当前pos的值直接使用引用返回,那么外界在进行接收时相当于为其取了一个别名,此时再去操作的话就相当于是在操作这一块上的值

int& PostAt(SqList& s, int pos)
{assert(pos >= 0 && pos < 200);return s.a[pos];
}
  • 在进行调用的时候就可以写成这样下面这样,形参部分也是因为有了引用所以不需要传递地址。而且我这一个函数代替了上面的两个函数,具备【查找】和【修改】的功能
PostAt(s, 0) = 1;
cout << PostAt(s, 0) << endl;
PostAt(s, 0) += 5;
  • 总结:返回值引用的好处就是减少了空间的拷贝,而且调用者可以轻易获取并修改返回值

但是要注意:

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;
}
  • 虽然我们这个函数的返回类型是一个引用,就相当于是把c的别名返回给main函数栈帧中,但是C这个变量是在Add函数栈帧中创建的,在调用完Add函数后,这块函数栈帧已经销毁,我们没有再对这块空间使用的权限。

这个时候我们再去打印:
在这里插入图片描述
就会打印一个随机值。

  • 现在你要知道的是因为Add函数做了一个【引用返回】,即返回了c的别名,但此时呢ret又使用引用接收了c的引用,所以可以说【ret又引用了c的引用】,那此时也就可以说ret与c就融为一体了,那么ret也就是c这块空间的别名

这时候ret就还和被销毁Add函数栈帧中的C一样的空间。
最后打印一个随机值

  • 为什么呢?这一点我在上面也有提到过。因为当Add函数栈帧销毁的时候,其空间还是在的,只是使用权不是你的了,可是呢它被操作系统回收了,操作系统就还可以把它分配给其他进程,那此时就可以说这块空间被重复利用了,下一次的函数调用可能还是在这块空间上建立栈帧,但是上一次的栈帧是否清理取决于编译器,可能清理了,也可能没清理

常引用

权限放大

int a = 1;
int& b = a;const int c = 2;
int& d = c;

b对a的引用没有什么问题,但是d对c的引用就有问题了。const关键字我们在C语言中提到过,被const修饰的变量是不能修改的。原变量C被const修饰不能修改了。但是d对C这个变量进行引用,他们就是同一块内存,他前面可没有const修饰,所以具有修改权限。

  • 这就违规了,我原来的变量都没有权利,你一个复制品凭什么有权利(也可以想象原变量是管理员,别名是用户,用户的权限不能超过管理员)

在这里插入图片描述

这时候进行权限持平

  • 那我们修改一下,不要让权限放大了,给变量d也加上一个常属性,让他俩一样,看看是否可行
const int c = 2;
const int& d = c;

权限缩小

  • 那既然权限保持不会出现问题,若是我现在将权限缩小会不会出现问题呢?
int c = 2;
const int& d = c;
  • 可以看到,也是不会出现问题。你呢允许我修改,但是我加上了常属性不去修改,那也是说得通的
    在这里插入图片描述

注意

const int m = 1;
int n = m;		//普通变量不受约束

有人看到,哈哈这一定是权限放大。绝对会报错。

  • 答案是错的,我们回顾引用特性,引用是对一个变量取别名,所以他没有创建一块新的内存空间。而是和原变量同一块内存空间

可是这里m和n是两块内存空间,就不会存在权限冲突

也就是说:对于权限放大只适用于引用和指针类型(他们都是 指向同一块内存空间)

临时变量具有常属性

需要注意的是类似 int& rb = a3; double d = 12.34; int& rd = d; 这样⼀些场
景下a
3的和结果保存在⼀个临时对象中, int& rd = d 也是类似,在类型转换中会产⽣临时对
象存储中间值,也就是时,rb和rd引⽤的都是临时对象,⽽C++规定临时对象具有常性,所以这⾥
就触发了权限放⼤,必须要⽤常引⽤才可以。

// 编译报错: “初始化”: ⽆法从“int”转换为“int &”
// int& rb = a * 3;
const int& rb = a*3;
double d = 12.34;
// 编译报错:“初始化”: ⽆法从“double”转换为“int &”
// int& rd = d;
const int& rd = d;

引用与指针的区别

  • 在学习了这么多有关引用的知识之后,相信读者也知道了。在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间
int a = 10;
int& ra = a;
cout<<"&a = "<<&a<<endl;
cout<<"&ra = "<<&ra<<endl;
  • 而对于指针来说,指针变量本身需要开辟一块内存空间来存储别人的地址
int a = 10;
int* pa = &a;

不过从【汇编层面】来看,其实二者是一样的,引用也是用指针去实现的,也会开空间

int main()
{int a = 10;// 语法层面:不开空间,是对a取别名int& ra = a;ra = 20;// 语法层面:开空间,存储a的地址int* pa = &a;*pa = 20;return 0;
}
  • 通过将这段代码转到【反汇编】,就可以发现引用和指针在底层的实现竟然是一样的
    在这里插入图片描述
  • 指针的可以指向另一个变量的空间,而引用只能指向初始化的空间

引出引用的目的

总结一下:

  • 引用本质上就是对指针的功能进行了复制。让程序员在进行操作的时候更加方便安全(如作为参数类型来交换两个数)
  • 还有一个帮助就是如果只使用一个变量就不必拷贝内存到形参

http://www.ppmy.cn/embedded/90112.html

相关文章

UWB定位技术原理及应用

UWB(超宽带)定位技术是一种基于超短脉冲信号的无线通信技术&#xff0c;具有高精度、低功耗和高安全性等特点。其主要原理是利用超短脉冲信号的时间测距&#xff0c;通过计算信号在空中飞行的时间来确定目标的位置。具体来说&#xff0c;UWB定位技术可以通过以下几种方法实现&a…

【Windows下搭建后台】Idea + Maven + JDK

Windos环境下搭建后台 一. IDEA1.1 下载1.2 安装 二. Apache Maven2.1 下载2.2 配置2.3 在IDEA中应用Maven 三. JDK3.1 下载3.2 安装2.3 配置3.4 验证3.5 在IDEA中应用 附 Windows10 IDEA ( 2024.1.4 ) Maven&#xff08;5.9.6&#xff09; JDK (11) 一. IDEA 1.1 下载 进入…

使用MySQLdump定时备份数据库实战

使用MySQLdump定时备份数据库实战 要使用mysqldump进行数据库的定时备份,你可以使用Linux系统的cron工具来设置定时任务。以下是一个简单的例子,展示如何每天凌晨1点自动备份名为mydatabase的MySQL数据库到/var/backups/mysql目录。 首先,你需要创建一个备份脚本。假设你的…

大模型LLM关键技术手段

大语言模型&#xff08;LLM&#xff09;是人工智能领域的一个突破性进展&#xff0c;它通过多种技术手段实现对自然语言的理解和生成。用比较通俗的话来列举一些我认为比较关键的技术手段&#xff1a; 深度学习技术&#xff1a;就像我们通过不断学习来掌握知识一样&#xff0c;…

云计算场景下数据恢复的挑战

基于Keepit A/S委托IDG Communications, Inc. dba Foundry进行的一项调查以及Keepit进行的深入访谈研究。这些研究揭示了数据恢复&#xff08;DR&#xff09;策略中的关键差距&#xff0c;并突出了加强数据安全措施的紧迫性。 ### 调查背景 随着云应用和生成式AI技术的迅速普及…

TongHttpServer 简介

1. 概述 随着网络技术的飞速发展,高并发大用户场景越来越普遍,单一应用服务节点已经不能满足并发需求,为了提高整个系统可靠性,扩展性,吞吐率,通常将多个应用服务器通过硬负载/软负载组成集群,负载均衡器根据不同负载算法将请求分发到各个应用服务器节点。 Tong…

《Milvus Cloud向量数据库指南》——什么是二进制嵌入?

引言 向量嵌入在现代机器学习和数据科学中已成为不可或缺的工具,它们能够将复杂数据以算法可以理解的数值格式表示。尽管密集嵌入因其能够以最小的信息损失保留语义含义而普遍存在,但随着数据量的增加,它们的计算需求和内存需求也在增加。这种增加促使开发者寻求更高效的数…

ETL工程师角度下的SQL优化

作为ETL&#xff08;Extract, Transform, Load&#xff09;工程师&#xff0c;SQL优化是提高数据处理和分析效率的关键一环。优化SQL查询可以显著降低数据处理时间&#xff0c;提高ETL过程的性能。本文将从 合理设计数据模型&#xff1a;在ETL过程中&#xff0c;正确的数据模型…

Vue-router的编程式导航有哪些方法?

Vue-router 提供了编程式导航的方法&#xff0c;这些方法允许你以编程的方式控制路由的跳转&#xff0c;而不是依赖于用户点击 <router-link> 组件。以下是 Vue-router 中常用的编程式导航方法&#xff1a; router.push(location, onComplete?, onAbort?) location&am…

机械学习—零基础学习日志(高数19——函数极限理解深化)

零基础为了学人工智能&#xff0c;真的开始复习高数 本次学习笔记&#xff0c;主要讲解函数极限的计算问题。 极限四则运算规则 这里有几个需要注意的地方。函数极限的四则运算&#xff0c;需要知道极限存在才能大胆放心的使用。而且使用超实数的概念会更好帮助我们理解&…

东方博宜1309 - 最多能倒多少杯水

问题描述 花花所在的学校引入了电水箱为同学们烧开水。 已知电水箱的容量为 n 升&#xff08; n≤10L &#xff09;&#xff0c;同学们带的杯子平均容量为 x 毫升&#xff08; x 在 100∼300 之间&#xff09;&#xff0c;请问烧一箱开水&#xff0c;最多能倒多少杯&#xff0…

fastadmin插件市场暂不可用,是否切换到本地插件

今天调试时需要安装一个富文本插件&#xff0c;结果在插件管理模块提示如下错误&#xff1a; 经过参考网上资料&#xff0c;最终解决方案&#xff1a; 修改backend/config目录下&#xff0c;fastadmin.php 中代码&#xff1a; //API接口地址 api_url > https://api.iuok.c…

【研发日记】Matlab/Simulink技能解锁(十二)——Stateflow中的两种状态机嵌套对比

文章目录 前言 项目背景 两级状态机 函数状态机 分析和应用 总结 参考资料 前言 见《【研发日记】Matlab/Simulink技能解锁(七)——两种复数移相算法》 见《【研发日记】Matlab/Simulink技能解锁(八)——分布式仿真》 见《【研发日记】Matlab/Simulink技能解锁(九)——基…

白骑士的PyCharm教学高级篇 3.1 性能分析与优化

系列目录 上一篇&#xff1a; 在软件开发中&#xff0c;性能分析与优化是提高程序运行效率和用户体验的重要环节。PyCharm提供了强大的性能分析工具&#xff0c;帮助你识别和优化代码中的性能瓶颈。本文将详细介绍PyCharm中的代码性能分析工具和内存使用优化建议&#xff0c;帮…

Astro 实现TodoList网页应用案例

Astro 是一个现代化的静态站点生成器和前端框架&#xff0c;它具有独特的设计理念&#xff1a;岛屿架构。它允许开发人员使用组件化的方式构建内容优先的网站&#xff0c;将各种技术栈&#xff08;如React、Vue、Svelte等&#xff09;的组件无缝集成到同一个项目中。 1、创建项…

设计模式——责任链模式

设计模式——责任链模式 一、责任链模式的概念二、责任链模式的结构三、责任链模式的优势四、Java 中责任链模式的实现示例五、责任链模式的应用场景六、总结 在 Java 编程中&#xff0c;责任链模式是一种非常有用的设计模式。它能够有效地处理请求的发送者和接收者之间的解耦&…

56、php实现N的阶乘末尾有多个0

题目&#xff1a; php实现N的阶乘末尾有多个0 描述&#xff1a; 阶乘 N! 123*…N; 比如 5! 12345 120 末端有1个0 解题思路&#xff1a; N! K*(10^M) N的阶乘为K和10的M次方的乘积&#xff0c;那么N!末尾就有M个0。如果将N的阶乘分解后&#xff0c;那么N的阶乘可以分解为&…

设计模式-备忘录

备忘录&#xff08;Memento&#xff09;设计模式是为了保存对象当前状态&#xff0c;并在需要的时候恢复到之前保存的状态。以下是一个简单的C#备忘录模式的实现&#xff1a; // Originator 类&#xff0c;负责创建和恢复备忘录 class Originator {private string state;publi…

新型蜜罐有哪些?未来方向如何?

前言&#xff1a;技术发展为时代带来变革&#xff0c;同时技术创新性对蜜罐产生推动力。 一、新型蜜罐的诞生 技术发展为时代带来变革&#xff0c;同时技术创新性对蜜罐产生推动力&#xff0c;通过借鉴不同技术思想、方法&#xff0c;与其它技术结合形成优势互补&#xff0c;…

架构设计中期应该怎么做?

设计备选方案 架构设计常见的错误思维 什么是备选架构&#xff1f; 能够解决系统复杂度的方案 备选方案设计过程 备选架构设计技巧 备选方案常见困难和应对技巧 学的太浅&#xff1a;不知道本质 比较学习法 评估和选择备选方案 错误的方法 正确的方法 - 360度环评 优先级排…