【C++】踏上C++学习之旅(三):“我“ 与 “引用“ 的浪漫邂逅

在这里插入图片描述

文章目录

  • 前言
  • 1. "引用"的概念
    • 1.1 "引用"的语法
  • 2. "引用"的特性
  • 3. "引用"的使用场景
    • 3.1 "引用"做参数
    • 3. 2 "引用"做返回值
      • 3.2.1 "引用"做返回值时需要注意的点
  • 4. 常引用
  • 5. "引用"在底层的实现
  • 6. "引用"和"指针"的不同点(面试常考)

前言

本文会着重的讲解"引用"的各项用法以及使用时需要注意的一些规则,另外这部分是面试官比较喜欢与指针一起作为问题来提问我们的,所以我在文章的末尾,给大家也准备好了答案!

话不多说,让我们从现在开始与"引用"进行一场浪漫的邂逅的吧!!!

哈哈哈

1. "引用"的概念

引用不是一种新的数据类型,而是在C++中给已存在变量起一个别名。编译器不会给引用变量开辟内存空间,它和它引用的变量共用同一块空间

举个例子,在《水浒传》中,李逵在家中被宋江叫做"铁牛",在江湖上人称"黑旋风"。那么,我们说"铁牛"和"黑旋风"都是在说李逵这个人,所以说"铁牛"和"黑旋风"就是别名。 在代码的世界里,相信大家已经对别名有所使用,就是typedef这个关键字通常被有做对结构体起别名。在C++中,引用是对变量起别名!

讲解完引用是什么之后,那我们就来看看,引用是如何在代码中表示的。

1.1 "引用"的语法

数据类型& 引用变量名(对象名) = 引用实体;

下面我来写一段代码,带着大家感受一下"引用"的魅力:

#include<iostream>
using namespace std;int main()
{int a = 10;int& ra = a;  //类型& 引用变量名 = 引用实体;cout << "a = " << a << endl;cout << "ra = "  << ra << endl;return 0;
}

引用演示

大家在注意引用的定义时,引用变量与引用实体必须得是相同的数据类型。否则,程序会报错的!

2. "引用"的特性

1. 引用在定义时必须初始化;
2. 一个变量可以有多个引用;
3. 一旦有个实体被引用,那么这个引用变量就不能再引用其它实体。

int main()
{int a = 10;//int&ra; //该条语句在编译时会报错int& ra = a;int& rra = a;printf("%p  %p  %p",&a,&ra,&rra);
}

错误示范

tupian
以上的代码案例就能很好的体现出引用的特性!

哈哈哈


3. "引用"的使用场景

光讲引用的定义和特性,相信这一定不能让大家认识到"引用"有多强大,有多舒服。那么,接下来,我结合C语言的代码场景来对比在C++下,引用的强大之处。

3.1 "引用"做参数

"引用"做参数,主要是针对输出型参数。

什么是输出型参数?
输出型参数:通过形参的改变能影响实参的改变。 这类的形参,我们就把它称为输出型参数。当然与之对应的还有输入型参数,这个参数想必大家肯定用的不少,==输入型参数:形参的改变不会影响到实参。==这类的形参,我们称它为输入型参数。

好处:
1. 针对输出型参数
2. 减少拷贝,提高效率(特别是大对象/深拷贝对象)

为了让大家更好的感受到"引用"的"爽",我将用多个代码在C语言下和C++下做比较:

//场景一:交换两个数(用C语言)
void Swap(int* x, int* y)
{int tmp = *x;*x = *y;*y = tmp;
}//交换两个数(用C++的"引用")
void Swap(int& x, int& y)
{int tmp = x;x = y;y = tmp;
}
struct Stack{int* a;int top;int capacity;
};//场景二:给栈初始化(用C语言)
void StackInit(struct Stack* pst,int STDefault = 4)
{pst->a = (int*)malloc(sizeof(int)*STDefault);if(pst->a == NULL){perror("malloc fail");exit(1);}pst->top = 0;pst->capacity = STDefault;
}//给栈初始化(用C++的"引用")
void StackInit(struct Stack& st,int STDefault = 4)
{st.a = (int*)malloc(sizeof(int)*STDefault);if(st.a == NULL){perror("malloc fail");exit(1);}st.top = 0;st.capacity = STDefault;
}int main()
{struct Stack st;//用C语言的版本StackInit(&st);//用C++的版本StackInit(st);
}

大家可以仔细对比一下,是C语言指针的写法好用,还是C++的"引用"好用。

