C++第三节入门 - 引用详解

news2024/9/20 9:32:12

引用

引用可以对别名进行引用!

#include<iostream>
using namespace std;

int main()
{
	int a = 0;    // 李逵
	int& b = a;   // 铁牛
	int& c = b;	  // 在铁牛的基础上取名为黑旋风
	return 0;
}

引用的特性:

  1. 引用在定义的时候必须初始化;
  2. 一个变量可以有多个引用(可以对引用再进行引用);
  3. 引用一旦引用一个实体,再不能引用其他实体

java当中没有指针! 因此每个节点中存放的是下一个节点的引用!

C++不能这样实现,因为C++中的引用是不能被修改的!

同一个域的引用不能同名;不同域的引用可以同名!

引用的使用场景

  • 做参数(输出型参数)

void Swap(int& left, int& right)
{
   int temp = left;
   left = right;
   right = temp;
}
  • 输出型参数:此时形参的改变会影响实参(引用)
  • 输入型参数 :形参的改变不会影响实参!

struct + 结构体得名字才是结构体的类型!(C语言中)

但是C++可以直接使用结构体的名字!

引用做参数还可以提高效率(后面详解)(大对象/深拷贝对象)

#include<iostream>
using namespace std;

void swap(int& p, int& q)
{
	int tmp = p;
	p = q;
	q = tmp;
}

int main()
{
	int aa = 10;    // 李逵
	int bb = 20;
	int& a = aa;   // 铁牛
	// int& c = b;	  // 在铁牛的基础上取名为黑旋风
	int& b = bb;
	cout << aa <<' '<<bb<< endl;
	//cout << bb << endl;
	swap(aa, bb);
	cout << aa << ' ' << bb << endl;


	return 0;
}

做返回值

分析下面两个代码:

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

(n拷贝给临时变量,临时变量再拷贝给ret!) 

 不能直接返回n的值,因为n在栈区,出作用域直接销毁,因此会创建临时变量!(详细解释如下:)

        中间会生成临时变量,临时变量可能由寄存器代替,会把临时变量放到寄存器里面,作为表达式的返回值,再给ret!

        但是寄存器一般只有4/8个字节,数据量大就放不下!

        在C/C++中,当你从一个函数返回一个局部变量(如n)的值时,编译器会创建一个临时变量来存储这个值。这个临时变量通常存放在栈区(stack)中。
        具体来说,当函数Count1执行完毕后,局部变量n会被销毁,但在返回时,编译器会将n的值复制到一个临时变量中。这个临时变量的生命周期会持续到它被使用完为止,通常是在调用函数的上下文中。
        因此,虽然n在函数结束时会被销毁,但返回的值(即临时变量的值)仍然可以在调用该函数的地方使用。这个临时变量的存储位置仍然是在栈区,直到它不再被需要。

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

此时n位于静态区,出作用域不会被销毁! 

那么此时n会创建临时变量吗?

会!编译器不看变量在栈区还是静态区,只要是传值返回,都会创建临时变量!(傻瓜处理)

此时,用引用做返回值就不会生成临时变量!

  • 减少拷贝,提高效率(大对象影响很大)

        以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效
率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。

值和引用的作为返回值类型的性能比较:

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

struct A { int a[10000]; }; // 定义结构体A,包含一个大小为10000的整数数组  
A a; // 创建一个全局变量a,类型为A  

// 值返回  
A TestFunc1() { return a; } // 定义一个函数TestFunc1,返回全局变量a的副本  

// 引用返回  
A& TestFunc2() { return a; } // 定义一个函数TestFunc2,返回全局变量a的引用  

void TestReturnByRefOrValue() {  
    // 以值作为函数的返回值类型  
    size_t begin1 = clock(); // 记录开始时间  
    for (size_t i = 0; i < 100000; ++i) // 循环调用TestFunc1 100,000次  
        TestFunc1();  
    size_t end1 = clock(); // 记录结束时间  

    // 以引用作为函数的返回值类型  
    size_t begin2 = clock(); // 记录开始时间  
    for (size_t i = 0; i < 100000; ++i) // 循环调用TestFunc2 100,000次  
        TestFunc2();  
    size_t end2 = clock(); // 记录结束时间  

    // 计算两个函数运算完成之后的时间  
    cout << "TestFunc1 time: " << end1 - begin1 << " ms" << endl; // 输出TestFunc1的执行时间  
    cout << "TestFunc2 time: " << end2 - begin2 << " ms" << endl; // 输出TestFunc2的执行时间  
}  

int main() {  
    TestReturnByRefOrValue(); // 调用性能测试函数  
    return 0; // 返回0表示程序正常结束  
}

分析下面用引用做返回值的代码 :

