C++的引用

目录

引用

常引用

指针与引用的关系

小拓展

引用的价值

做形参

 传值、传引用的效率比较

做返回值

函数传值返回

函数传引用返回(错误示范)

野引用(错误示范)

引用的正常应用

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

引用和指针的区别

语法上

底层(汇编)上


引用

基本概念:引用是给已存在变量取了一个别名

李逵,在家称为“铁牛”,江湖上人称“黑旋风”

特点:和原变量共用同一块内存空间(地址相同,且牵一发而动全身),同一变量可以有多个别名

格式:类型& 引用变量名(对象名) = 引用实体

注意事项:

1、C++中的&依然可以表示取地址运算符和按位与运算符

2、引用类型必须和引用实体是同种类型的

3、当引用实体和它的别名属于不同的域时,别名和引用实体的名字可以相同但是不建议这样做

#include <iostream>
using namespace std;int main()
{int a = 10;int& b = a;cout << &a << endl;cout << &b << endl << endl;a++;b++;cout << a << endl;cout << b << endl << endl;return 0;
}

4、 引用必须在开始就初始化(说明自己是谁的别名)

int a = 0;//wrong
int& b;
b = a;  //right
int &b = a;

5、引用定义后,不能改变指向(别名与绑定后无法修改,对别名的任何操作就是对实体的操作)

int a = 0;
int& b = a;
int c = 1;
b = c;    // 这里不是让b指向c,而是将1赋值给了引用实体a

常引用

定义:使用 const 修饰的引用

特点:被const修饰的引用不能被修改

void printValue(const int& value) {// 这里无法通过value来修改原始值,如果输入value += 10;编译器就会报错cout << "Value: " << value << endl;
}int main() {int num = 10;// 使用常量引⽤来传递numprintValue(num);return 0;
}

指针与引用的关系

基本概念:指针和引用是类似的,指针找到并修改你,别名直接修改你(二者存在依赖关系)

        虽然C++的引用可以对使用指针后比较复杂的场景进行一些替换,让代码更简单易懂,但是因

为引用在定义后不能改变指向(删除双向链表的一个结点时, 需要用改变前去指针和后继指针,

用不能做到这一点),而指针可以,所以在C++中引用并不能替代指针:

struct Node
{struct Node* next;struct Node* prev;int val;
};

      之前我们在写单链表时,用二级指针pphead接收头指针的地址,而当我们有了引用的概念后,

可以以一种更好理解的方式实现下列单链表的(伪)代码:

#include <stdio.h> 
struct Node
{struct Node* next;struct Node* prev;int val;
};//二级指针版本
void PushBack(struct Node** pphead,int x)
{*pphead = newnode;
}//引用版本
void PushBack(struct Node*& phead, int x)//为指针取别名,plist指针的别名是phead
{phead = newnode;
}int main()
{struct Node* plist = NULL;PushBack(plist,0);return 0;
}

        我们将原来的二级指针pphead换为struct Node*& phead,phead是plist的别名,对phead的操

作就是对plist的操作,这使得代码看起来更加简单。单链表使用二级指针的原因就是为了能够向

头指针中存入新节点的值(不用的话对于值得修改不能被带出尾插函数),这里直接通过引用就实

现了这一目的所以不需要再去考虑传值调用的问题

关于单链表的内容可以查看:单链表的实现(全注释promax版)

小拓展

#include <stdio.h> typedef struct Node
{struct Node* next;struct Node* prev;int val;
}LNode,*PNode;//引用版本
void PushBack(PNode& phead, int x)//为指针取别名,plist指针的别名是phead
{phead = newnode;
}int main()
{PNode plist = NULL;PushBack(plist,0);return 0;
}
  • LNode: 是 struct Node 的别名(当你使用 LNode 时,就相当于使用了 struct Node)

  • PNode: 是结构体类型的指针struct Node*的别名

C++百分之八十的场景都在使用引用,剩下的才会用指针

引用的价值

做形参

        原来我们在交换两个变量的时候,向Swap函数中传递的是地址,形参是实参的拷贝。实参必

须传递的是地址,否则交换后的结果无法传递回去,有了别名后就不需要传递地址了,形参可以直

接写成实参的别名即可,对于别名的修改就相当于原来的实参的修改:

