C++引用与引用两大应用场景,临时变量的常性,常引用与权限大小问题

news2025/1/14 18:29:13

tips

  1. 内存栈区的用习惯是先使用高地址,然后使用低地址嘛
  2. 顺序表数组支持随机下标访问,也是目前已知的仅有的数据结构
  3. 类当中的话,它不可以不仅可以去定义变量,它也可以定义函数,这个跟c当中的结构体不一样的,也就是给他升级了一下。

引用

在这里插入图片描述

  1. 引用就是取别名,没有去开新的内存空间,在引用当中也可以体现出c++的符号重载这个问题,&已经不是去取地址的意思
  2. (内存中的数据类型)&别名 = 本名/已有别名。
  3. 引用在定义的时候必须初始化,也就是说必须得说清楚到底是谁的引用。对于一个变量来说,它可以有多个引用,一个变量一旦引用了一个实体,就不能再去引用其他的实体了,终生制。

引用使用场景1 (输出型参数)部分取代指针+提高效率

  1. 就是体现在当往函数里面去传参的时候。
  2. 做一些输出型参数(本质上还是函数的参数,是函数参数的一种),什么叫做输出型参数?就是说形参的改变必须得影响到实参,也就是说当从函数里面退出来的时候,在函数里面的影响得持续到外面。与之相对应的就是输入性参数,输入型参数就是说这些参数传进来,它仅仅是给函数来用的,形参改变不影响实参。而对于输出型参数而言,形参的改变必须得影响到实参。经典操作就是swap。
  3. 在以前的话,针对输出型参数都是通过传址调用,也就是把指针传过去,从而达到函数出来之后能够保留修改影响的这个效果,但整体来说麻烦一点。尤其是在c语言的oj当中,int* returnSize。但现在可以改为int& returnSize,现在形参是实参的一个别名,一荣俱荣,一损俱损。
  4. 用引用来做参数还可以提高传参效率(针对大对象或者深拷贝类对象,什么是深拷贝以后会讲),那什么是大对象呢?sizeof比较大的对象。举个例子:
    在这里插入图片描述在这里插入图片描述
    测试代码:
#include <iostream>
#include <time.h>
using namespace std;
struct A 
{ 
	int a[10000]; 
};
void TestFunc1(A* a) 
{
}
void TestFunc2(A& a) 
{
}
void TestRefAndValue()
{
	A a;
	// 以值作为函数参数
	size_t begin1 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc1(&a);
	size_t end1 = clock();
	// 以引用作为函数参数
	size_t begin2 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc2(a);
	size_t end2 = clock();
	// 分别计算两个函数运行结束后的时间
	cout << "TestFunc1(A*)-time:" << end1 - begin1 << endl;
	cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}
int main()
{
	TestRefAndValue();
	return 0;
}
  1. 其实引用一定会有效率提升,因为他不需要去新开空间,像其他传参的话,他需要开一个空间,然后需要去拷贝(传值调用就不用说了),但是对于传址调用的话,其实到后面会讲,就是说如果你从底层的角度来理解,其实引用与指针两者是一模一样的,在底层当中的消耗是一样的,根据上面的测试结果也可以发现两者不分伯仲,在底层里面只有指针,没有引用
  2. 引用能做到的时候,指针都能做到,其实引用他革命的并不彻底,没有起到质的提升。
  3. 具体例子:
void Swap(int& x, int& y)
{
	int tmp = x;
	x = y;
	y = tmp;
}

函数的传值返回(必有临时变量)

  1. 函数返回的话,它也分为两种类型,一种是传值返回,一种是引用返回。
  2. 如果函数的返回是传值返回,那么也就意味着在函数的外面肯定有一个变量需要去接收它的返回值。从函数栈帧底层的角度来理解,是先需要把这个返回值给他放到一个临时变量(这是由编译器自己生成的,一般可能由寄存器代替,但不一定是寄存器,寄存器一般只有4/8字节,数据量大就over了,以后再说)里面,然后把这个函数栈帧给他全部先销毁掉,然后再把这个临时变量(寄存器一般来说)里面的值给他转移到函数外面(刚刚被销毁的函数栈帧上面高地址处的函数栈帧里面)的那个变量所在的内存空间里。如果说返回值它所在的地方是静态区,那就没事儿了,但编译器他并不会做过多的处理,他还是会像刚才那样生成一个临时变量(一般来说寄存器),编译器不想去搞特例,不来越来越乱可能。编译器他只看你这个函数的返回类型,如果是传值返回,不管你是局部变量还是全局变量,都会生成一个临时变量,先把这个返回值给他保存起来。然后做为函数外面某个式子的一个返回值,不管咋样都会生成临时变量(傻瓜式操作)
  3. 对于传值返回的话,它对于这个结果会先拷贝到一个临时变量当中,然后等到函数栈帧销毁之后,再把这个临时变量当中的值给到函数外面的某个接收处。
  4. 然后对于引用返回的话,是不存在拷贝这个环节的,此时此刻我返回的是一个别名,是引用,本来就是指定同一块空间的,拷贝啥?当在传值返回的时候,无论这个值是一个局部变量还是一个全局静态变量,编译器都会自己去生成一个临时变量,一般都是由寄存器来充当,然后把值放到那个临时变量当中,然后再拷贝到外面(我再说传值返回嗷)。