StackInit函数struct Stack& st这个形参就相当于输出型参数,当然这个想象在Swap函数更加明显。

好了,"引用"作为形参的第一个好处我理解了,那第二个好处又怎么解读呢?

我说引用作为参数,可以减少拷贝,提高效率 ,这个点就体现在函数栈帧的创建和销毁中。如果对这方面不了解的读者,可以看一下往期我写的文章:【C语言】函数栈帧的创建和销毁(启航——迎接崭新的自己)。

回到主线上,我们在调用一个函数时,会在栈区给函数的调用开辟一块空间,这块空间就是函数的栈帧,编译器开会就会往栈中压入以西寄存器之类的东西。重点来了,随后,它就会把我们的形参从右往左依次压入栈中,在这个过程中是通过寄存器将形参先拷贝下来,而这段拷贝是要花时间的。 而我们使用"引用"的话,就可以掠过拷贝的过程,这将这个变量给放到栈中,减少了拷贝的花销。

大家可以拷贝一下程序,在你自己的电脑检测一下:

#include<iostream>
#include<time.h>
using namespace std;//减少拷贝,提高效率
struct test //创建一个大对象
{int a[10000];
};test a;
void Func1(test a) {};
void Func2(test& a) {};int main()
{int begin1 = clock();for (int i = 1; i <= 10000; i++){Func1(a);}int end1 = clock();int begin2 = clock();for (int i = 1; i <= 10000; i++){Func2(a);}int end2 = clock();cout << "time void Func1(test a) : " << end1 - begin1 << endl;cout << "time void Func2(test& a) : " << end2 - begin2 << endl;return 0;
}

结果
可以看到,引用确实是提高了程序的效率!

当然,引用不仅在参数中能大放异彩,它在做函数返回值时也同样优秀。

3. 2 "引用"做返回值

好处:
1. 减少拷贝,提高效率(特别是大对象/深拷贝对象)
2. 修改返回值 + 获取返回值

请大家看下面的代码:

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

结果

3.2.1 "引用"做返回值时需要注意的点

请大家想看看下面这个代码的结果:

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

引用做返回值
可以看到引用作为返回值时,及其容易出错,那错误的原因就是"非法访问"。

有的读者此时就会说,程序也没有崩溃,何来的"非法访问"一说?
有时候并不是编译器不报错就不代表你这个代码没有问题,就像数组越界一样。

🍉那我们该怎么理解"非法访问"这一说呢?
这就又要牵扯到函数栈帧的知识了。函数栈帧在被销毁时,编译器做了一个这样的策略,它会将这个返回值用一个寄存器给保存起来。如果我们用"引用"的话,就相当于直接拿着这个返回值的地址了,所以函数栈帧销毁时,会把这块返回值空间的使用权归还给操作系统,此时我们还要用的话,就相当于"非法访问"了。

那有眼尖的读者就会看到,就算是这样,结果也是没有问题的啊。
这是因为编译器在函数栈帧销毁时的处理方式不同:

两种处理方式:

  1. 函数栈帧销毁时,编译器不清空栈帧里面的内容
  2. 函数栈帧销毁时,编译器会清空栈帧里面的内容

显然,我们的编译器是选择前者的方案。

可以看看一下这个代码,让你感觉到用"引用"作为返回值是否非法访问了:

#include<stdlib.h>
#include<iostream>
using namespace std;int& count(int x)
{int n = x;n++;//...return n;
}int main()
{int& ret = count(10);cout << ret << endl;printf("ssssssssssssssssssssssssss\n");rand();cout << ret << endl;return 0;
}

jieguo
结果分析:可以看到第二次打印变量ret的值时,发现是一个随机值,这也就是说明了我i们正在非法的访问者我们的内存。

总结:所以我们在用"引用"作为返回值时,一定不要用局部变量作为函数的返回值,或者和说不要使用在栈区上创建的变量,可以使用静态区或者堆区上的变量!

4. 常引用

“引用"还有一种场景,那就是"常引用”。

常引用通常搭配着一个关键字const使用。

int main()
{int& ret1 = func1();  // 权限放大,编译器会报错const int& ret1 = func1(); // 权限平移int ret1 = func1();  // 拷贝int& ret2 = func2();		// 权限平移const int& rret2 = func2();  // 权限缩小return 0;
}

以上的写法都是被允许的。可以看到常引用的权限是小于引用的权限的。