#include <iostream>
using namespace std;//原版本,传递地址
void Swap(int* left, int* right)
{int tmp;tmp = *left;*left = *right;*right = tmp;
}int main()
{int i = 10;int j = 20;Swap(&i, &j);cout << i << endl;cout << j << endl;return 0;
}//现版本,引用
void Swap(int& left, int& right)
{int tmp = left;left = right;right = tmp;
}int main()
{int i = 10;int j = 20;Swap(i, j);cout << i << endl;cout << j << endl;return 0;
}

从语法上来讲,left和right是i和j的拷贝,int& left和int& right是i和j的别名

🤡从底层上来讲,这里先不讲,我不会🤡 

 传值、传引用的效率比较

         以值作为参数和返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身
直接返回,而是传递实参或者返回变量的一份临时拷贝,因此用值作为参数或者返回值类型,效
率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低:
#include <stdio.h>
#include <time.h>
#include <iostream>
using namespace std;struct A { int a[10000]; };
void TestFunc1(A a) {}
void TestFunc2(A& a) {}int main()
{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;return 0;
}

结论:引用做形参时是输出型参数,且当对象较大时,减少拷贝提高效率

这些效果指针也可以实现,但是没引用方便 

输出型参数:传递给函数的参数数据,函数可以使用且能修改这些数据

输入型参数:传递给函数的参数数据,函数可以使用但不能修改这些数据

做返回值

函数传值返回

        main函数开辟了一块栈帧,接着又调用了func函数,再开辟一块帧栈,然后func返回a的

值并结束,然后main函数又想接收func函数的返回值a,但是返回值a已经在func函数结束后被销

