浅析C++的指针与引用

浅析C++的指针与引用

文章目录

  • 浅析C++的指针与引用
    • 一、对比引用与指针
    • 二、引用
        • 左值引用
        • 右值引用
        • 引用折叠
    • 三、指针与引用的性能差距
    • 总结

一、对比引用与指针

总论:

引用指针
必须初始化可以不初始化
不能为空可以为空
不能更换目标可以更换目标

引用必须初始化,而指针可以不初始化。

我们在定义一个引用的时候必须为其指定一个初始值,但是指针却不需要。

int &r;    //不合法,没有初始化引用
int *p;    //合法,但p为野指针,使用需要小心

引用不能为空,而指针可以为空。

由于引用不能为空,所以我们在使用引用的时候不需要测试其合法性,而在使用指针的时候需要首先判断指针是否为空指针,否则可能会引起程序崩溃。

void test_p(int* p)
{if(p != null_ptr)    //对p所指对象赋值时需先判断p是否为空指针*p = 3;return;
}
void test_r(int& r)
{r = 3;    //由于引用不能为空,所以此处无需判断r的有效性就可以对r直接赋值return;
}

引用不能更换目标

指针可以随时改变指向,但是引用只能指向初始化时指向的对象,无法改变。

int a = 1;
int b = 2;int &r = a;    //初始化引用r指向变量a
int *p = &a;   //初始化指针p指向变量ap = &b;        //指针p指向了变量b
r = b;         //引用r依然指向a,但a的值变成了b

二、引用

左值引用

常规引用,一般表示对象的身份。

右值引用

右值引用就是必须绑定到右值(一个临时对象、将要销毁的对象)的引用,一般表示对象的值。

右值引用可实现转移语义(Move Sementics)和精确传递(Perfect Forwarding),它的主要目的有两个方面:

  • 消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率。
  • 能够更简洁明确地定义泛型函数。
引用折叠
  • X& &X& &&X&& & 可折叠成 X&
  • X&& && 可折叠成 X&&

例如,当我们有一个模板函数参数声明为 T&&,并且传递给它一个左值时,类型 T 会被推导为左值引用类型。这就是所谓的万能引用,它可以根据传入的参数类型自动调整为左值引用或右值引用。

这里有一个简单的例子来说明引用折叠:

template<typename T>
void forward(T&& arg) {// arg 可以是左值引用或右值引用
}int main() {int x = 10;forward(x);  // x 是左值,T 被推导为 int&forward(10); // 10 是右值,T 被推导为 int&&
}

在这个例子中,forward 函数可以接受任何类型的参数,无论是左值还是右值,而不需要为每种情况编写重载函数。这就是引用折叠和右值引用在现代C++编程中的妙用。

C++的引用在减少了程序员自由度的同时提升了内存操作的安全性和语义的优美性。比如引用强制要求必须初始化,可以让我们在使用引用的时候不用再去判断引用是否为空,让代码更加简洁优美,避免了指针满天飞的情形。除了这种场景之外引用还用于如下两个场景:

引用型参数

一般我们使用const reference参数作为只读形参,这种情况下既可以避免参数拷贝还可以获得与传值参数一样的调用方式

void test(const vector<int> &data)
{//...
}
int main()
{vector<int> data{1,2,3,4,5,6,7,8};test(data);
}

引用型返回值

C++提供了重载运算符的功能,我们在重载某些操作符的时候,使用引用型返回值可以获得跟该操作符原来语法相同的调用方式,保持了操作符语义的一致性。一个例子就是operator []操作符,这个操作符一般需要返回一个引用对象,才能正确的被修改。这就是我们常用的链式调用

vector<int> v(10);
v[5] = 10;    //[]操作符返回引用,然后vector对应元素才能被修改//如果[]操作符不返回引用而是指针的话,赋值语句则需要这样写
*v[5] = 10;   //这种书写方式,完全不符合我们对[]调用的认知,容易产生误解

三、指针与引用的性能差距

指针与引用之间有没有性能差距呢?这种问题就需要进入汇编层面去看一下。我们先写一个test1函数,参数传递使用指针:

void test1(int* p)
{*p = 3;    //此处应该首先判断p是否为空,为了测试的需要,此处我们没加。return;
}

该代码段对应的汇编代码如下:

(gdb) disassemble 
Dump of assembler code for function test1(int*):0x0000000000400886 <+0>:  push   %rbp0x0000000000400887 <+1>:  mov    %rsp,%rbp0x000000000040088a <+4>:  mov    %rdi,-0x8(%rbp)
=> 0x000000000040088e <+8>:  mov    -0x8(%rbp),%rax0x0000000000400892 <+12>: movl   $0x3,(%rax)0x0000000000400898 <+18>: nop0x0000000000400899 <+19>: pop    %rbp0x000000000040089a <+20>: retq   
End of assembler dump.

