C++_引用

目录

1、引用的使用

1.1 对“别名”的修改

1.2 “别名”的“别名” 

1.3 对“别名”进行赋值

2、引用的意义 

2.1 指针的“别名”

3、函数返回值作为引用

3.1 返回值作为引用的意义

4、引用的权限

4.1 引用的类型转换

5、指针与引用

5.1 指针与引用的相似处

5.2 指针与引用的区别

结语:


前言:

        在C++中,引用的符号为”&“,和取地址的符号是一样的。使用引用的前提是必须得有一个变量作为引用的对象,通常我们把对一个变量的引用叫做给该变量“取别名”,因此调用该变量的“别名”实则就是调用该变量。而且“别名”与“本名”之间建立了联系,既对“别名”的修改会影响“本名”,对“本名”的修改也会影响“别名”。

        注意:在同一作用域下,一个变量可以有多个“别名”,但是一个“别名”只要确定有了一个“本名”后就不能再成为其他本名的“别名”,通俗点来说就是“别名”已经确定“指向”一个“本名”后,就不能更改他的“指向”了。

1、引用的使用

        写法:类型+&+”别名称“=引用对象变量。引用的使用代码如下:

#include<iostream>
using namespace std;

int main()
{
	int a = 10;
	int& c = a;//引用a,并且别名叫c(注意别名类型与本名类型保证一致)
	cout << a << endl;
	cout << c << endl;

    //观察别名和本名的地址是否相同
    cout << &a << endl;
	cout << &c << endl;

	return 0;
}

        运行结果:

        从结果中可以看到,别名c的地址实质上就是a的地址,既虽然别名也进行了初始化但是别名和本名用的是同一个空间,并没有产生新的空间,与现实中一个人的”别名"和“本名”逻辑相同,都是作用在同一个人上。(注意别名类型与本名类型保证一致)


        注意:别名创建的时候必须进行初始化,不然会报错。

1.1 对“别名”的修改

        上文提到对别名的修改会影响本名,测试代码如下:

#include<iostream>
using namespace std;

int main()
{
	int a = 10;
	int& c = a;
	int b = a;
	cout << &a << endl;
	cout << &c << endl;
	cout << &b << endl;

	c++;
	cout << a << endl;
	cout << c << endl;
	cout << b << endl;

	return 0;
}

        运行结果:

         因此,a、b、c的关系图如下:

1.2 “别名”的“别名” 

        在C++中,“别名”也可以有属于他自己的“别名”,可以嵌套使用,当对“别名”的“别名”进行修改时,会修改与之相关的所有“本名”和“别名”。

        “别名”嵌套代码如下:

#include<iostream>
using namespace std;

int main()
{
	int a = 10;
	int& c = a;
	int b = a;
	cout << b << endl;

	cout << endl;
	int& ra = a;//一个本名可以有多个别名
	int& rc = c;

	rc++;//改变其中一个别名的值
	cout << a << endl;
	cout << c << endl;
	cout << ra << endl;
	cout << rc << endl;

	return 0;
}

        运行结果:

1.3 对“别名”进行赋值

        上文提到“别名”一旦初始化后就不能修改“别名”的“指向”,所以对“别名”进行赋值也只能修改“别名”和“本名”的值,并不会让“别名”指向其他“本名”。

#include<iostream>
using namespace std;

int main()
{
	int a = 12;
	int& c = a;//初始化c为a的别名

	int b = 20;
	c = b;//将b的值赋给c
	b = b + 102;//更改b的值,观察c是否变化

	cout << a << endl;
	cout << c << endl;
	a++;//更改a的值,观察c是否变化
	cout << a << endl;
	cout << c << endl;

	return 0;
}

        运行结果:

        从结果可以看到,c依然是a的别名,则对“别名”进行赋值不会更改“别名”的“指向”,只会更改“别名”和其“本名”的值。 

2、引用的意义 

        通过以上例子可以发现引用和指针在某些方面很相似,引用是:通过更改一个变量的“别名”就能达到更改该变量的效果。指针是:通过访问一个变量的地址,对其解引用后也可以更改该变量的值。所以在某些场景下,引用可以代替指针,并且可以实现和指针一样的效果。

        体现引用意义的代码如下:

#include<iostream>
using namespace std;

