C++入门——引用

news2024/11/26 10:48:36

1.概念

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

类型& 引用变量名(对象名) = 引用实体;
而引用类型必然要与引用实体的类型一致。

void TestRef()
{
	int a = 10;
	int& ra = a;
	printf("%p\n", &a);
	printf("%p\n", &ra);
}


2.引用特性

1.不同于指针,不初始化也不会报错,引用在定义时必须初始化

int& ra = a;//right
int& ra;//error

2. 一个变量可以有多个引用

int a=0;
int& b=a;
int& c=a;

由于引用只是引用实体的一个别名,因此我们也可以这样使用

int a=0;
int& b=a;
int& c=b;

3. 引用一旦引用一个实体,再不能引用其他实体。

int a=0;
int b=1;
int& c=a;
c=b;

例如上面的代码,c=b的作用并不是让c变为b的别名,而是将b的值赋给c,也就是a


3 .常引用

int a=10;
const int& ra=a;

类似于const常量,const修饰常引用后,我们无法做到改变ra的值来改变a的值,而只能通过a来进行改变。

const int a=10;
int& ra=a;

而这段代码,本身a不能修改,如果直接用ra引用,会导致权限放大,是不可以的

而权限不变和权限缩小都是没有问题的

const int b=10;
const int& rb=b;

int c=10;
const int& rc=c;

我们再来看一下下面的代码

double a=1.23
int b=a;
int& c=a;
const int& d=a;

经过测试,我们可以得知,b和d是可以这样使用的,而c不可以,这是因为,在进行隐式类型转换或整形提升时,需要创建一个临时变量来存储,这样,c和d其实就是作为这个临时变量的引用,而临时变量是无法被改变的,所以我们需要进行常引用。

而同样,由于临时变量具有常性

int x1=1;
int x2=2;
int& x=x1+x2;

这样的代码也是不可行的,依然需要使用常引用。

而在后面会讲到的引用传参中,如果不需要改变参数,建议也使用常引用 


4.   引用的使用场景

(1).引用作参数

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

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

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

例如一个简单的交换函数,在c语言的学习中,我们知道,传值调用参数只是一个拷贝,无法完成实参的交换,因此我们选用传址调用,通过指针来完成交换。

而我们也可以进行传引用,这是,left与right都是实参的别名,对形参进行交换时,其实也就是对实参进行交换。

我们在这里插入一个点,这三个函数是否构成函数重载呢?其实是构成的,因为它们的参数类型是不同的,但是,由于传引用与传值的实参都是相同的,所以即使能构成函数重载,在使用时也会出现问题。

单从上面,我们似乎还感知不到引用的优势,我们可以看一下我们在数据结构中所学到的链表