上述代码1、2行是参数调用保存现场操作;第3行是参数传递,函数调用第一个参数一般放在rdi寄存器,此行代码把rdi寄存器值(指针p的值)写入栈中;第4行是把栈中p的值写入rax寄存器;第5行是把立即数3写入到rax寄存器值所指向的内存中,此处要注意(%rax)两边的括号,这个括号并并不是可有可无的,(%rax)和%rax完全是两种意义,(%rax)代表rax寄存器中值所代表地址部分的内存,即相当于C++代码中的*p,而%rax代表rax寄存器,相当于C++代码中的p值,所以汇编这里使用了(%rax)而不是%rax。

我们再写出参数传递使用引用的C++代码段test2:

void test2(int& r)
{r = 3;    //赋值前无需判断reference是否为空return;
}

这段代码对应的汇编代码如下:

(gdb) disassemble 
Dump of assembler code for function test2(int&):0x000000000040089b <+0>:  push   %rbp0x000000000040089c <+1>:  mov    %rsp,%rbp0x000000000040089f <+4>:  mov    %rdi,-0x8(%rbp)
=> 0x00000000004008a3 <+8>:  mov    -0x8(%rbp),%rax0x00000000004008a7 <+12>: movl   $0x3,(%rax)0x00000000004008ad <+18>: nop0x00000000004008ae <+19>: pop    %rbp0x00000000004008af <+20>: retq   
End of assembler dump.

我们发现test2对应的汇编代码和test1对应的汇编代码完全相同,这说明C++编译器在编译程序的时候将指针和引用编译成了完全一样的机器码。所以C++中的引用只是C++对指针操作的一个“语法糖”,在底层实现时C++编译器实现这两种操作的方法完全相同


总结

​ C++中引入了引用操作,在对引用的使用加了更多限制条件的情况下,保证了引用使用的安全性和便捷性,还可以保持代码的优雅性。在适合的情况使用适合的操作,引用的使用可以一定程度避免“指针满天飞”的情况,对于提升程序稳定性也有一定的积极意义。最后,指针与引用底层实现都是一样的,不用担心两者的性能差距

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

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

相关文章

如何用SMU数字源表测试apd管的暗电流

01 APD工作原理 APD雪崩光电二极管的工作原理是基于光电效应和雪崩效应&#xff0c;当光子被吸收时&#xff0c;会产生电子空穴对&#xff0c;空穴向P区移动&#xff0c;电子向N区移动&#xff0c;由于电场的作用&#xff0c;电子与空穴相遇时会产生二次电子&#xff0c;形成雪…

串行通信——IIC总结

一.什么是IIC&#xff1f; IIC&#xff08;Inter-Integrated Circuit&#xff09;也称I2C&#xff0c;中文叫集成电路总线。是一个多主从的串行总线&#xff0c;由飞利浦公司发明的通讯总线&#xff0c;属于半双工同步传输类总线&#xff0c;仅由两条线就能完成多机通讯&#…

Android 辅助功能 -抢红包(二)

Android 辅助功能 -抢红包(二) 本篇文章继续讲述辅助功能实现抢红包的方案. 上篇文章主要讲了下辅助功能的基本使用,本文涉及的一些基础内容就不再赘述了. 有疑问的可以查看上篇文章: Android 辅助功能 -抢红包 1: 添加微信监听 修改xml文件,android:packageNames中新增微…

【解读】区块链和分布式记账技术标准体系建设指南

大家好&#xff0c;这里是苏泽。一个从业Java后端的区块链技术爱好者。 今天带大家来解读这份三部门印发的行业建设指南《区块链和分布式记账技术标准体系建设指南》 原文件可查看P020240112840724196854.pdf (www.gov.cn) 以下是个人解读&#xff0c;如有纰漏请指正&#xff…

Nginx 报错 504 Gateway Time-out 的解决方法

报错信息 504 Gateway Time-out 原因是程序执行时间过长&#xff0c;导致请求超时。 解决方法 首先&#xff0c;尽可能地优化程序代码的执行时间。 其次&#xff0c;修改配置文件。 修改 php.ini 配置文件。 max_execution_time 600 复制 修改 nginx.conf 配置文件。…

KY9 成绩排序

描述&#xff1a; 用一维数组存储学号和成绩&#xff0c;然后&#xff0c;按成绩排序输出。 输入描述&#xff1a; 输入第一行包括一个整数N(1<N<100)&#xff0c;代表学生的个数。 接下来的N行每行包括两个整数p和q&#xff0c;分别代表每个学生的学号和成绩。 输出描述…

【系统架构师】-第16章-嵌入式系统架构设计理论与实践

1、嵌入式系统发展 第一阶段&#xff1a;单片微型计算机 (SCM) 阶段&#xff0c;即单片机时代&#xff0c;五操作系统 第二阶段&#xff1a;微控制器 (MUC) 阶段&#xff0c;有简单操作系统 第三阶段&#xff1a;片上系统 (SoC)&#xff0c;兼容各种微处理器 第四阶段&…

常见滤波方式的区别的优势

一. 限幅滤波法 给定一个最大偏差X&#xff0c;如果本次值与上次差值小于X&#xff0c;则本次有效&#xff0c;否则无效&#xff0c;使用上次值代替。 #incldue <stdio.h>#define X 2 int lastvalue; //限幅滤波法 int filter(void) {int nowValue ;nowValue getValue…