void swap(int& x, int& y)//x为实参a的引用,y为b的引用
{
    //修改变量的“别名”,既修改变量本身
	int temp = x;
	x = y;
	y = temp;
}

int main()
{
	int a = 12;
	int b = 24;
	cout << a << endl;
	cout << b << endl;
	cout << endl;

	swap(a, b);

	cout << a << endl;
	cout << b << endl;
	return 0;
}

        运行结果:

        从结果可以看到,没有使用传统的传址操作依然可以做到形参的改变影响实参。 

2.1 指针的“别名”

        引用的类型不止局限于int类型,还可以引用char、double、float以及指针类型,只是引用的类型必须跟引用对象类型保持一致。

        引用指针类型代码如下:

#include<iostream>
using namespace std;

int main()
{
	int a = 12;
	int* pa = &a;
	int*& rpa = pa;

	*rpa = 20;//对别名进行解引用同样可以找到a
	cout << a << endl;
	return 0;
}

        运行结果:

        这里的pa虽然等价于&a,但是引用时不能写成: int*& rpa = &a,因为引用的对象必须为一个变量,&a不是一个变量,他具有常属性,因此不能作为引用的对象。

3、函数返回值作为引用

        在讲述函数的返回值作为引用的作用前,要先引入一个概念,既函数调用结束后空间理应是还给操作系统的,那么函数的返回值是如何给到main函数中的呢。把函数调用时向系统申请的空间叫做栈帧空间,具体示意图如下:

        因此在func函数被系统收回前会先完成一个动作,就是把n的值拷贝给main函数中的临时变量,这样即使func函数的空间被收回了也无所谓,因为n的值已经传递过来了,然后临时变量在把值给到poi,完成返回值的传递,并不是直接把n的值直接给到poi的。 


        可以发现以上的过程是相对复杂的,因为过程中涉及临时变量的拷贝与赋值,似乎认为造成这一现象的原因就是n变量的生命周期随着函数空间被收回而结束了。如果可以延长n的生命周期是不是就能够直接把n的值返回给到poi呢?

        示意图如下:

        其实即使加了static,n的值也没有直接返回给到poi,还是和上面情况一样先拷贝给临时变量,然后再赋值给poi,所以以上的两种情况效率上没什么太大的区别,都是要经过一系列的过程。


        但是关键点来了,上面两种情况只有第二种情况是适合将函数返回值作为引用的,首先把函数的返回值作为引用的意思是返回的不再是一个值了,而是一个“别名”,拿上面的例子来说,返回的是n的别名而不是n的拷贝。

        其次为什么第一种情况不适合做返回值引用,因为第一种情况的n出了函数后就有可能会被销毁,也就是n的值可能会变成随机值,这时候如果返回他的“别名”则poi有可能接收到的是随机值(也有可能不是随机值,具体根据编译器而定了),总之是一种不稳定的情况。

        而第二种情况的n放在静态区,他的值是“有保障”的,不会因为函数栈帧被收回而变成随机值,这时候返回n的别名则一定是有效值。因此得出结论:当一个函数的返回值出了作用域他的生命周期还在就可以用传引用返回(既返回值作为引用),否则只能用传值返回(既返回临时变量)。

        返回值引用代码如下,并且测试对返回值别名的修改是否会影响返回值本名:

#include<iostream>
using namespace std;

int& func()
{
	static int n = 12;//本名
	cout << n << endl;
	return n;
}

int main()
{
	int& poi = func();//poi为别名
	cout << poi << endl;
	poi++;//poi变化则n也会变化
	func();//观察n是否发生变化
	cout << poi << endl;//观察poi和n是否一致
	return 0;
}

        运行结果: 

         从结果可以看到,改变别名poi的值,则n的值也受到了变化,说明该函数返回的是n的别名,而不是n的拷贝。


        那么问题来了,因为此时用的别名poi来接收,所以可以通过poi来改变n的值,这么一看应该是别名poi在发挥影响,如果传的是n值的拷贝,用别名poi来接收按理来说应该也可以做到更改poi去影响n的效果,毕竟传n的拷贝和传n的别名,poi接收到的值都是一样的,都是12。

        运行结果:

        发现此时编译器报错了,原因就是传值返回的是临时变量,而临时变量是具有常属性的,是不可被修改的,因此不能用变量的引用来接收他。可以在int& poi前面加上const,让poi也具有常属性即可,所以这里引出了引用权限的概念。