🍉总结:在使用常引用时,我们要注意权限之间的问题,只能进行权限的平移或者权限的缩小。权限的大小关系:"引用"权限 > "常引用"权限。

5. "引用"在底层的实现

大家可以看一下下面代码的反汇编代码:

int main()
{int a = 10;int& ra = a;ra = 20;int* pa = &a;*pa = 20;return 0;
}

以下是对应代码的反汇编代码:
反汇编代码
可以看到的是,"引用"的底层也是用指针来实现的!

重点说明:在底层实现上"引用"的确是消耗了空间,但是在语法概念上,我们是认为"引用"是不会单独开辟空间的。我们会通常使用后者的说法。

6. "引用"和"指针"的不同点(面试常考)

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

以上就是本文的全部内容了,如果觉得本文对你有帮助的话,麻烦给偶点个赞吧!!!

哈哈哈

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

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

相关文章

自动化数据处理:使用Selenium与Excel打造的数据爬取管道

随着互联网信息爆炸式增长&#xff0c;获取有效数据成为决策者的重要任务。人工爬取数据不仅耗时且效率低下&#xff0c;因此自动化数据处理成为一种高效解决方案。本文将介绍如何使用Selenium与Excel实现数据爬取与处理&#xff0c;结合代理IP技术构建一个可稳定运行的数据爬取…

RocketMQ快速开始

前置推荐阅读&#xff1a;RocketMQ简介-CSDN博客 本地部署 RocketMQ 这一节介绍如何快速部署一个单节点单副本 RocketMQ 服务&#xff0c;并完成简单的消息收发。 系统要求 64位操作系统&#xff0c;推荐 Linux/Unix/macOS64位 JDK 1.8 1.下载安装Apache RocketMQ​ RocketMQ…

aws 把vpc残留删除干净

最近忘了把vpc 删干净导致又被收了冤大头钱 在删除vpc 的收发现又eni在使用&#xff0c;但是忘了是哪个资源在占用 先用命令行把占用的资源找出来停掉 使用 AWS 命令行界面&#xff08;CLI&#xff09;来查看 VPC 的使用情况 列出子网&#xff1a; aws ec2 describe-subnets …

抖音列表页采集-前言

准备工作&#xff1a; 1.关于selenium介绍&#xff1a; python自动化入门的话&#xff0c;selenium绝对是最方便的选择&#xff0c;基本逻辑即为&#xff1a;程序模拟人的行为操作浏览器&#xff0c;这样的操作需要借用浏览器驱动&#xff0c;我选用的是chrome浏览器&#xff…

浮动练习(3)

##每台电脑分辨率不同&#xff0c;数值高度宽度需要自己调&#xff0c;仅供参考 <!DOCTYPE html> <html> <head> <meta charset"UTF-8"> <title></title> <style> div{ …

港大和字节提出长视频生成模型Loong,可生成具有一致外观、大运动动态和自然场景过渡的分钟级长视频。

HKU, ByteDance&#xff5c;⭐️ 港大和字节联合提出长视频生成模型Loong&#xff0c;该模型可以生成外观一致、运动动态大、场景过渡自然的分钟级长视频。选择以统一的顺序对文本标记和视频标记进行建模&#xff0c;并使用渐进式短到长训练方案和损失重新加权来克服长视频训练…

MATLAB(Octave)混电动力能耗评估

&#x1f3af;要点 处理电动和混动汽车能耗的后向和前向算法模型(simulink)&#xff0c;以及图形函数、后处理函数等实现。构建储能元数据信息&#xff1a;电池标称特性、电池标识符等以及静止、恒定电流和恒定电压等特征阶段。使用电流脉冲或要识别的等效电路模型类型配置阻抗…

多功能纤维上线,大脑肠道 “无线畅聊” 不是梦

大家好&#xff01;今天来了解一篇多功能微电子纤维研究——《Multifunctional microelectronic fibers enable wireless modulation of gut and brain neural circuits》发表于《Nature Biotechnology》。我们都知道大脑和内脏器官的沟通对生存至关重要&#xff0c;可一直以来…

为您的 WordPress 网站打造完美广告布局 A5广告单元格插件

一个为 WordPress 网站量身定制的强大工具,它将彻底改变您展示广告的方式 灵活多变的布局设计 A5 广告单元格插件的核心优势在于其无与伦比的灵活性。无论您是想要创建整齐的网格布局,还是希望打造独特的不规则设计,这款插件都能满足您的需求。 自定义网格数量&#xff1a;从 2…

