【C++】初阶 --- 引用(超级详细版!!!)

文章目录

  • 🍪一、引用的概念
  • 🍪二、引用的特性
    • 🍿1、引用在定义时必须初始化
    • 🍿2、一个变量可以有多个引用
    • 🍿3、引用一旦引用一个实体,再不能引用其他实体
  • 🍪三、常引用(被const 修饰的引用)
    • 🍿1、权限的放大
    • 🍿2、权限的平移
    • 🍿3、权限的缩小
    • 🍿4、临时变量具有常性
  • 🍪四、引用的使用场景
    • 🍿1、做参数(传引用传参)
    • 🍿2、做返回值(引用做返回值)
    • 🍿3、传值、传引用效率比较
      • 🥗 传值和传引用的作为参数的性能比较
      • 🥗 传值和传引用的作为返回值的性能比较
    • 🍿4、传引用返回修改返回对象
    • 🍿5、总结
  • 🍪五、引用和指针的区别

在这里插入图片描述

🍪一、引用的概念

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
比如:李逵,在家称为"铁牛",江湖上人称"黑旋风"
比如:抓 “周树人” 和我 "鲁迅"有什么关系🤭,本质上其实是一个人
在这里插入图片描述

🚩引用的操作符: &
🚩类型& 引用变量名(对象名) = 引用实体

🌰举个栗子演示👇

int main()
{int a = 0;int& b = a; //引用cout << &a << endl;cout << &b << endl;return 0;
}

在这里插入图片描述

☝️代码段中相当于给变量a取了一个别名b,通过标识名b可以在其被定义的作用域中访问变量a,可以看到地址是一样的,说明a和b共用同一块内存空间

🚨注意:引用类型必须和引用实体是同种类型的

🍪二、引用的特性

🍿1、引用在定义时必须初始化

🥰请看代码与注释👇

void TestRef()
{int a = 10;// int& ra;  // 该条语句编译时会出错int& ra = a;cout << a << "  " << &a << endl;cout << ra << "  " << &ra << endl;
}int main()
{TestRef();return 0;
}

🚨int& ra; 该条语句编译时会出错,是不可以的,必须要进行初始化

在这里插入图片描述

🍿2、一个变量可以有多个引用

🚩一个变量可以有多个引用,并且引用可以嵌套定义

🥰请看代码与注释👇

void TestRef()
{int a = 10;// int& ra;  // 该条语句编译时会出错int& ra = a; // ra是a的引用int& rra = a; // rra是a的引用int& rrra = ra; // rrra是ra的引用cout << a << "  " << &a << endl;cout << ra << "  " << &ra << endl; cout << rra << "  " << &rra << endl;cout << rrra << "  " << &rrra << endl;
}int main()
{TestRef();return 0;
}

在这里插入图片描述

基于引用这种可以嵌套定义并且无需多次解引用就可以直接访问被引用变量的这种特性,很多时候使用引用可以避免多级指针的出现

🍿3、引用一旦引用一个实体,再不能引用其他实体

🌰举个栗子演示👇

void TestRef()
{int a = 10;int b = 20;int& x = a; int& x = b; cout << a << "  " << &a << endl;cout << x << "  " << &x << endl; cout << b << "  " << &b << endl;cout << x << "  " << &x << endl;
}

在这里插入图片描述

由于这个特性,引用无法完全代替指针(比如链表中结构体的next指针无法用引用来代替,因为引用一旦引用一个实体,再不能引用其他实体),灵活性也不如指针,但是引用也因此比指针更安全,这也是引用这个语法的设计初衷之一(使用指针很容易出现野指针,非法访问内存空间的情况)

🍪三、常引用(被const 修饰的引用)

🚨在引用的过程中,权限可以平移,权限可以缩小,但是权限不能放大

🍿1、权限的放大

假如a是鲁智深 可以喝酒 可以吃肉 不能杀人
给a取个别名b 叫花和尚 可以喝酒 可以吃肉 可以杀人?
a(鲁智深)和 b(花和尚)是同一个人,当然不可以杀人

🌰举个栗子👇

int main()
{//权限的放大const int a = 0;int& b = a;return 0;
}

🍿2、权限的平移

🌰举个栗子👇

int main()
{//权限的平移const int a = 0;const int& b = a;return 0;
}

🍿3、权限的缩小

🌰举个栗子👇

int main()
{//权限的缩小int a = 0; //a可以修改,可以影响bconst int& b = a;return 0;
}

a可以修改,可以影响b

🍿4、临时变量具有常性

🌰举个栗子👇

int main()
{int i = 0;double& d = i;//临时变量具有常性return 0;
}