3.1 返回值作为引用的意义

        为什么要用返回值作为引用呢,因为传引用返回相比于传值返回(临时变量作为返回值)在效率上要快一些,尤其是在处理大量的数据时。

        效率测试代码如下:

#include<iostream>
#include <time.h>
using namespace std;

//创建一个大的对象,方便测试
struct A
{
	int a[10000];
};

A a;

// 值返回
A Return_by_value()
{
	return a;
}
// 引用返回
A& Return_by_reference()
{
	return a;

}
void TestReturnByRefOrValue()
{
	// 临时变量作为返回值
	int begin1 = clock();//clock函数是返回系统开始运行到调用到该函数的时间,单位毫秒
	for (int i = 0; i < 100000; ++i)
		Return_by_value();
	int end1 = clock();

	// 把返回值的引用作为返回值
	int begin2 = clock();
	for (int i = 0; i < 100000; ++i)
		Return_by_reference();
	int end2 = clock();

	// 比较两种方法的效率
	cout << "Return_by_value time:" << end1 - begin1 << endl;
	cout << "Return_by_reference time:" << end2 - begin2 << endl;
}

int main()
{
	TestReturnByRefOrValue();
	return 0;
}

        运行结果:

        从结果可以看到, 传引用返回的效率在处理大的数据时是优于传值返回的效率的。

4、引用的权限

        在c\c++中,变量是可以被修改的,而常量是不可被修改的,因此如果引用对象为常量,则不能用引用变量的写法来引用常量,一般会在前面加const,让“别名”具有常属性就可以引用常量了。可以把变量的权限理解为:“可读”、“可改”,常量的权限理解为:“只可读”。那么在引用变量时可以加const,既允许权限缩小。当引用常量时就不能不加const,既不允许权限放大。

        引用权限代码如下:

#include<iostream>
using namespace std;

int main()
{
	//权限缩小(允许)
	int a = 12;
	const int& c = a;
	
	//权限保持(允许)
	int e = 12;
	int& f = e;

	const int* p2 = NULL;
	const int*& rp2 = p2;

	//权限放大(不允许)
	//const int g = 12;
	//int& h = g;

	//const int* p1 = &g;
	//int*& rp1 = p1;

	return 0;
}

4.1 引用的类型转换

        在上文提到过“别名”类型要与“本名”类型一致,不然编译器会报错,这里可以很好理解为什么“别名”类型和“本名”类型不一致会导致报错,第一反应肯定是因为他们的类型不匹配导致报错的。那么来看以下代码:

        发现在double前面加了个const就能正常编译,说明不是类型不匹配引起的错误,而是在转换的过程中,并不是直接将i转换double,而是在这过程中产生了一个临时变量(该临时变量是double类型),i先把值给到临时变量中,然后临时变量再被引用至ri。

        ri是中间产生的临时变量的别名,并不是变量i的别名,但是临时变量是具有常属性的,ri本身是没有常属性的,因此在前面加了个const让ri具有常属性就不会报错了。

5、指针与引用

5.1 指针与引用的相似处

        上文说到引用和实体是共用同一个空间的,说明引用不会另外开一个空间,但是在底层实际是给引用开了空间的,并且底层引用是按照指针的逻辑实现的。

        指针和引用的汇编语言实现图:

        从上图可以得出引用的底层逻辑和指针是一样的。

5.2 指针与引用的区别

        指针与引用的具体区别如下: 

1、指针存在空指针,引用不存在“空”的概念。

2、在32位机器下,指针的大小始终为4字节,而引用的大小是取决于实体对象的类型大小。

3、指针可以不初始化,但是引用必须初始化。

4、有二级指针的概念,但是没有二级引用的概念。

5、指针可以更改其指向,但是引用初始化后就不能更改他的“指向”了。

6、在运算中,对指针自加1操作是让指针指向原本地址的后一位,引用自加1是数值+1。

7、由于指针涉及到空指针以及非法空间的访问的情况,因此引用相比于指针会更加安全。

8、在语法上,指针会开辟一块空间用于存放地址,而引用不会另外再开辟一块空间。

9、若要更改实体变量本身,用指针的方法则要先解引用后才能找到变量,才能更改变量的值。用引用的方法无需解引用,直接更改“别名”的值,就能更改变量的值。