这里ret与n的关系是拷贝! (栈帧的销毁不会影响ret)

这个代码的输出结果为随机值!

n所在的空间已经被销毁!(归还它的使用权) 此时访问的数据的结果是不确定的!访问的是随机值!

  • 如果Count函数结束,栈帧销毁,没有清理栈帧,那么ret的结果是侥幸正确的!
  • 如果Count函数结束,栈帧销毁,清理栈帧,那么ret的结果是随机值!

相当于访问的是野指针!

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

int main()
{
	//int ret2 = Count2();
	int& ret2 = Count2();
	cout << ret2 << endl;
	return 0;
}

这种情况下:count()等于n的别名,ret2是count2的别名,相当于给别名取别名!

此时ret2也是n的别名!

  • ret是n的别名,当函数调用结束后,栈帧销毁,如果没有清理栈帧,此时两次打印ret的值为11和21;(访问的是一个已经被销毁的空间的变量);
  • 第二次调用函数所占的空间一样大,将原来的10覆盖为20,
  • 如果栈帧被清理,此时打印的就是随机值!

但是如果此时调用任何一个其他的函数,ret的值就为一个随机值!

因为调用下一个函数的时候,需要建立函数栈帧,正好将上面的空间覆盖,覆盖后为什么样子不确定,此时再访问这块空间就为一个随机值!(再次调用count()是在同一块空间的同一块位置将其值从10覆盖为20)

因此!上面的做法不可取!返回局部作用的变量的引用非常危险!

我们采用以下做法:

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

int main()
{
	int ret = Count();
	cout << ret << endl;
	return 0;
}

此时栈帧销毁,n位于静态区,n一直存在,不会影响返回!

 总结:

  1. 基本任何场景都可以使用引用传参;
  2. 谨慎用引用做返回值,出了函数作用域,对象不在了,就不能引用返回,还在就可以用引用返回!

可以使用引用返回的:static修饰的,全局变量、malloc开辟的,常量值.

常引用

第一种

const int a = 0;
int& b = a;

这种写法是错误的!权限不能扩大!a的值为常量不能改变 ;

第二种

const int c = 0;
int d = c;

这种写法可以!因为c拷贝给d,此时d的改变不影响c;

第三种

int main()
{
	int a = 10;
	int& b = a;   // 平移
	const int& z = a;    // 缩小
	a++;
	z++;   // 这种写法是错误的,z不能修改!
	cout << a << endl;
	cout << z << endl;

	return 0;
}

引用过程中,权限可以平移或者缩小,但是不能放大!

这里a++的时候z也能++,但是不能使用z++!

第四种:

const int& m = 10;

权限进行了平移,两边都不能修改!

第五种:

	double a = 1.11;
	int b = a;
	int& c = a;  // 这种行不通!!!
	const int& d = a;
    double& e = a;

b实际上是强制类型转换,发生类型转换(类型提升,截断等)的时候会产生临时变量,类似于传值返回!

实际上是把dd给临时变量,再将临时变量给ri,因此,中间会产生一个int的临时变量!

对应的,下面的实际上是将dd给临时变量,再把临时变量给rii!

临时变量具有常性!相当于被const修饰!

因此这样子不满足不是因为类型不行,而是因为权限放大!

第六种

上面这种写法不可取!因为func1()返回的是一个临时变量,临时变量具有常属性!此时这样子做会造成权限的方法!

解决方法:前面加上const即可!

当函数返回一个基本数据类型的值时,返回的是该值的副本。这个副本是一个临时变量,不能被修改,所以我们一般用一个变量来接受这个函数的返回值!(传值返回)

第七种:

上面这两种都可以!(没有产生临时对象)

分析下面这种特殊情况:

当j和i进行比较的时候,实际上i的值没有发生改变,但是会产生一个临时变量,将i转化为double类型的临时变量,此时再用这个临时变量和j进行比较!

指针和引用的区别

语法层面上:

int main()
{
	int a = 10;

	// 语法层面上,不开空间,是对a取别名
	int& b = a;
	b = 20;
	// 语法层面上,开空间,存储a的地址
	int* ps = &a;
	*ps = 30;
	return 0;
}

先看指针:

  • lea是取地址的意思:取a的地址放到eax中(eax是一个寄存器);
  • 然后把eax的值放入pa中,pa存放的是地址;(内存的速度太慢了,一般都会借助寄存器,缓存当中转!)
  • 解引用的时候将pa的值放到eax中;再对eax解引用将30放进去!

可以发现:从底层汇编指令实现的角度来看:引用是类似指针的方法实现的!

语法层面上我们认为引用不开辟空间,但是从底层来看引用还是会开辟空间!(底层和上层可能是不一样的!)

引用和指针的区别:

1. 引用概念上定义一个变量的别名,指针存储一个变量地址;
2. 引用在定义时必须初始化,指针没有要求;
3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何
一个同类型实体;
4. 没有NULL引用,但有NULL指针;
(引用不能指向 NULL 是因为引用必须始终绑定到一个有效的对象,以确保安全性和简洁性。引用的设计旨在避免指针所带来的复杂性和潜在错误,因此不允许引用指向 NULL。如果需要表示“无对象”的状态,应该使用指针。)
5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节);
6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小;
7. 有多级指针,但是没有多级引用 ;
8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理 ;
9. 引用比指针使用起来相对更安全 ;

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

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

相关文章

『功能项目』单例模式框架【37】

我们打开上一篇36C#拓展 - 优化冗余脚本的项目&#xff0c; 本章要做的事情是编写单例模式基类&#xff0c;让继承其基类的子类在运行时只存在一个&#xff0c;共有两个单例基类框架&#xff0c;分别是不继承MonoBehaviour的单例和继承MonoBehaviour的单例框架 首先编写不继承…

【最新华为OD机试E卷-支持在线评测】跳马(200分)多语言题解-(Python/C/JavaScript/Java/Cpp)

🍭 大家好这里是春秋招笔试突围 ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-E/D卷的三语言AC题解 💻 ACM金牌🏅️团队| 多次AK大厂笔试 | 编程一对一辅导 👏 感谢大家的订阅➕ 和 喜欢💗 🍿 最新华为OD机试E卷,全、新、准,题目覆盖率达 95% 以上,支持…

LabVIEW重构其他语言开发的旧系统

在面对一个运行已久、代码不清晰的项目时&#xff0c;如果该项目涉及复杂的通讯协议&#xff08;如串口和488通讯&#xff09;&#xff0c;重新开发并优化成LabVIEW版本可以极大提升系统的易用性和维护性。为了确保通讯协议的顺利解析和移植&#xff0c;借助专业工具分析现有通…

【OpenCV-阈值与平滑处理】灰度图、HSV、图像阈值、图像平滑处理(方框滤波、均值滤波、高斯滤波、中值滤波)

1 灰度图 import cv2 # 导入 OpenCV 库&#xff0c;用于图像处理 import numpy as np # 导入 NumPy 库&#xff0c;用于数组操作 import matplotlib.pyplot as plt # 导入 Matplotlib 库&#xff0c;用于绘图# %matplotlib inline 是 Jupyter Notebook 特有的魔法命令&…

流媒体平台/视频监控/安防视频汇聚EasyCVR播放暂停后视频画面黑屏是什么原因?

视频智能分析/视频监控/安防监控综合管理系统EasyCVR视频汇聚融合平台&#xff0c;是TSINGSEE青犀视频垂直深耕音视频流媒体技术、AI智能技术领域的杰出成果。该平台以其强大的视频处理、汇聚与融合能力&#xff0c;在构建全栈视频监控系统中展现出了独特的优势。视频监控管理系…

kitti数据label的2d与3d坐标转为像素坐标方法与教程(代码实现)