引用使用场景2(函数的引用返回)提高效率+仅限(static修饰,malloc等,全局静态变量)引用

  1. 函数返回的话,它也分为两种类型,一种是传值返回,一种是引用返回。
  2. 但也存在着可能,就是不生成临时变量,就是让引用作为返回值(传引用返回)。
  3. 这时候就不会再生成临时变量了。价值何在?减少了拷贝,提高了效率。尤其是那些大对象返回,效率差距就会拉开。
    在这里插入图片描述
#include <iostream>
#include <time.h>
using namespace std;
struct A 
{ 
	int a[10000]; 
};
A a;
// 值返回
A TestFunc1() 
{ 
	return a; 
}
// 引用返回
A& TestFunc2()
{
	return a;
}
void TestReturnByRefOrValue()
{
	// 以值作为函数的返回值类型
	size_t begin1 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc1();
	size_t end1 = clock();
	// 以引用作为函数的返回值类型
	size_t begin2 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc2();
	size_t end2 = clock();
	// 计算两个函数运算完成之后的时间
	cout << "TestFunc1 time:" << end1 - begin1 << endl;
	cout << "TestFunc2 time:" << end2 - begin2 << endl;
}

int main()
{
	TestReturnByRefOrValue();
	return 0;
}
  1. 如果说return n ,然后函数的返回类型是int&,那么这时候他返回的就是n的引用,也就是n的别名。
  2. 但显而易见,当一个东西是静态对象(在内存静态区)的时候,可以通过引用去返回,没有什么问题。
  3. 但如果说对这个函数栈帧里面的一个局部变量进行引用返回,那由于当你返回出去的时候,这个函数栈帧自己都老早销毁掉了,然后你居然通过引用等会儿就是去访问已经属于操作系统的内存空间,相当于就是野指针访问,结果就是不确定的。
  4. 问答1:ret多少? 1
#include <iostream>
using namespace std;
int& Count()
{
	static int n = 0;
	n++;
	// ...
	return n;
}
int main()
{
	int ret = Count();
	cout << ret << endl;
	return 0;
}
  1. 问答2:ret多少? 侥幸11
#include <iostream>
using namespace std;
int& Count(int x)
{
	int n = x;
	n++;
	return n;
}
int main()
{
	int& ret = Count(10);
	cout << ret << endl;
	return 0;
}

因为此时其实你画张图就很容易明白,此时此刻这个ret就是n的引用,但是变量n所在的内存空间早在函数退出的时候就已经还给操作系统了,这时候就需要去看这个函数栈帧销毁之后到底有没有被清理掉,如果没有清理栈帧,那么ret结果侥幸是正确为11。如果说清理了栈帧,那么ret的结果就是随机值,事实上这时候已经构成了非法访问
9. 问答3:ret分别多少? 侥幸11,21

#include <iostream>
using namespace std;
int& Count(int x)
{
	int n = x;
	n++;
	return n;
}
int main()
{
	int& ret = Count(10);
	cout << ret << endl;
	Count(20);
	cout << ret << endl;
	return 0;
}