结语:

        以上就是关于C++_引用的讲解,引用与指针之间有微妙的关系,他们的目的基本都相同,都是间接的更改实体变量的值。熟悉引用的使用场景可以更好的理解引用的意义,最后希望本文可以给你带来更多的收获,如果本文对你起到了帮助,希望可以动动小指头帮忙点赞👍+关注😎+收藏👌!如果有遗漏或者有误的地方欢迎大家在评论区补充~!!谢谢大家!!( ̄︶ ̄)↗ 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

FPGA时序分析与约束(0)——目录与传送门

一、简介 关于时序分析和约束的学习似乎是学习FPGA的一道分水岭&#xff0c;似乎只有理解了时序约束才能算是真正入门了FPGA&#xff0c;对于FPGA从业者或者未来想要从事FPGA开发的工程师来说&#xff0c;时序约束可以说是一道躲不过去的坎&#xff0c;所以这个系列我们会详细介…

使用selenium的edge浏览器登录某为

互联网上基本都是某哥的用法&#xff0c;其实edge和某哥的用法是一样的就有一下参数不一样。 一、运行环境 Python&#xff1a;3.7 Selenium&#xff1a;4.11.2 Edge&#xff1a;版本 120.0.2210.61 (正式版本) (64 位) 二、执行代码 from time import sleepfrom selenium…

如何赢得并留住订阅者:12 个必须尝试的订阅营销策略

Netflix、Hubspot、Spotify 和 Slack 都是流行的基于订阅的服务&#xff0c;您可能每天都会使用它们&#xff0c;无论是工作还是娱乐。这些例子表明&#xff0c;订阅业务模式深受 SaaS 创业者的青睐。 这种模式的吸引力很容易理解&#xff0c;特别是考虑到订阅市场预计到 2025…

Node包管理工具 - nvm、npm、yarn、cnpm、pnpm

转载说明 原文地址 简介 nvm : 可以实现一台电脑&#xff0c;拥有多个版本的Node npm : node package manager 下载Node后自带的一个包管理工具 yarn : npm 的升级版&#xff0c;更优秀 cnpm : 配置下载非官方地址的依赖&#xff08;淘宝、华为、腾讯镜像&#xff09; pnpm :…

智能优化算法应用:基于蜣螂算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于蜣螂算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于蜣螂算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.蜣螂算法4.实验参数设定5.算法结果6.参考文献7.MATLAB…

数据中心:保障企业运营安全可靠的关键

随着人工智能与云计算的爆发&#xff0c;数据中心行业迎来了前所未有的需求增长。然而&#xff0c;这也带来了一系列的挑战。各地政府机构对数据中心建设出台了更为完善和严格的地方标准&#xff0c;企业面临着运营成本高、人才短缺和节能减排等困难。同时&#xff0c;过去频频…

redis 三主三从高可用集群docker swarm

由于数据量过大&#xff0c;单个Master复制集难以承担&#xff0c;因此需要对多个复制集进行集群&#xff0c;形成水平扩展每个复制集只负责存储整个数据集的一部分&#xff0c;这就是Redis的集群&#xff0c;其作用是提供在多个Redis节点间共享数据的程序集。 官网介绍地址 re…

理解基于 Hadoop 生态的大数据技术架构

转眼间&#xff0c;一年又悄然而逝&#xff0c;时光荏苒&#xff0c;岁月如梭。当回首这段光阴&#xff0c;不禁感叹时间的匆匆&#xff0c;仿佛只是一个眨眼的瞬间&#xff0c;一年的旅程已成为过去&#xff0c;而如今又到了画饼的时刻了 &#xff01; 基于 Hadoop 生态的大数…

msvcp100.dll丢失的常见原因/msvcp100.dll丢失的解决方法分享

在计算机使用过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中之一就是“msvcp100.dll丢失”。这个错误提示通常出现在运行某些程序或游戏时&#xff0c;给使用者带来了很大的困扰。那么&#xff0c;究竟是什么原因导致了msvcp100.dll文件的丢失呢&#xff1f;本…

【Fastadmin】利用 build_select 做一个树状下拉选择框