文章目录 前言一、kitti标签label坐标转换的主函数1、主函数调用代码2、数据格式示意图二、kitti数据获取1、图像获取2、label标签数据获取3、标定文件数据获取 三、kitti标签坐标转换方法1、集成主函数-labels_boxes2pixel_in_image2、标签3d坐标转像素坐标-compute_box_3d(ob…

Caffenie配合Redis做两级缓存

一、什么是两级缓存 在项目中。一级缓存用Caffeine&#xff0c;二级缓存用Redis&#xff0c;查询数据时首先查本地的Caffeine缓存&#xff0c;没有命中再通过网络去访问Redis缓存&#xff0c;还是没有命中再查数据库。具体流程如下 二、简单的二级缓存实现-v1 目录结构 2…

MySQL——主从复制、读写分离

目录 前言 一、MySQL主从复制的概述 1、MySQL主从复制的概念 2、Mysql主从复制功能和使用场景 2.1、Mysql主从复制功能 2.2、Mysql主从复制使用场景 3、MySQL支持的复制类型 3.1、基于语句的复制 3.2、基于行的复制 3.3、混合复制 4、主从复制的工作过程 5、MySQL三…

iOS 15推出后利用邮件打开率的7种方法

自从苹果在2021年底推出iOS 15以来&#xff0c;邮件打开率就一直是一个让人头疼的指标。 Klaviyo市场情报主管Mindy Regnell表示&#xff1a;“对于启用了Apple邮件隐私保护&#xff08;MPP&#xff09;的用户来说&#xff0c;苹果会打开这些邮件并预先下载内容到他们的服务器…

2024年“华为杯”第二十一届中国研究生数学建模竞赛(附2004-2023年优秀论文合集)

中国研究生数学建模竞赛&#xff08;以下简称“竞赛”&#xff09;是教育部学位管理与研究生教育司指导&#xff0c;中国学位与研究生教育学会、中国科协青少年科技中心主办的“中国研究生创新实践系列大赛”主题赛事之一。本届比赛报名时间为&#xff1a;2024年6月1日&#xf…

数据结构——线性表(静态链表、循环链表以及双向链表)

1、静态链表 用数组描述的链表叫做静态链表&#xff0c;这种描述方法叫做游标实现法。 静态链表需要对数组的第一个和最后一个元素作为特殊元素处理&#xff0c;不存数据。 最后一个指向第一个有数据的下标地址&#xff0c;第一个游标指向第一个没有数据的下标地址。 我们对…

[译] 大模型推理的极限:理论分析、数学建模与 CPU/GPU 实测(2024)

译者序 本文翻译自 2024 年的一篇文章&#xff1a; LLM inference speed of light&#xff0c; 分析了大模型推理的速度瓶颈及量化评估方式&#xff0c;并给出了一些实测数据&#xff08;我们在国产模型上的实测结果也大体吻合&#xff09;&#xff0c; 对理解大模型推理内部工…

职场答案薄

公司做大的过程就是创始人把职责一层层分摊下去的过程&#xff0c;公司里的各级领导在招聘时的原始诉求都是一样的&#xff0c;就是招到可以帮自己分担一部分工作的人&#xff0c;然后自己好集中精力去做更重要的工作 如何去做运营 1.流程制度&#xff08;三个目的&#xff1a;…

AI边缘计算在安防领域的智能化革新:赋能安防系统的智能化升级

随着人工智能&#xff08;AI&#xff09;和边缘计算技术的快速发展&#xff0c;两者在安防视频领域的应用日益广泛&#xff0c;为传统安防系统带来了革命性的变革。AI边缘计算技术通过将AI算法和模型部署在边缘设备上&#xff0c;实现了数据处理和智能决策的即时响应&#xff0…

FuTalk设计周刊-Vol.073

#AI漫谈 热点捕手 1.Midjourney 样式分享网站 群里设计师创建的 Midjourney 风格网站&#xff0c;用来收集高质量 Sref Codes&#xff0c;展示示例图并且展示风格关键词&#xff0c;使用场景&#xff0c;以及示例提示词&#xff0c;作者称会持续更新 链接https://aiartsecre…

【Java】String StringBuffer与StringBuilder(实操+面试+记忆方法)

Java系列文章目录 补充内容 Windows通过SSH连接Linux 第一章 Linux基本命令的学习与Linux历史 文章目录 Java系列文章目录一、前言二、学习内容&#xff1a;三、问题描述四、解决方案&#xff1a;4.1 代码学习与性能测试4.1.1 代码4.1.2 性能测试结果 4.2 区别 五、总结&#…

uniapp升级Vue3:避坑指南与步骤详解

为什么要升级到 Vue3 Vue3 是 Vue.js 的最新版本&#xff0c;相比 Vue2&#xff0c;它带来了许多改进和新特性&#xff0c;比如更小的包体积、更好的性能、更强大的组合式 API 等。通过升级到 Vue3&#xff0c;我们可以享受到这些新特性带来的好处&#xff0c;提升项目的开发效…

全国-住宅区AOI数据

数据量级&#xff1a;54万&#xff0c;更新时间&#xff1a;2024年3月 覆盖字段&#xff1a; 名称&#xff0c;地址&#xff0c;经纬度&#xff0c;一级分类&#xff0c;二级分类&#xff0c;三级分类&#xff0c;默认图片&#xff0c;AOI围栏 数据来源于&#xff1a;魔行观察…

从知识孤岛到知识共享:内部知识库如何促进团队协作

在当今快速变化的商业环境中&#xff0c;企业内部的知识管理和协作能力成为决定其竞争力的关键因素之一。然而&#xff0c;许多企业面临着“知识孤岛”的困境&#xff0c;即各部门和团队之间信息交流不畅&#xff0c;知识和经验难以有效传递和共享&#xff0c;导致资源浪费、决…

Sanster/IOPaint:AIGC大模型GFPGAN人像修复/美颜:修复历史人物老照片(3)

Sanster/IOPaint&#xff1a;AIGC大模型GFPGAN人像修复/美颜&#xff1a;修复历史人物老照片&#xff08;3&#xff09; 使用大模型GFPGAN进行人像修复/美颜&#xff0c;比如修复历史上某些人物的老照片。 一、首先pip安装组件依赖库&#xff1a; pip install gfpgan 二&…