软件测试 —— 测试用例设计报告

写出测试网站的测试用例&#xff0c;测试网站具体内容可看团购网站系统需求说明书1.2.doc 一、流程1&#xff1a;注册→登录 图1&#xff1a;注册->登录流程图 1、 使用场景设计法设计测试用例 1&#xff09; 找出基本流和备选流 基本流注册用户-成功登录系统备选流1注册…

Jenkins cron定时构建触发器

from&#xff1a; https://www.jenkins.io/doc/book/pipeline/syntax/#cron-syntax 以下内容为根据Jenkins官方文档cron表达式部分翻译过来&#xff0c;使用机翻加个人理解补充内容&#xff0c;包括举例。 目录 介绍举例&#xff1a;设置方法方法一&#xff1a;方法二&#xf…

3.2_1 虚拟内存的基本概念

3.2_1 虚拟内存的基本概念 虚拟存储技术也是存储空间扩充的一种技术&#xff0c;它比交换、覆盖技术要更先进一些。 &#xff08;一&#xff09;传统存储管理方式的特征、缺点 对于这种传统的存储管理方案&#xff0c;很多暂时用不到的数据也会长期占用内存&#xff0c;导致内存…

R 语言patchwork包拼图间隙

在R语言中&#xff0c;patchwork包是一个非常强大的工具&#xff0c;允许你轻松地将多个图表拼接在一起。如果你希望调整拼图间的间隙&#xff08;即图表之间的空白区域&#xff09;&#xff0c;可以通过使用plot_layout()函数来实现&#xff0c;其中可以指定guides参数和spaci…

【数据结构和算法初阶(C语言)】栈的概念和实现(后进先出---后来者居上的神奇线性结构带来的惊喜体验)

目录 1.栈 1.1栈的概念及结构 2.栈的实现 3.栈结构对数据的处理方式 3.1对栈进行初始化 3.2 从栈顶添加元素 3.3 打印栈元素 3.4移除栈顶元素 3.5获取栈顶元素 3.6获取栈中的有效个数 3.7 判断链表是否为空 3.9 销毁栈空间 4.结语及整个源码 1.栈 1.1栈的概念及结构 栈&am…

Codeforces Round 933 (Div. 3)

比赛地址传送门 A. Rudolf and the Ticket 题目大意&#xff1a; 给定两个数组和一个 k&#xff0c;要求从两个数组中各选一个数求和不大于 k&#xff0c;有多少种方案 思路&#xff1a; 维护一个数组 f[i] 代表小于等于 i 的数字的数量&#xff0c;遍历另一个数组&#xff0…

遇到:java.lang.reflect.InaccessibleObjectException: Unable to make错误应该如何解决

遇到 "java.lang.reflect.InaccessibleObjectException: Unable to make" 错误是因为你的代码尝试访问了一个不可访问的对象或方法。这通常会发生在使用反射机制时&#xff0c;尝试访问私有或受限制的成员时。要解决这个问题&#xff0c;你可以考虑以下几个步骤&…

高压辊磨机(辊压机)在矿物加工领域应用广泛 目前本土企业处于向高端转型阶段

高压辊磨机&#xff08;辊压机&#xff09;在矿物加工领域应用广泛 目前本土企业处于向高端转型阶段 高压辊磨机又称为辊压机、挤压磨&#xff0c;是基于料层粉碎原理设计的一种干式辊磨设备。高压辊磨机结构形式多样&#xff0c;但原理基本相似&#xff0c;主要由机架、高压工…

浅谈C++绑定器bind1st、bind2nd和函数对象function

今天我们先来谈谈C 标准库里面的绑定器bind1st&#xff0c;bind2nd 和函数对象function C 绑定器和函数对象 一、绑定器二、函数对象 一、绑定器 虽然在C11标准中这两个绑定函数已经被弃用&#xff0c;但仍然值得我们深入思考其底层原理。从字面上理解&#xff0c;“绑定” 这…

【3】文件读写

Python 读取文件的三种常见方法 使用open()函数打开文件&#xff0c;并使用read()方法读取文件的内容。例如&#xff1a; file open("filename.txt", "r") content file.read() file.close()使用with语句打开文件&#xff0c;并使用readlines()方法读取…

Explain

Explain EXPLAIN是MySQL提供的一种用于分析SQL查询执行计划的工具&#xff0c;通过它我们可以深入了解数据库如何执行一条SQL语句&#xff0c;以及优化器在选择索引、访问表和排序数据等方面的决策。 我整理了一份思维导图方便更好查看各个参数的意义&#xff0c;红色表示比较…

RabbitMq踩坑记录

1、连接报错&#xff1a;Broker not available; cannot force queue declarations during start: java.io.IOException 2.1、原因&#xff1a;端口不对 2.2、解决方案&#xff1a; 检查你的连接配置&#xff0c;很可能是你的yml里面的端口配置的是15672&#xff0c;更改为5672即…