【C++】初识引用

news2024/9/27 12:13:20

目录

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

概念

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

比如:李逵,在家称为"铁牛",江湖上人称"黑旋风"。【水浒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.coloradmin.cn/o/1975467.html

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

相关文章

【前端学习笔记二】CSS基础二

一、颜色模型 1.颜色设置 颜色名称 https://www.w3schools.com/colors/colors_names.asp 这里是一些颜色的名称&#xff08;关键字&#xff09;&#xff0c;比如red、black、green等&#xff0c;可以直接指定名称来设置颜色。名称不区分大小写。 color:red;transparent tr…

OCC 网格化(三)-网格划分算法原理

目录 一、简介 二、基本原理 三、工作流程 四、BRepMesh模块与网格化流程 4.1 BRepMesh 主要组件 4.2 工作流程 4.3 网格生成示例 五、关键参数总结 一、简介 BRepMesh_IncrementalMesh 是一种基于迭代细分的网格划分算法,通过设置线性偏转和角偏转参数,可以生成高精…

利用Python爬虫实现数据收集与挖掘

Python爬虫通常使用requests、selenium等库来发送HTTP请求&#xff0c;获取网页内容&#xff0c;并使用BeautifulSoup、lxml等库来解析网页&#xff0c;提取所需的数据。 以下是一个简单的Python爬虫示例&#xff0c;用于从某个网页上抓取数据&#xff1a; import requests …

免费【2024】springboot 大学生志愿者管理系统的设计与实现

博主介绍&#xff1a;✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术范围&#xff1a;SpringBoot、Vue、SSM、HTML、Jsp、PHP、Nodejs、Python、爬虫、数据可视化…

Executing an update/delete query,解决Hibernate更新数据库报错