这个还是关于函数栈帧的问题,这边可以发现就是当你这个函数栈帧销毁的时候,但此时此刻原先这个函数栈帧还没有被清理掉,所以此时此刻你去访问ret(n的引用)还是能够访问的到值,一开始是11。然后后面又去调用了count函数,相当于在原先刚刚被摧毁掉的函数栈帧那块空间上又开辟了一个函数栈帧(这两个函数是一模一样的,所以说原先n的那块空间,现在又给到了数字20,然后加一变成了21),但当这个函数的函数栈帧也被销毁之后,它还是没有被清理掉,因此访问的话ret就为21。但实际上两次访问都是构成了非法访问,因为ret此刻所存在的内存空间已经是属于操作系统了,但只需稍微改动,便能破坏原先侥幸尚存的未被处理的内存里面的分步状况
在这里插入图片描述
10. 具体例子:比方说现在用一个函数就可以实现对顺序表的指定位置的访问与修改
在这里插入图片描述
因为我这个函数返回的是整个顺序表当中第pos个元素的引用,属于是引用返回,返回一方面他确实可以提高效率,减少拷贝,引用的第二个功能就是说可以去修改它的返回值,读写返回值,非常强大
在这里插入图片描述

关于临时变量的问题 (临时变量具有常性)

  1. 在各种类型转换(整形提升,算数转换,自己强制类型转换)当中,以及包括各种截断等,中间它都会产生一个临时变量(就跟函数传值返回一样)
  2. 是先把源头的数据拷贝给到这个临时变量当中。然后需要特别注意的是临时变量具有常性(你可以理解成相当于被const修饰了一样,不能修改
  3. 那为什么类型转换一定要产生临时变量呢?(临时变量具有常性,你可以把它理解成相当于用const的修饰了一样),比方说,举两个例子,变量i是int类型的,然后变量d是double类型的,然后比如说有个表达式是i<d,在这个过程当中很明显会发生类型转化(算术提升),但他其实是先生成一个double类型(8个字节)的一个临时变量,然后再把i的数据拷贝到临时变量当中然后自己提升与类型转换,这个类型转换的过程并不是在变量i自己所在的内存空间当中去进行的,而是先会开辟出一块八个字节的临时变量空间,然后再进行折腾。
  4. 再说变量i总共就四个字节,他自己内部怎么可能也类型提升不到像double类型这样的八个字节嘛。就是说你再进行这么类型转换的过程当中,你是不能对两个终端进行改动的,谁要进行类型转换,就开辟一块转换后类型大小的内存空间,然后在临时变量空间当中去进行操作
  5. 函数的传值返回也会有一个临时变量的问题,因为马上故土要被攻占了,所以说先把返回值放到一个临时变量里面安全一点,至少这个值我要在,当函数栈帧被销毁之后,再把临时变量里面的这个值给他赋给上层函数的某个接收处

在这里插入图片描述
7.
在这里插入图片描述
8.
在这里插入图片描述

常引用与引用的权限大小问题

在这里插入图片描述
他这个权限主要针对的就是对于一个常量还是变量的可读可写的问题,但是各个外号与变量名之间的权限互不干涉与交叉。虽然他们指向的都是同一内存空间。
在这里插入图片描述

  1. 首先这边涉及到常引用,然后再去判断一下权限有没有出错,果然,权限放大了,错
#include <iostream>
#include <assert.h>
using namespace std;
int main()
{
	const int a = 10;
	int& b = a;
	return 0;
}
  1. 首先这边涉及到常引用,然后再去判断一下权限有没有出错,权限平移,对
#include <iostream>
#include <assert.h>
using namespace std;
int main()
{
	const int a = 10;
	const int& b = a;
	return 0;
}
  1. 这边根本就没有涉及到引用,这边就是一个简单的赋值拷贝,完全没啥问题,你不要哪壶不开提哪壶,若在应用的过程当中才有权限的放大啊,缩小啊,平移呀等等,在平时的一般赋值啊,拷贝啊什么的,根本就不用去管什么权限的放大平移还是缩小,管什么呢?仅限于引用。
#include <iostream>
#include <assert.h>
using namespace std;
int main()
{
	const int a = 10;
	int c = a;
	double n = a;
	return 0;
}
  1. 首先这边涉及到常引用,然后再去判断一下权限有没有出错,权限缩小与平移,对
#include <iostream>
#include <assert.h>
using namespace std;
int main()
{
	int a = 10;
	const int& b = a;
	int& c = a;
	return 0;
}
  1. 对,权限平移
#include <iostream>
#include <assert.h>
using namespace std;
int main()
{
	double a = 1.1;
	double& b = a;
	return 0;
}
  1. 错,首先这边就已经发生了类型转换,类型转换就意味着有临时变量的生成,此时此刻你引用的时候,你的权限不能放大,也就是不能可读可写,只能平移,也就是只读
#include <iostream>
#include <assert.h>
using namespace std;
int main()
{
	double a = 1.1;
	int& b = a;
	float& c = a;
	return 0;
}
  1. 可以,权限平移
#include <iostream>
#include <assert.h>
using namespace std;
int main()
{
	double a = 1.1;
	const int& b = a;
	const float& c = a;
	return 0;
}
  1. 函数结合起来如下:
int func1()
{
	static int x = 0;
	return x;
}
int& func2()
{
	static int x = 0;
	return x;
}
int main()
{
	int& ret1 = func1();  // 权限放大
	const int& ret1 = func1(); // 权限平移
	int ret1 = func1();  // 拷贝
	int& ret2 = func2();		// 权限平移
	const int& rret2 = func2();  // 权限缩小
	return 0;
}

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

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

相关文章

数据结构考研版——队列的配置问题

一、正常配置下的情况 队空状态 frontrear;入队操作 出队操作 队满状态 在正常配置下元素的个数&#xff08;rear>front&#xff09; 当rear<front 综上所述用一个表达式表示&#xff1a;(rear-frontmaxSize)%maxSize 二、非正常配置下的情况1 队空状态 入队操作…

Stable Diffusion-webUI ckpt模型、lora模型的区别和使用

一、常用的两种模型&#xff1a;ckpt和Lora分别是什么&#xff1f;有什么区别&#xff1f; 1、CKPT&#xff08;CheckPoint&#xff09; 经过训练的图片合集&#xff0c;被称作模型&#xff0c;也就是chekpoint&#xff0c;体积较大&#xff0c;一般真人版的单个模型的大小在…

StarRC的妙用

在整个R2G的流程里边&#xff0c;寄生参数抽取&#xff08;StarRC&#xff09;是比较没有存在感的。大部分的时间&#xff0c;工程师们只是用这个工具来刷SPEF。并不会关注太多。这本身其实是一个好事情&#xff0c;反向证明了参数抽取工具的高度稳定性&#xff01; 但是&#…

虚拟数字人的3种驱动方式

虚拟数字人是由计算机程序所构建的具有人类特征的虚拟实体&#xff0c;目前的虚拟数字人经过了三代的更迭&#xff0c;划分每一代更迭的标准则是虚拟数字人的驱动方式。 一、虚拟数字人1.0&#xff1a;动画&CG驱动 虚拟数字人1.0就是目前我们所熟知的&#xff0c;比如&am…

[NOIP2000 提高组] 进制转换

[NOIP2000 提高组] 进制转换 题目描述 我们可以用这样的方式来表示一个十进制数: 将每个阿拉伯数字乘以一个以该数字所处位置为指数,以 10为底数的幂之和的形式。例如 123 可表示为 10^22*10^13*10^0 这样的形式。 与之相似的&#xff0c;对二进制数来说&#xff0c;也可表示成…

运行时内存数据区之执行引擎(二)

JIT编译器 第一种是将源代码编译成字节码文件&#xff0c;然后在运行时通过解释器将字节码文件转为机器码执行。 第二种是编译执行&#xff08;直接编译成机器码&#xff09;。现代虚拟机为了提高执行效率&#xff0c;会使用即时编译技术(JIT,Just In Time)将方法编译成机器 …

北邮22信通:(13)二叉树 书上重要知识点补充 例4.3 统计结点总数 深度和叶子结点数

北邮22信通一枚~ 跟随课程进度每周更新数据结构与算法的代码和文章 持续关注作者 解锁更多邮苑信通专属代码~ 上一篇文章&#xff1a; 下一篇文章&#xff1a; 目录 一.统计结点总个数 二.统计二叉树深度 三.统计叶子结点总数 四.完整代码 4.1测试int存储类型&…

网络原理基础(认识IP地址、子网掩码、端口号、协议、五元组)

文章目录 前言一、网络通信基础1、IP地址2、子网掩码3、端口号4、协议5、五元组 二、协议基础知识1.协议分层2.OSI七层模型3、TCP/IP五层(或四层)模型4、网络设备所在分层5、封装和分用 总结 前言 网络互连的目的是进行网络通信&#xff0c;也即是网络数据传输&#xff0c;更具…

Mac/Linux系统idea启动springboot项目慢;Oracle数据库连接:ORA-21561: OID generation failed

Mac/Linux系统idea启动springboot项目慢;Oracle数据库连接:ORA-21561: OID generation failed 解决方案&#xff1a; 1、终点输入localhost查看&#xff1b;发现我这里是local 2、终点输入cat /etc/hosts查看配置&#xff1b; hosts文件中127.0.0.1的映射是&#xff1a;127…

react 之 useState

参考&#xff1a;https://blog.csdn.net/Ljwen_/article/details/125319191 一、基本使用 useState是 react 提供的一个定义响应式变量的 hook 函数&#xff0c;基本语法如下&#xff1a; const [count, setCount] useState(initialCount)它返回一个状态和一个修改状态的方…

R语言ggplot2 | 绘制随机森林重要性+相关性热图

&#x1f4cb;文章目录 原图复现准备数据集及数据处理构建不同分类随机森林模型的并行计算绘制随机森林变量重要性柱状图计算数据集的相关性热图可视化合并随机森林重要性和热图 附上所有代码 在文献中&#xff0c;我们经常遇到随机森林和相关性热图的组合图片(下图)&#xff0…

LeetCode热题HOT100:76. 最小覆盖子串,84.柱状图中最大的矩形、96. 不同的二叉搜索树

LeetCode 热题 HOT 100 76. 最小覆盖子串 题目&#xff1a;给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串&#xff0c;则返回空字符串 “” 。 注意&#xff1a; 对于 t 中重复字符&#xff0c;我们寻找的子字…

Ubuntu18.04获取root权限并用root用户登录

写在前面&#xff1a;以下步骤中需要在终端输入命令&#xff0c;电脑端查看博客的朋友可以直接复制粘贴到终端&#xff0c;手机端查看的朋友请注意命令里面的空格是必须的&#xff0c;否则运行会出错。 1.为root设置初始密码 &#xff08;1&#xff09;登录系统&#xff0c;打…

【unity实战】随机地下城生成1

先看看最终效果 导入素材 导入房间图片素材,配置图片信息信息 点击sprite Editor,开始切割图片 随机创建基本房间 已一个白底图片模拟房间预设体 思路:建立一个空的 GameObject 用来做创建房间的点,设置坐标(0,0,0)。每创建1个房间之后,随机在上、下、右判断是否有…

python以及PyCharm工具的环境安装与配置

这里以Windows为例 Python的安装 当然是到Python官网下载咯&#xff0c;https://www.python.org/downloads/点我直达&#xff0c;如图&#xff1a; 可以下载最新版本&#xff0c;可以下拉找到之前特定的版本安装&#xff0c;如图&#xff1a; 这里先择的是最新版的进行安装…

leetcode每日一题:链表专题篇第一期(1/2)

&#x1f61a;一个不甘平凡的普通人&#xff0c;日更算法学习和打卡&#xff0c;期待您的关注和认可&#xff0c;陪您一起学习打卡&#xff01;&#xff01;&#xff01;&#x1f618;&#x1f618;&#x1f618; &#x1f917;专栏&#xff1a;每日算法学习 &#x1f4ac;个人…

现在备考2023年5月软考网络工程师时间够吗?

距离2023年5月软考还有1个多月的时间&#xff0c;备考网络工程师的时间是够的&#xff0c;以下是一些备考方法&#xff1a; 1.了解考试内容 在你开始学习考试之前&#xff0c;了解考试的形式和内容是很重要的。这将帮助你把注意力集中在最有可能被测试的领域。你应该复习考试…

Gartner Magic Quadrant for SD-WAN 2022 (Gartner 魔力象限:软件定义广域网 2022)

Gartner 魔力象限&#xff1a;SD-WAN 2022 请访问原文链接&#xff1a;https://sysin.org/blog/gartner-magic-quadrant-sd-wan-2022/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org Gartner 魔力象限&#xff1a;SD-WAN 2022…

ChatGPT最强竞争对手,无需魔法,直接使用

无需科学上网&#xff0c;无需加入Waitlist&#xff0c;免费使用&#xff0c;没有高峰限制&#xff0c;而且效果媲美ChatGPT&#xff01; ChatGPT 的最强竞争对手Claude!!! Claude 是由Anthropic这家人工智能公司开发出来的&#xff0c;其联合创始人Dario Amodei曾经担任OpenAI…

K8S使用持久化卷存储到NFS(NAS盘)

参考文章&#xff1a;K8S-v1.20中使用PVC持久卷 - 知乎 Persistent Volumes&#xff1a;PV是持久化卷&#xff0c;系统管理员设置的存储&#xff0c;它是群集的一部分&#xff0c;是一种资源&#xff0c;所以它有独立于Pod的生命周期 Persistent Volume Claim&#xff1a;PVC…