在这里插入图片描述
✅正确操作👇

int main()
{int i = 0;const double& d = i;return 0;
}

🚩这里涉及一个知识点:
代码段中b去引用i,i会发生隐式类型转换,i转换的结果会存入一个临时空间中。
(当赋值等号右边有运算表达式或有变量发生类型转换时,表达式或类型转换的结果都会先存入一个临时空间后再赋值给等号左边的变量)

因此这里的d引用的实质上是一块临时空间:
在这里插入图片描述

🍪四、引用的使用场景

🍿1、做参数(传引用传参)

🌰举个栗子👇

void Swap(int& left, int& right)
{int temp = left;left = right;right = temp;
}int main()
{int a = 10;int b = 20;Swap(a, b);cout << "a = " << a << endl;cout << "b = " << b << endl;return 0;
}

在这里插入图片描述

🍿2、做返回值(引用做返回值)

先看一下传值返回👇

//传值返回    
int Count()
{int n = 0;n++;// ...return n;
}int main()
{int ret = Count();cout << ret << endl;return 0;
}

我们非常的熟悉,结果为 1

再来看一下下面这段代码👇

//传引用返回
int& Count()
{int n = 0;n++;// ...return n;
}int main()
{int ret = Count();//这里打印的结果可能是1,也可能是随机值cout << ret << endl;return 0;
}

在这里插入图片描述

🚨这里输出的结果有两种可能,一种为 1 ,另一种可能为随机值,原因是:传引用返回返回的是n的别名,但是这里存在的问题是我们返回n的别名也就是访问n这块空间,访问n这块空间就有两个结果:如果这个栈帧没有清除,它的值就是1,如果它的空间被清了,那么它的就会是一个 随机值

看图比较一下两段代码👇
在这里插入图片描述

上面的理解了之后,再看一段代码👇

int& Count()
{int n = 0;n++;// ...return n;
}int main()
{int& ret = Count();//这里打印的结果可能是1,也可能是随机值cout << ret << endl;cout << ret << endl; //被覆盖return 0;
}

在这里插入图片描述
这是为什么呢?

🚨函数调用要先传参,也就是先取值,这个时候还没有建立栈帧,取值之前还没有被覆盖,传参过去之后,建立栈帧,值不会受到影响;那第二次调用,再去取值,这时这个值已经被建立的栈帧覆盖了,所以输出的是随机值

理解了之后,再看一段代码👇

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;
}

在这里插入图片描述
这样的函数不能使用引用返回,是非常不安全的,相当于野引用(野指针)的方式
在这里插入图片描述

🚨注意:如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回

🍿3、传值、传引用效率比较

🥗 传值和传引用的作为参数的性能比较

#include <time.h>
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 < 10000; ++i)TestFunc1(a);size_t end1 = clock();// 以引用作为函数参数size_t begin2 = clock();for (size_t i = 0; i < 10000; ++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;
}

在这里插入图片描述

🥗 传值和传引用的作为返回值的性能比较


#include <time.h>
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;
}

在这里插入图片描述

🍿4、传引用返回修改返回对象

传引用返回还有一个隐藏作用:

假如一个顺序表要进行读和修改数据

struct SeqList
{int* a;int size;int capacity;
};

🚩C的接口设计:

//C的接口设计//读取第i个位置的值
int SLAT(struct SeqList* ps, int i)
{assert(i < ps->size);//...return ps->a[i];
}
//修改第i个位置的值
void SLModify(struct SeqList* ps, int i, int x)
{assert(i < ps->size);//...ps->a[i] = x;
}

🚩CPP的接口设计:

//CPP的接口设计//读 or 修改第i个位置的值
int& SLAT(struct SeqList& ps, int i)
{assert(i < ps.size);//...return ps.a[i];
}int main()
{struct SeqList s;//...SLAT(s, 0) = 10;//修改SLAT(s, 1) = 20;SLAT(s, 2) = 30;cout << SLAT(s, 0) << endl;//打印cout << SLAT(s, 1) << endl;cout << SLAT(s, 2) << endl;return 0;
}

这里相比 C的接口设计来看,非常的香,减少了拷贝🥰

🍿5、总结

🚩传引用传参(任何时候都可以)

1、提高效率
2、输出型参数(形参的修改,影响实参)

🚩传引用返回(出了函数作用域对象还在才可以用)

1、提高效率
2、修改返回对象

🍪五、引用和指针的区别

🚩在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间
🚩在底层实现上实际是有空间的,因为引用是按照指针方式来实现的

int main()
{int a = 0;int* p1 = &a;int& ref = a;return 0;
}