void SListPushBack(SLTNode** pphead, SLTDateType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
 
	if (*pphead == NULL)//由于无哨兵位,若链表为空只需要赋值
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* tail = *pphead;
        //找尾
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

就以尾插为例,许多同学可能在刚开始学习时无法理解参数中二级指针的含义

而本身我们需要传入的是以指针存储的头结点的地址,但在插入或删除中,头结点的位置可能会被改变,所以我们需要进行传址调用,这便是使用二级指针的原因。

而我们将传址调用改为传引用,同样能做到改变头结点的位置,同时也更容易去理解

void SListPushBack(SLTNode*& rphead, SLTDateType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
 
	if (rphead == NULL)//由于无哨兵位,若链表为空只需要赋值
	{
		rphead = newnode;
	}
	else
	{
		SLTNode* tail = rphead;
        //找尾
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

(2).引用作返回值

我们先来简单分析一下传值返回

int Add(int a, int b)
{
    int c=a+b;
    return c;
}

int main()
{
    int add=Add(1,2);
    return 0;
}

 首先先开辟一块主函数的空间,紧接着去调用Add函数。而Add函数的空间内分别有三块空间存储a,b,c的值。而在传值返回中,返回的并不是c,而是c的一个临时变量。而Add这块空间便被销毁了

而临时变量较小时,会被存放在寄存器中,较大时,则会存放在调用Add函数的栈帧中

当我们转到反汇编时,可以看到,在return c中,临时变量的确时存储在eax这个寄存器之中的。

而当我们进行传引用返回时,返回的便不是c的拷贝的临时变量,而是c的引用

int& Add(int a, int b)
{
    int c=a+b;
    return c;
}

int main()
{
    int add=Add(1,2);
    return 0;
}

 而这样,会出现一些问题,例如非法访问。而由于编译器的不同,有些编译器中Add函数在调用过后会被销毁,这样会c为随机值,进而导致add变量为随机值。

例如我们在原代码的基础上做一些改变

int& Add(int a, int b)
{
    int c = a + b;
    return c;
}

int main()
{
    int& add = Add(1, 2);
    cout << add << endl;
    Add(10, 20);
    cout << add << endl;
    return 0;
}

这时,临时变量是c的引用,而add是临时变量的引用,因此add便是c的引用。

而当第二次调用Add函数是,Add函数所占的空间不变,因此会使c变为30,而add也一样

同样,若是Add函数调用过之后会被销毁,当c变量原来的地址被占用时,也会出现类似的问题。

当然,传引用返回也有好处,例如在内存中开辟一块空间存储一个较大的数组等,当我们使用传值返回时,需要进行拷贝,使用大量空间,而传引用返回就不需要进行拷贝,从而会使效率提升。同时,由于是在内存中开辟空间来存储数组,也不会发生非法访问的问题。

同样,引用传参也有效率提升这个优势。


5.引用传参和传返回值的优势 

1.有些场景下会使效率提升(大对象+深拷贝,后面会讲)

2.有些场景下,形参的改变可以改变实参,引用返回可以改变返回对象(后面也会将讲)


6. 引用和指针的区别

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

为什么说引用更加安全呢?举个简单的例子

void test1(int* p)
{
	*p = 10;
}

void test2(int& r)
{
	r = 10;
}

这样的函数,在传参时,使用指针可能会发生许多人为的错误,例如空指针、野指针等。

test1(NULL);
test1(0);

而使用引用并不会。


 

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

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

相关文章

下载微信小程序中的视频

工具准备&#xff1a;Fiddler 我这里用的5.0的版本。&#xff08;这个用来抓取视频下载地址&#xff09;Internet Download Manager&#xff08;idm&#xff09;版本6.37&#xff08;这个用来下载视频&#xff09;步骤&#xff1a;打开Fiddler如下图配置后抓包2.登录PC版微信&a…

1.5日报

今天完成项目环境的搭建&#xff0c;并成功剥离TestMrl相关接口&#xff0c;并成功运行 遇到的问题&#xff1a; 缺少 <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <v…

展望2023,回首2022,让我们一起努力

回首2022 时光如白驹过隙般&#xff0c;飞逝而过。时光荏苒&#xff0c;日月如梭。不知不觉中&#xff0c;充满希望与光明的2023年已经到来了。回首2022年&#xff0c;有喜悦&#xff0c;有失落&#xff0c;有艰辛与困难&#xff0c;也有解决困难后的欣慰&#xff0c;有着无数…

第03章 运算符

运算符介绍 运算符是一种特殊的符号&#xff0c;用以表示数据间的运算、赋值和比较等。 算数运算符 算数运算符用于对数值型变量间的计算。 注意&#xff1a;图片中最后一行&#xff0c;字符串相加的结果中间没有空格&#xff0c;这个课件错误❌。 除法[ / ] System.out.pr…

RabbitMQ 消息类型

RabbitMQ 消息类型 下面我们简单介绍下RabbitMQ的一些消息种类&#xff0c;并结合Java代码进行学习。 如果需要执行代码&#xff0c;需要下载RabbitMQ的客户端&#xff08;例如java客户端&#xff1a; https://www.rabbitmq.com/java-client.html&#xff09; 使用maven&…

聊天突然尬住?教你用Python一键获取斗图表情包,各种表情包轻松化解尴尬

很多兄弟在聊天上没有下太多的功夫&#xff0c;导致自己聊天的时候很容易尬住&#xff0c;然后就不知道聊啥了&#xff0c;这时候合适表情包分分钟就能救场&#xff0c;但是一看自己收藏的表情包&#xff0c;好家伙&#xff0c;两只手都数得过来。 所以今天来给兄弟们分享一下…

IDEA书签,备份使用,全分支共享

IDEA原本设计就是给单分支保存书签使用的&#xff0c;但是我比较喜欢多个分支用同一个IDEA书签。然后在网上找到很久的IDEA书签全分支共享的办法&#xff0c;真心没找到合适的&#xff0c;自己浅浅总结了一下。首先找到我们需要备份书签的项目的目录然后在项目的目录下打开隐藏…

小程序 超长页面截图保存web-view+html2canvas

web-view文档建议参考----支付宝提供的文档&#xff0c;html2canvas官方文档&#xff08;官网可以下载html2canvas.js 和 html2canvas.min.js&#xff09;。由于篇幅受限&#xff0c;这里就贴了一下用法&#xff0c;对于web-view的配置情况&#xff0c;需要自己去查看文档&…

【测试】开发模型+测试模型

努力经营当下&#xff0c;直至未来明朗&#xff01; 文章目录一、开发模型和测试模型概述二、 开发模型一&#xff09; 瀑布模型二&#xff09;螺旋模型三&#xff09;增量模型和迭代模型四&#xff09;敏捷模型【重点:sunny:】三、 测试模型一&#xff09;V模型二&#xff09;…

【4.2】Ribbon负载均衡策略

【4.2】Ribbon负载均衡策略1 Ribbon--负载均衡策略2.1 修改负载均衡规则--代码方式2.1.1 具体测试&#xff1a;2.2 修改负载均衡规则--配置文件方式2.2.1 具体配置3 总结Ribbon负载均衡原理 中学习到&#xff1a; IRule接口决定了负载均衡的策略。 接下来学习IRule接口的实现有…

【Java编程进阶】Object类及常用方法详解

Java 编程基础教程系列&#xff1a;Java 编程进阶之路【从入门到精通】 &#xff0c;从入门到精通一站学习&#xff0c;买不了吃亏&#xff0c;买不了上当&#xff01;&#xff01; 文章目录1. Object类2. 常用的方法2.1 toString 方法2.2 equals 方法2.3 hashcode 方法3. 注意…

Verilog语法笔记(夏宇闻第三版)-数据类型及其常量、变量

目录 常量&#xff1a; 整数&#xff1a; x和z值: 负数: 下划线(underscore_)&#xff1a; 参数(Parameter)型&#xff1a; 变量&#xff1a; wire型&#xff1a; reg型&#xff1a; memory型&#xff1a; Verilog HDL中总共有十九种数据类型,数据类型是用来表示数字…

SSH远程连接服务详解

远程连接服务器 一&#xff0c;远程连接服务器简介 1、什么是远程连接服务器 远程连接服务器通过文字或图形接口方式来远程登录系统&#xff0c;让你在远程终端前登录 linux 主机以取得可操作主机接口&#xff08;shell&#xff09;&#xff0c;而登录后的操作感觉就像是坐在…

MyBatis Plus学习笔记

MyBatis Plus 国产的开源框架&#xff0c;基于 MyBatis 在Mybatis-Plus中&#xff0c;内置了代码生成器&#xff0c;我们可以通过该工具&#xff0c;生成我们需要的代码&#xff0c;例如&#xff1a;entity层&#xff0c;controller层&#xff0c;mapper层&#xff0c;service…

Java面向对象:构造器、this

目录构造器学构造器的目的构造器的作用样例构造器的注意事项总结this关键字this关键字是什么样例this关键字的作用总结构造器 学构造器的目的 真正知道对象具体是通过调用什么代码得到的。能够掌握为对象赋值的其他简便写法。为以后学习面向对象编程的其他内容做支撑。 构造…

Python实现可视化案例:采集天气数据并可视化分析

前言 最近长沙的天气&#xff0c;真的就是不能理解&#xff0c;大起大落的&#xff0c;就跟我的心情一样… 有点无聊就来采集一些天气数据&#xff0c;做个可视化的小案例吧&#xff08;我采集的是以前北上广深的天气数据哈&#xff09; 实现案例的步骤 一.分析数据来源 从…

狂神说笔记——Linux快速入门27

Linux快速入门 参考于&#xff1a;B站狂神视频&#xff01; Java开发之路&#xff1a;JavaSE、MySQL、前端&#xff08;HTML、Css、JS&#xff09;、JavaWeb、SSM框架、SpringBoot、Vue、SpringCloud、Mybatis-plus、Git、Linux &#xff08;CentOS 7&#xff09; 操作系统&…

【Linux】-- 程序地址空间

目录 程序地址空间 进程地址空间 - 虚拟地址空间 概念引入&#xff08;浅&#xff09; 初步理解结构 深入理解虚拟地址 为什么要有地址空间&#xff1f; 程序地址空间的角度理解挂起 程序地址空间 C/C在Linux下的程序地址空间分布&#xff1a; 栈向低地址增长&#xff0…

透过现象看本质,我找到了Netty粘包与半包的这几种解决方案

1、粘包与半包 啥也不说了&#xff0c;直接上代码是不是有点不太友好&#xff0c;我所谓了&#xff0c;都快过年了&#xff0c;还要啥自行车 我上来就是一段代码猛如虎 1.1 服务器代码 public class StudyServer {static final Logger log LoggerFactory.getLogger(StudyS…

怎样进行股票量化对冲策略分析?

股票量化对冲策略的分析需要从各方面去深入了解&#xff0c;就比如说明确量化和对冲的概念&#xff0c;可以先下载OA系统中“量化对冲 产品基础知识的学习&#xff0c;也要知道量化对冲产品在构建股票多头的同时&#xff0c;也构建期货空头。在市场不稳定的操作情绪之下&#x…