1.效果展示 系统crud生成的下拉分类有些不是很好看&#xff0c;并且选择困难&#xff0c;看不出级差&#xff0c;效果如下&#xff1a; 经过 build_select 加工后的效果,美观好看&#xff0c;并添加上搜索功能: 2. 首先需要写一个树状图的数据格式 protected $datalist []; pu…

集合贴——问答引擎

1.FAQ问答引擎 FAQ问答引擎是一种传统的问答机器人引擎&#xff0c;它基于NLP算法研发&#xff0c;为用户提供高泛化性、灵活拓展的QA对匹配引擎。FAQ通常以{1条标准问 1 条标准答案 n条相似问}的结构将语料存储在FAQ语料库中&#xff08;如mysql、ElasticSearch&#xff09…

# K近邻算法 度量距离

K近邻算法 度量距离 欧氏距离(Euclidean distance) 欧几里得度量(euclidean metric)(也称欧氏距离)是一个通常采用的距离定义&#xff0c;指在 m m m维空间中两个点之间的真实距离&#xff0c;或者向量的自然长度(即该点到原点的距离)。在二维和三维空间中的欧氏距离就是两点…

电商平台商品销量API接口,30天销量API接口接口超详细接入方案说明

电商平台商品销量API接口的作用主要是帮助开发者获取电商平台上的商品销量信息。通过这个接口&#xff0c;开发者可以在自己的应用或网站中实时获取商品的销量数据&#xff0c;以便进行销售分析、库存管理、市场预测等操作。 具体来说&#xff0c;电商平台商品销量API接口的使…

如何用c语言来判断素数

首先要知道什么是素数&#xff0c;就是素数的定义&#xff0c;素数一般指质数。质数是指在大于1的自然数中&#xff0c;除了1和它本身以外不再有其他因数的自然数。然后我们以100到200以内的数字来举例。 先用穷举法的思想&#xff0c;来把100到200的数字一个一个的列举出来&a…

[ 蓝桥杯Web真题 ]-Markdown 文档解析

目录 介绍 准备 目标 规定 思路 补充知识 解法参考 介绍 Markdown 因为其简洁的语法大受欢迎&#xff0c;已经成为大家写博客或文档时必备的技能点&#xff0c;众多博客平台都提倡用户使用 Markdown 语法进行文章书写&#xff0c;然后再发布后&#xff0c;实时的将其转化…

Meta开源最大多模态视频数据集—Ego-Exo4D

社交、科技巨头Meta联合15所大学的研究机构&#xff0c;经过两年多的努力发布了首个多模态视频训练数据集和基础套件Ego-Exo4D&#xff0c;用于训练和研究AI大模型。 据悉&#xff0c;该数据集收集了来自13个城市839名参与者的视频,总时长超过1400小时,包含舞蹈、足球、篮球、…

keepalived+lvs 对nginx做负载均衡和高可用

LVS_Director KeepAlivedKeepAlived在该项目中的功能&#xff1a; 1. 管理IPVS的路由表&#xff08;包括对RealServer做健康检查&#xff09; 2. 实现调度器的HA http://www.keepalived.orgKeepalived所执行的外部脚本命令建议使用绝对路径实施步骤&#xff1a; 1. 主/备调度器…

在线人数(oj题)

题目不少于5个字&#xff0c;所以整了个括号凑字数 首先我想到的是用一个数组来记录每一秒的在线人数 但是即使是short类型&#xff08;2字节&#xff09;&#xff0c;也会用到60 * 60 * 24 * 30 * 12 * 60 * 2 / 1024 / 1024 3,559.5703125 MB 而题目上限是256MB&#xff0…

Echarts饼图中显示百分比

开发中遇到一个需求&#xff0c;要在饼图上显示数据百分比&#xff0c;下图&#xff1a; 查了echarts 文档&#xff0c;并不能通过简单的配置来实现&#xff0c;原因如下&#xff1a;在单个serie的label中&#xff0c;只能设置一个label&#xff0c;位置可以选择在饼图内部inne…

SAP UI5 walkthrough step5 Controllers

在这个章节&#xff0c;我们要做的是&#xff0c;将之前的text文本展示为一个按钮&#xff0c;并将声明绑定在点击按钮事件。 因为改的是外观&#xff0c;所以我们修改的是view.XML webapp/view/App.view.xml <mvc:ViewcontrollerName"ui5.walkthrough.controller.A…