我们来看下引用和指针的汇编代码对比:
在这里插入图片描述
🥰可以看到:引用和指针底层是一样的,可以说是引用就是化了妆的指针

引用和指针的不同点:

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

😍这期内容有一点难理解,希望烙铁们能理解消化,有所收获哦!

总结🥰
以上就是 【C++】引用 的全部内容啦🥳🥳🥳🥳
本文章所在【C++初阶】专栏,感兴趣的烙铁可以订阅本专栏哦🥳🥳🥳
前途很远,也很暗,但是不要怕,不怕的人面前才有路。💕💕💕
小的会继续学习,继续努力带来更好的作品😊😊😊
创作写文不易,还多请各位大佬uu们多多支持哦🥰🥰🥰

请添加图片描述

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

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

相关文章

Flink读取mysql数据库(java)

代码如下: package com.weilanaoli.ruge.vlink.flink;import com.ververica.cdc.connectors.mysql.source.MySqlSource; import com.ververica.cdc.connectors.mysql.table.StartupOptions; import com.ververica.cdc.debezium.JsonDebeziumDeserializationSchema; import org…

Excel修改日期格式,改变日期的筛选方式

我们有两列日期数据&#xff1a; 左边这一列筛选会显示&#xff1a; 右边这一列筛选会显示&#xff1a; 修改格式&#xff0c;将【日期1】改为【日期2】 将【日期1】的格式修改为文本格式即可 修改格式&#xff0c;将【日期2】改为【日期1】 选中日期2&#xff0c;点击【数据…

JDK各版本重要变革

各版本更新详情 JDK8(LTS)--2014/3 语法层面 lambda表达式(重要特色之一) 一种特殊的匿名内部类,语法更加简洁允许把函数作为一个方法的参数,将代码象数据一样传递&#xff0c;即将函数作为方法参数传递基本语法: <函数式接口> <变量名> (参数...) -> { 方法…

LeetCode 热题 100 JavaScript --226. 翻转二叉树

给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 3&#xff1a; 输入&#xff1a;root [] 输出&#xff1a;[] 提示&#xff1a; 树中节点数目范围在 [0, 100] 内 -100 < Node.val < 100 var invertTree function(root…

哈工大计算机网络课程网络安全基本原理详解之:消息完整性与数字签名

哈工大计算机网络课程网络安全基本原理详解之&#xff1a;消息完整性与数字签名 这一小节&#xff0c;我们继续介绍网络完全中的另一个重要内容&#xff0c;就是消息完整性&#xff0c;也为后面的数字签名打下基础。 报文完整性 首先来看一下什么是报文完整性。 报文完整性…

基于springboot+jpa+mysql+html网上中药商城系统

基于springbootjpamysqlhtml网上中药商城系统 一、系统介绍二、功能展示1.主页(客户)2.登陆&#xff08;客户&#xff09;3.注册&#xff08;客户&#xff09;4.购物车(客户)5.我的订单&#xff08;客户&#xff09;6.用户管理&#xff08;管理员&#xff09;7.分类管理&#x…

远程连接身份验证错误,又找不到加密Oracle修正

一、问题描述 远程连接服务器出现了错误&#xff0c;错误信息为&#xff1a;远程连接身份验证错误&#xff0c;又找不到加密Oracle修正。 二、原因分析 出错原因&#xff1a;Windows的CVE-2018-0886 的 CredSSP 更新将CredSSP 身份验证协议默认设置成了“缓解”&#xff0c;…

Informer 论文学习笔记

论文&#xff1a;《Informer: Beyond Efficient Transformer for Long Sequence Time-Series Forecasting》 代码&#xff1a;https://github.com/zhouhaoyi/Informer2020 地址&#xff1a;https://arxiv.org/abs/2012.07436v3 特点&#xff1a; 实现时间与空间复杂度为 O ( …

轻松批量文件改名!一键翻译重命名文件夹/文件,省时高效!」

繁忙的数字时代&#xff0c;我们经常需要处理大量的文件和文件夹。而手动逐个更改文件名不仅费时费力&#xff0c;还容易出错。因此&#xff0c;我们为您带来了一款强大的工具——批量文件改名软件&#xff01;现在&#xff0c;您可以一键翻译重命名文件夹和文件&#xff0c;轻…

在centos7.9安装tomcat8,并配置服务启动脚本,部署jpress应用

目录 一、简述静态网页和动态网页的区别 二、简述 Webl.0 和 Web2.0 的区别 三、 安装Tomcat8&#xff0c;配置服务启动脚本&#xff0c;部署jpress应用 3.1、Tomcat简介 3.2、安装Tomcat 3.2.1、配置环境 3.2.2、安装JDK 3.2.3、安装tomcat8 3.2.4、访问主页&#xff1…