问题描述 在使用Hibernate更新数据库中一条记录时,发送如下错误: javax.persistence.TransactionRequiredException: Executing an update/delete query at org.hibernate.internal.AbstractSharedSessionContract.checkTransactionNeededForUpdateOperation(AbstractShare…

HCIA基础回顾

OSI参考模型 OSI&#xff08;Open System Interconnect&#xff09;参考模型&#xff0c;即为开放式系统互连参考模型。 应用层&#xff1a;人机交互&#xff0c;提供网络服务。 表示层&#xff1a;将逻辑语言转换为二进制语言&#xff0c;定义数据格式。 会话层&#xff1…

Linux 安装gradle

1.下载 下载地址&#xff1a; 下载地址&#xff1a; Gradle | ReleasesFind binaries and reference documentation for current and past versions of Gradle.https://gradle.org/releases/ 2. 解压 unzip gradle-7.6.2-all.zip 3.修改配置文件 #1.进入配置文件 vim /etc/…

【探索Linux】P.44(数据链路层 —— 以太网的帧格式 | MAC地址 | MTU | ARP协议)

阅读导航 引言一、认识以太网二、以太网的帧格式三、MAC地址四、MTU五、ARP协议温馨提示 引言 在深入探讨了网络层的IP协议之后&#xff0c;本文将带领读者进一步深入网络的底层——数据链路层。我们将详细解析以太网的帧格式&#xff0c;这是数据链路层传输数据的基本单元&am…

漏洞复现:Apache solr

目录 漏洞简述 环境搭建 漏洞复现 漏洞检测 漏洞修复 漏洞简述 Apache Solr是一个开源的搜索服务&#xff0c;使用Java编写、运行在Servlet容器的一个独立的全文搜索服务器&#xff0c;是Apache Lucene项目的开源企业搜索平台。 该漏洞是由于没有对输入的内容进行校验&…

深度体验:IntelliJ Idea自带AI Assistant,开启面向AI编程新纪元!

首发公众号&#xff1a; 赵侠客 引言 JetBrains AI Assistant 是 JetBrains 集成开发环境&#xff08;IDE&#xff09;中嵌入的一款智能开发助手工具&#xff0c;旨在通过人工智能技术来简化和提升软件开发过程&#xff0c;我深度体验了一下在IntelliJ IDEA 2024.2 Beta (Ulti…

JAVA项目基于SSM的学生成绩管理系统

目录 一、前言 二、技术介绍 三、项目实现流程 四、论文流程参考 五、核心代码截图 专注于大学生实战开发、讲解和毕业答疑等辅导&#xff0c;获取源码后台 一、前言 二、技术介绍 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端…

全球氢化双酚A (HBPA)市场规划预测:2030年市场规模将接近1330亿元,未来六年CAGR为2.7%

一、引言 随着全球化工行业的持续发展&#xff0c;氢化双酚A (HBPA)作为重要的化工原料&#xff0c;其市场重要性日益凸显。本文旨在探索HBPA行业的发展趋势、潜在商机及其未来展望。 二、市场趋势 全球HBPA市场的增长主要受全球化工行业增加、消费者对高性能化工产品要求提高…

vue3内置组件Suspense

给多个异步组件提供一个统一的状态管理 使用前&#xff0c;有两个loading... 使用后&#xff0c; 只有一个loading... Index.vue: <script setup lang"ts"> import { onMounted, ref, defineAsyncComponent } from vue import { useRouter } from vue-router…

CTF入门教程(非常详细)从零基础入门到竞赛,看这一篇就够了!

一、CTF简介 CTF&#xff08;Capture The Flag&#xff09;中文一般译作夺旗赛&#xff0c;在网络安全领域中指的是网络安全技术人员之间进行技术竞技的一种比赛形式。CTF起源于1996年DEFCON全球黑客大会&#xff0c;以代替之前黑客们通过互相发起真实攻击进行技术比拼的方式。…

什么是网络安全?一文了解网络安全究竟有多重要!

随着互联网的普及和数字化进程的加速&#xff0c;网络安全已经成为我们生活中不可或缺的一部分。然而&#xff0c;很多人对于网络安全的概念仍然模糊不清。 ​ 那么&#xff0c;什么是网络安全&#xff1f;它究竟有多重要呢&#xff1f; 一、网络安全的定义 网络安全是指通过…

【Java】/* JDK 新增语法 */

目录 一、yield 关键字 二、var 关键字 三、空指针异常 四、密封类 五、接口中的私有方法 六、instanceof 一、yield 关键字 yield关键字&#xff0c;从Java13开始引⼊。yield关键字⽤于从case的代码块中返回值。 原本的switch语句写法&#xff1a; public static void …

Notion爆红背后,笔记成了AI创业新共识?

在数字化时代&#xff0c;笔记软件已成为我们记录、整理和创造知识的得力助手。本文将带您深入了解Notion以及其他五个AI笔记产品&#xff0c;它们如何通过AI重塑笔记体验&#xff0c;满足我们快速记录、捕捉灵感、智能整理、情感陪伴和自动撰写文章的五大核心需求。 ———— …

NC 在两个长度相等的排序数组中找到上中位数

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 描述 给定两个递增…

项目比赛项目负责人的汇报技巧:如何让每一次汇报都清晰有力

项目比赛项目负责人的汇报技巧&#xff1a;如何让每一次汇报都清晰有力 前言MECE原则&#xff1a;确保全面性与互斥性SCQA结构&#xff1a;讲一个引人入胜的故事逻辑树思维模型&#xff1a;深入挖掘问题根源STAR法则&#xff1a;展示你的行动与成果PREP模型&#xff1a;清晰表达…

SAP 执行程序报错:Screen does not exist.解决办法

当我们第一次执行创建计划协议—ME31L和ME31事务代码的时候,可能会遇到下图中的这个报错,提示屏幕不存在 处理方式:我们要通过激活二级屏幕的方式激活即可。 1、执行事务代码:CMOD 自定义创建一个项目,这里我们创建的是ZMM02,然后点击创建 然后点击增强分配 将程序名称…