生命科学的前沿挑战与未来机遇

生命科学的前沿挑战与未来机遇 一、引言 21世纪被誉为生命科学的世纪&#xff0c;生命科学的迅猛发展为人类的健康、环境和社会经济带来了巨大的变革。从基因编辑技术的突破&#xff0c;到合成生物学的兴起&#xff0c;再到生物医药的快速进步&#xff0c;生命科学的前沿挑战…

如何使用 Browserless 抓取动态网站?

什么是动态网页&#xff1f; 动态网页是指其内容并非完全直接嵌入在静态HTML中&#xff0c;而是通过服务器端或客户端渲染生成的网页。 它可以根据用户的操作实时显示数据&#xff0c;例如在用户点击按钮或向下滚动页面时加载更多内容&#xff08;如无限滚动&#xff09;。这…

DolphinDB 2024 年度峰会回顾之分论坛:权益类数字基建与技术创新

在这个数字化时代&#xff0c;金融科技正以前所未有的速度发展&#xff0c;而权益类数字基建作为这一进程的核心支撑&#xff0c;正不断推动着金融领域的创新与变革。 DolphinDB 2024 年度峰会的分论坛 A 聚焦《权益类数字基建与技术创新》这一核心议题&#xff0c;邀请到了业…

携手并进,智驭教育!和鲸科技与智谱 AI 签署“101 数智领航计划”战略合作协议

近日&#xff0c;上海和今信息科技有限公司&#xff08;以下简称“和鲸科技”&#xff09;与北京智谱华章科技有限公司&#xff08;以下简称“智谱 AI”&#xff09;签署“101 数智领航计划”战略合作协议。双方将携手营造智能化学科教育与科研环境&#xff0c;提供多种大模型工…

HTTP协议讲解

前瞻&#xff1a; 认识URL 1.ipport 2.平时上网&#xff0c;就是进程间通信 3.上网行为&#xff0c;1.获取资源 2.上传数据 相当于I/O 4.http协议采用tcp协议 网页 图片 音乐其实都是资源 Http请求 http request Method&#xff1a;Get/Post资源/路径&#xff1a…

GitLab 老旧版本如何升级?

极狐GitLab 正式对外推出 GitLab 专业升级服务 https://dl.gitlab.cn/cm33bsfv&#xff01; 专业的技术人员为您的 GitLab 老旧版本实例进行专业升级&#xff01;服务详情可以在官网查看详细解读&#xff01; 那些因为老旧版本而被攻击的例子 话不多说&#xff0c;直接上图&a…

通用大模型应用研究七:RAGOS和AgentOS

RAG&#xff0c;即检索增强生成&#xff08;Retrieval-Augmented Generation&#xff09;&#xff0c;是一种结合了信息检索和大型语言模型&#xff08;LLM&#xff09;提示的技术。它通过从数据源检索相关信息&#xff0c;并将检索到的信息与问题一起注入到LLM提示中&#xff…

一起赚美元第九期及相关推荐

一、核心内容 &#xff08;一&#xff09;一起赚美元第九期文章导读 作者复盘了在 10 天内通过知识付费赚到 220750 美元的故事。运营数据&#xff1a;24 号课程做完&#xff0c;28 号课程开卖&#xff0c;10 天后 262 人付款&#xff0c;均价 800 美元&#xff0c;总金额 22…

【Android】事件分发机制

Android 的事件分发机制主要包括以下几个步骤&#xff1a; 事件生成&#xff1a;用户在设备上进行触摸、滑动等操作时&#xff0c;系统会生成相应的事件&#xff0c;如触摸事件&#xff08;MotionEvent&#xff09;。 事件发送&#xff1a;生成的事件会被发送到当前活动&#…

【linux】线程 (三)

13. 常见锁概念 &#xff08;一&#xff09;了解死锁 死锁是指在一组进程中的各个进程均占有不会释放的资源&#xff0c;但因互相申请被其他进程占有的&#xff0c;且不释放的资源&#xff0c;而处于的一种永久等待状态 &#xff08;二&#xff09;死锁四个必要条件 互斥条件…

uniapp项目结构基本了解

基本结构的解释 App.vue&#xff1a;应用的根组件&#xff0c;定义全局布局和逻辑。pages/&#xff1a;存放各个页面的 .vue 文件&#xff0c;定义应用的具体页面和功能模块。main.js&#xff1a;应用入口文件&#xff0c;初始化应用&#xff0c;挂载 App.vue。manifest.json&…