go 如何知道一个对象是分配在栈上还是堆上?

如何判断变量是分配在栈&#xff08;stack&#xff09;上还是堆&#xff08;heap&#xff09;上&#xff1f; Go和C不同&#xff0c;Go局部变量会进行逃逸分析。如果变量离开作用域后没有被引用&#xff0c;则优先分配到栈上&#xff0c;否则分配到堆上。判断语句&#xff1a;…

数据可视化(4)散点图及面积图

1.简单散点图 #散点图 #scatter(x,y) x数据&#xff0c;y数据 x[i for i in range(10)] y[random.randint(1,10) for i in range(10)] plt.scatter(x,y) plt.show()2.散点图分析 #分析广告支出与销售收入相关性 dfcarpd.read_excel(广告支出.xlsx) dfdatapd.read_excel(销售…

1.3 eureka+ribbon,完成服务注册与调用,负载均衡源码追踪

本篇继先前发布的1.2 eureka注册中心&#xff0c;完成服务注册的内容。 目录 环境搭建 采用eurekaribbon的方式&#xff0c;对多个user服务发送请求&#xff0c;并实现负载均衡 负载均衡原理 负载均衡源码追踪 负载均衡策略 如何选择负载均衡策略&#xff1f; 饥饿加载…

抖音seo短视频账号矩阵系统技术开发简述

说明&#xff1a;本开发文档适用于抖音seo源码开发&#xff0c;抖音矩阵系统开发&#xff0c;短视频seo源码开发&#xff0c;短视频矩阵系统源码开发 一、 抖音seo短视频矩阵系统开发包括 抖音seo短视频账号矩阵系统的技术开发主要包括以下几个方面&#xff1a; 1.前端界面设…

使用文心一言等智能工具指数级提升嵌入式/物联网(M5Atom/ESP32)和机器人操作系统(ROS1/ROS2)学习研究和开发效率

以M5AtomS3为例&#xff0c;博客撰写效率提升10倍以上&#xff1a; 0. Linux环境Arduino IDE中配置ATOM S3_zhangrelay的博客-CSDN博客 1. M5ATOMS3基础01按键_zhangrelay的博客-CSDN博客 2. M5ATOMS3基础02传感器MPU6886_zhangrelay的博客-CSDN博客 3. M5ATOMS3基础03给RO…

【MySQL】表的增删查改

文章目录 一、创建表create二、查看表desc三、修改表3.1 修改表名alter3.2 在表中插入数据insert3.3 在表中新增字段alter3.4 修改指定列的属性alter3.5 移除表中的一列alter3.6 修改表中某一列的列名alter 四、删除表drop 一、创建表create mysql> create table if not ex…

Neo4j文档阅读笔记-Installation and Launch Guide

安装&#xff08;Windows&#xff09; ①找到下载好的Neo4j Desktop文件&#xff0c;然后双击进行安装&#xff1b; ②安装Neo4j Desktop根据下一步进行安装。 启动 ①激活 打开Neo4j Desktop应用程序后&#xff0c;将激活码输入到“Activation Key”窗口中。 ②创建数据库…

AMEYA:尼得科科宝滑动型DIP开关CVS产品参数及价格​

日本电产尼得科科宝滑动型DIP开关CVS采用紧凑设计&#xff0c;3bit产品&#xff0c;旋钮把手高度为0.2mm&#xff0c;操作性良好端子为1mm间距&#xff0c;电路数丰富(2,3,4,8)端接样式为鸥翼式&#xff0c;J形引线使用树脂材料符合UL认证94V-0 符合RoHS规范。 日本电产尼得科科…

Vol的学习

首先学习基础用法 1.查看系统基本信息 vol.py -f 路径 imageinfo 2.查看进程命令行 vol.py -f 路径 --profile系统版本 cmdline vol.py -f 路径 --profile版本 cmdscan 3.查看进程信息 vol.py -f 路径 --profile系统 pslist 通过树的方式返回 vol.py -f 路径 --profile系统…

postgis mvt矢量切片 django drf mapboxgl

postgis mvt矢量切片 django drf mapboxgl 0.前提 [1] 静态的矢量切片可以采用 tippecanoe 生成&#xff0c;nginx代理&#xff0c;这种数据是不更新的&#xff1b; [2] 动态的矢量切片&#xff0c;一般采用postgis生成。基本上矢量切片80%的厂商都采用postgis&#xff0c;确实…