毁了,a空间中所存放的值已经不再被保证有效,所以此时ret得到是随机(也有可能是原来的

值,这相当于你鸡蛋碎在大街上了,你记着位置回去找,还能在地上找到蛋黄。如果等久一点,再

回去找,那就不一定能找到啥了,但是在大多数情况下,编译器在栈帧销毁时会将返回存储在

寄存器中(对象比较小时)或者其他适当位(除了函数返回值之外,编译器还可以选择使用寄存

器来保存一些重要的局部变量或者临时变量,以减少对内存的读写作)最后器中存放的原来返回值的值会交给ret

        在函数结束后硬要把局部变量搞成一个随机值是一件没有意义的事情。只能说函数结束后局部变量的值【不再保证有效】

        销毁是需要花点力气的,函数销毁后,原来函数的那一片空间变成空闲区域,随时会再次被使用。大部分情况下,你还能输出,是因为没人用到了那片空闲区域!然而机器也懒得去销毁,重置内存也得费电。

函数传引用返回(错误示范)

        func函数的返回值是一个int&类型的引用,即返回值是局部变量a的引用(别名),而当函数

返回该引用时,a的值已经不再保证有效了(随机值或者原值)

结论:函数传值返回的是返回变量的拷贝,函数传引用返回的是返回变量的引用(别名)

野引用(错误示范)

        func函数返回的是a的引用(别名),那么ret就是a的引用的引用,而a的那片空间在func函

数销毁时已经不再保证有效了,所以ret就是一个野引用(在程序运行过程中无法保证该内存空间

仍然有效或包含原始值(因为它可能已被其他数据覆盖),因此称之为野引用)

结论:返回变量(局部变量)出了函数作用域就被销毁时,不能用引用返回(薛定谔的🐱)

引用的正常应用

全局变量、静态变量、堆上分配对象等内容可以用引用返回:

  • 全局变量:全局变量在程序运行期间始终存在,因此可以安全地通过引用返回
  • 静态变量:静态变量也类似于全局常驻内存,在程序整个执行周期中都存在
  • 堆上分配对象:如果一个对象是通过 new 运算符在堆上动态分配内存创建的,则其生命周期由 new 和 delete 控制,并不受限于函数作用域
①int a = 10;
int& func()
{②static int a = 0;return a;
}int main()
{int& ret = func();cout << ret << endl;return 0;
}

堆上分配对象的例子我不会🤡

这是C语言(参杂了一点C++)实现顺序表的简化代码:

#include <iostream>
#include <assert.h>
#include <stdio.h>using namespace std;struct SeqList
{int* a;int size;int capacity;
};//初始化
void SLInit(SeqList& sl)//利用引用了
{sl.a = (int*)malloc(sizeof(int) * 4);//... sl.size = 0;sl.capacity = 4;
}void SLPushBack(SeqList& sl, int x)
{//...(扩容)sl.a[sl.size++] = x;
}//修改
void SLModity(SeqList& sl, int pos, int x)
{assert(pos >= 0);assert(pos <= sl.size);sl.a[pos] = x;
}//获取pos位置的值
int SLGet(SeqList& sl, int pos)
{assert(pos >= 0);assert(pos <= sl.size);return sl.a[pos];
}int main()
{SeqList s;//C++将stryct SeqList变为了类,所以可以直接用SeqListSLInit(s);SLPushBack(s, 1);SLPushBack(s, 2);SLPushBack(s, 3);SLPushBack(s, 4);for (int i = 0; i < s.size; i++){cout << SLGet(s,i) << " ";}cout << endl;for (int i = 0; i < s.size; i++){int val = SLGet(s,i);if (val % 2 == 0){SLModity(s, i, val * 2);}}cout << endl;for (int i = 0; i < s.size; i++){cout << SLGet(s, i) << " ";}cout << endl;return 0;
}

这是完全使用C++语法写出的顺序表代码:

#include <iostream>
#include <assert.h>
#include <stdio.h>using namespace std;struct SeqList
{//成员变量int* a;int size;int capacity;//成员函数//C++的结构体(类)除了可以定义变量还可以定义函数void Init()//可以不写前缀{a = (int*)malloc(sizeof(int) * 4);//... size = 0;capacity = 4;}void PushBack(int x){//...(扩容)a[size++] = x;}int& Get(int pos)//加上一个引用就可以代替原来的SLModify和SLGet两个函数的作用{assert(pos >= 0);assert(pos <= size);return a[pos];//由于数组中pos位置的空间是由malloc开辟的,如果不主动释放它就一直都在,所以即使函数销毁,该空间也不会销毁,该空间中的值也会被保留 }
};int main()
{SeqList s;//C++将stryct SeqList变为了类,所以可以直接用SeqLists.Init();s.PushBack(1);s.PushBack(2);s.PushBack(3);s.PushBack(4);for (int i = 0; i < s.size; i++){cout << s.Get(i) << " ";}cout << endl;//将满足条件的数组pos位置的值进行修改for (int i = 0; i < s.size; i++){if (s.Get(i) % 2 == 0){s.Get(i) *= 2;}}cout << endl;for (int i = 0; i < s.size; i++){cout << SLGet(s, i) << " ";}cout << endl;return 0;
}
  • C语言:数据与函数分离,想要访问数据就要将数据作为参数传递给函数
  • C++:数据和函数不分离,都处于一个类中(实际上也传了编译器做的但是现在没学到)可以直接用(不需要再传一个结构体类型的指针将存储在结构体中的数据传递)

        在这段代码中我们不仅仅需要关注的是将原本放在外部的尾插、初始化等函数放在了结构体

,还需要注意在这里我们用Get一个函数就可以实现原来SLGet和SLModify两个函数的作用(读

写pos位置的数据)这是因为C++规定临时变量具有常性(特指存储在寄存器中的变量,虽然

pos位置的值除非主动释放否则不会销毁,但是可以将该值拷贝一份放入寄存器中作为函数的返回

值),临时变量默认被const修饰无法修改,如果在试图修改pos位置的值时就会出现不可修改的

左值的报错,但是即使C++没有做出这一规定,我们仍然不能做到对数组pos位置的值进行修改,

因为临时变量只是原来该位置数组的一个拷贝,对拷贝内容的修改无法影响到原来位置的数值,这

两种情况都只能对pos位置的值进行读取而不能进行修改,而当我们加上了一个引用,就可以实现

对pos位置的值的修改,此时Get函数返回的就不是一个临时变量(函数返回引用不会产生临时变

量),而是a[pos]的别名,至于谁的别名作者不知道就不做解释,只需要此时我们既可以读取到

pos位置的值也可以对该值进行修改 

        完成对数组pos位置的值的修改还有一个前提就是数组pos位置存放值的空间在函数结束后仍然可以保证值得有效,否则就会出现传引用返回(错误示范)中出现得值不保证有效的问题,对于这一点由于数组空间是由mallo开辟的,除非主动销毁否则不会释放,即使函数结束数组pos位置存放值得空间仍然存在,值仍然保证有效,所以可以放心引用该实体对象

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

#include <iostream>
#include <time.h>
using namespace std;struct A { int a[10000]; };
A a;
// 值返回
A TestFunc1() { return a; }
// 引用返回
A& TestFunc2() { return a; }int main()
{// 以值作为函数的返回值类型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()
{int a = 10;int& ra = a;//引用语法上不开空间ra = 20;int* pa = &a;//指针语法上开空间*pa = 20;return 0;
}

②、引用必须初始化,指针可以初始胡也可以不初始化(所以指针更容易出现野的情况)

③、引用不可以改变指向,指针可以改变指向

④、引用相对安全,没有空引用,但是有空指针,容易出现野指针,但是不容易出现野引用

⑤、在sizeof中含义不同引用结果为引用类型的大小,但指针始终是地址空间所占字节个数

⑥、引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小

⑦、有多级指针,但是没有多级引用

⑧、访问实体方式不同,指针需要显式解引用,引用编译器自己处理

⑨、引用比指针使用起来相对更安全

底层(汇编)上

对于①中的引用,在底层(汇编)上的情况是这样的:

        “003F2026 lea eax,[a]”:取a的地址放在eax寄存器,故引用在底层层面需要开空间

结论:

  • 引用底层是用指针实现的
  • 语法含义和底层实现是背离的(鱼香肉丝没有🐟)

汇编层面上,没有引用,都是指针,引用编译后也转换成指针了

~over~

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

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

相关文章

spring-boot-starter-parent和spring-boot-dependencies介绍

springboot项目的pom文件中&#xff0c;我们经常看见这样(下图)两种springboot的版本依赖管理方式&#xff1b;图片中的这两种依赖声明方式任意用其中一种都可以。文章后面会简单阐述一下区别和使用场景。 事例中完整的pom文件 <?xml version"1.0" encoding&quo…

阿尔卡特Adixen ADP/ADS 系列 2 干泵使用说明

阿尔卡特Adixen ADP/ADS 系列 2 干泵使用说明

HTML教程(3)——常用标签(1)

一、图片标签 1.场景&#xff1a;在网页中显示图片 2.基本写法&#xff1a; <img src""> 3.特点&#xff1a;单标签&#xff0c;img标签需要展示对应的效果&#xff0c;需要借助其属性进行设置 4常用属性&#xff1a; src&#xff1a;其属性值为目标图片…

【框架】Spring 框架重点解析

Spring 框架重点解析 1. Spring 框架中的单例 bean 是线程安全的吗&#xff1f; 不是线程安全的 Spring 框架中有一个 Scope 注解&#xff0c;默认的值是 singleton&#xff0c;即单例的&#xff1b;因为一般在 Spring 的 bean 对象都是无状态的&#xff08;在生命周期中不被…

解决Mybatis报Type interface *.*Mapper is not known to the MapperRegis

解决Mybatis报Type interface *.*Mapper is not known to the MapperRegis 问题发现问题解决方法一&#xff1a;检查Mapper文件的namespace路径是否正确方法二&#xff1a;使用其他方法是否正确 问题发现 在学习MyBatis框架的时候&#xff0c;不使用 XML 构建 SqlSessionFacto…

Project_Euler-44 题解

Project_Euler-44 题解 题目 思路 题目给出了一个性质&#xff0c;让我在对应性质的数据中找出目标值&#xff0c;这种问题首先想到的就是枚举。 我们可以枚举 P k P_k Pk​ &#xff0c;对于每一个 P k P_k Pk​ &#xff0c;我们再枚举 P j P_j Pj​&#xff0c; P j P_…

【ue5】滑铲系统蓝图笔记

大致逻辑如下&#xff1a; 一、导入动画 滑铲蹲待机蹲行走 导入到文件夹中 可以右键设置颜色&#xff0c;便于区分。 二、调整动画 1.启动根运动 启动根运动后&#xff0c;人物才可以位移&#xff0c;不然只能在原地。 打开动画序列&#xff0c;勾选启用根运动Enabled…

用node或者vscode开启一个简单的本地server服务器,加载html网页

使用Live Server 想要加载本地html页面可以快速能让它在你本地浏览器中打开&#xff0c;可以有好多种方式&#xff0c;如果你有使用vscode&#xff0c;可以安装一个插件&#xff1a;Live Server&#xff0c;然后直接在vscode中直接右键就可以开启这个服务&#xff1a; 安装好之…

C++基于多设计模式下的同步异步日志系统day2

&#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;C基于多设计模式下的同步&异步日志系统 &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 主要内容实现了日志代码设计的实…

select,poll和epoll有什么区别

它们都是NIO中多路复用的三种实现机制&#xff0c;是由linux操作系统提供的。 用户空间和内核空间&#xff1a;操作系统为了保证系统安全&#xff0c;将内核分为两个部分&#xff0c;一个是用户空间&#xff0c;一个是内核空间。用户空间不能直接访问底层的硬件设备&#xff0…

IT廉连看——Uniapp——配置文件pages

IT廉连看——Uniapp——配置文件pages [IT廉连看] 本堂课主要为大家介绍pages.json这个配置文件 一、打开官网查看pages.json可以配置哪些属性。 下面边写边讲解 新建一个home页面理解一下这句话。 以下一些页面的通用配置 通用设置里我们可以对导航栏和状态栏进行一些设…

Android修行手册-集成Python开发环境

Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总游戏脚本-辅助自动化Android控件全解手册再战Android系列Scratch编程案例软考全系列Unity3D学习专栏蓝桥系列ChatGPT和AIGC &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分…

Apache Flink连载(三十五):Flink基于Kubernetes部署(5)-Kubernetes 集群搭建-1

🏡 个人主页:IT贫道-CSDN博客 🚩 私聊博主:私聊博主加WX好友,获取更多资料哦~ 🔔 博主个人B栈地址:豹哥教你学编程的个人空间-豹哥教你学编程个人主页-哔哩哔哩视频 目录 ​编辑

Python爬虫——Urllib库-2

编解码 问题引入 例如&#xff1a; https://www.baidu.com/s?wd章若楠 https://www.baidu.com/s?wd%E7%AB%A0%E8%8B%A5%E6%A5%A0 第二部分的一串乱码就是章若楠 如果这里是写的章若楠就会 产生这样的错误 所以我们就可以使用get请求方式的quote方法了 get请求方式的q…

laravel ApiResponse接口统一响应封装

一&#xff0c;新增接口返回码配置文件 在config中新增配置文件apicode.php <?phpreturn [ apicodes>[/*** Message("OK")* 对成功的 GET、PUT、PATCH 或 DELETE 操作进行响应。也可以被用在不创建新资源的 POST 操作上*/HTTP_OK > 200,/*** Message(&qu…

使用el-form之表单校验自动定位到报错位置问题,,提升用户体验

需求描述 由于需要填写的表单项太多&#xff0c;提交的时候校验不通过&#xff0c; 如果没填写的表单项在最上面&#xff0c;用户看不到不知道发生了啥&#xff0c; 所以需要将页面滚动定位到第一个报错的表单项位置&#xff0c;提升用户体验实现步骤 1. 给form表单添加ref …

数据中心GPU集群高性能组网技术分析

数据中心GPU集群组网技术是指将多个GPU设备连接在一起&#xff0c;形成一个高性能计算的集群系统。通过集群组网技术&#xff0c;可以实现多个GPU设备之间的协同计算&#xff0c;提供更大规模的计算能力&#xff0c;适用于需要大规模并行计算的应用场景。 常用的组网技术&…

1209. 带分数 刷题笔记

思路 暴力匹配 读入目标数 n 看n是否与ab/c相等 因为c里面的除法是整除 我们将 nab/c 转换为 c*na*cb 那么如何获得a,b&#xff0c;c 依题意 a&#xff0c;b&#xff0c;c三个数由1-9九个数字组成 且每个数字只能出现一次 由此 我们可以搜出123456789的全部排列方式…

我做的app上架应用市场一天,快破400下载量,0差评

上集说到&#xff0c;我做了一个叫QB音乐的安卓app&#xff0c;经过一段时间的自我使用与测试终于算发布了。我昨天顺便把它上架了奇妙应用市场&#xff0c;截止目前3月1号过去了一天&#xff0c;下载量快到400&#xff0c;0差评。看来还是能正常使用的。 一、为什么做这个ap…

CleanMyMac X2024免费Mac电脑清理和优化工具

CleanMyMac X是一款专业的 Mac 清理和优化工具&#xff0c;它具备一系列强大的功能&#xff0c;可以帮助用户轻松管理和维护他们的 Mac 电脑。以下是一些关于 CleanMyMac X 的主要功能和特点&#xff1a; 智能清理&#xff1a;CleanMyMac X 能够智能识别并清理 Mac 上的无用文件…