当前位置: 首页 > news >正文

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

相关文章:

浅析C++的指针与引用

浅析C的指针与引用 文章目录 浅析C的指针与引用一、对比引用与指针二、引用左值引用右值引用引用折叠 三、指针与引用的性能差距总结 一、对比引用与指针 总论&#xff1a; 引用指针必须初始化可以不初始化不能为空可以为空不能更换目标可以更换目标 引用必须初始化&#xff…...

【消息队列开发】 实现消息删除逻辑

文章目录 &#x1f343;前言&#x1f332;实现步骤&#x1f6a9;检验参数的合法性&#x1f6a9;读取Message数据&#x1f6a9;二进制转为message&#x1f6a9;isValid 设置为无效&#x1f6a9;写入文件&#x1f6a9;更新统计文件&#x1f6a9;特别注意&#x1f6a9;完整代码 ⭕…...

【golang】28、用 httptest 做 web server 的 controller 的单测

文章目录 一、构建 HTTP server1.1 model.go1.2 server.go1.3 curl 验证 server 功能1.3.1 新建1.3.2 查询1.3.3 更新1.3.4 删除 二、httptest 测试2.1 完整示例2.2 实现逻辑2.3 其他示例2.4 用 TestMain 避免重复的测试代码2.5 gin 框架的 httptest 一、构建 HTTP server 1.1…...

296.【华为OD机试】污染水域 (图的多源BFS—JavaPythonC++JS实现)

🚀点击这里可直接跳转到本专栏,可查阅顶置最新的华为OD机试宝典~ 本专栏所有题目均包含优质解题思路,高质量解题代码(Java&Python&C++&JS分别实现),详细代码讲解,助你深入学习,深度掌握! 文章目录 一. 题目-污染水域二.解题思路三.题解代码Python题解代码…...

C语言——动态内存分配

前言&#xff1a;通过前面的学习&#xff0c;我们知道C语言中在内存中开辟空间的方法有&#xff1a;变量和数组。既然拥有了开辟空间的方法&#xff0c;我们为什么还要学习动态内存分配呢&#xff1f; int val 20; //在内存中开辟四个字节的空间 int arr[10] { 0 }; //在内…...

瑞_23种设计模式_策略模式

文章目录 1 策略模式&#xff08;Strategy Pattern&#xff09;★1.1 介绍1.2 概述1.3 策略模式的结构1.4 策略模式的优缺点1.5 策略模式的使用场景 2 案例一2.1 需求2.2 代码实现 3 案例二3.1 需求3.2 代码实现 4 JDK源码解析&#xff08;Comparator&#xff09; &#x1f64a…...

使用 OpenAI 的 text-embedding 构建知识向量库并进行相似搜索

OpenAI的embedding模型的使用 首先第一篇文章中探讨和使用了ChatGPT4的API-Key实现基础的多轮对话和流式输出&#xff0c;完成了对GPT-API的一个初探索&#xff0c;那第二步打算使用OpenAI的embedding模型来构建一个知识向量库&#xff0c;其实知识向量库本质上就是一个包含着一…...

设计模式学习笔记 - 规范与重构 - 5.如何通过封装、抽象、模块化、中间层解耦代码?

前言 《规范与重构 - 1.什么情况下要重构&#xff1f;重构什么&#xff1f;又该如何重构&#xff1f;》讲过&#xff0c;重构可以分为大规模高层重构&#xff08;简称 “大型重构”&#xff09;和小规模低层次重构&#xff08;简称 “小型重构”&#xff09;。大型重构是对系统…...

YOLOv9实例分割教程|(二)验证教程

专栏地址&#xff1a;目前售价售价59.9&#xff0c;改进点30个 专栏介绍&#xff1a;YOLOv9改进系列 | 包含深度学习最新创新&#xff0c;助力高效涨点&#xff01;&#xff01;&#xff01; 一、验证 打开分割验证文件&#xff0c;填入数据集配置文件、训练好的权重文件&…...

python 基础知识点(蓝桥杯python科目个人复习计划63)

今日复习内容&#xff1a;做题 例题1&#xff1a;蓝桥骑士 问题描述&#xff1a; 小蓝是蓝桥王国的骑士&#xff0c;他喜欢不断突破自我。 这天蓝桥国王给他安排了N个对手&#xff0c;他们的战力值分别为a1,a2,...,an&#xff0c;且按顺序阻挡在小蓝的前方。对于这些对手小…...

IAB视频广告标准《数字视频和有线电视广告格式指南》之 简介、目录及视频配套广告 - 我为什么要翻译介绍美国人工智能科技公司IAB系列(2)

写在前面 谈及到中国企业走入国际市场&#xff0c;拓展海外营销渠道的时候&#xff0c;如果单纯依靠一个小公司去国外做广告&#xff0c;拉渠道&#xff0c;找代理公司&#xff0c;从售前到售后&#xff0c;都是非常不现实的。我们可以回想一下40年前&#xff0c;30年前&#x…...

Python网络基础爬虫-python基本语法

文章目录 逻辑语句if,else,elifforwhile异常处理 函数与类defpassclass 逻辑语句 熟悉C/C语言的人们可能很希望Python提供switch语句&#xff0c;但Python中并没有这个关键词&#xff0c;也没有这个语句结构。但是可以通过if-elif-elif-…这样的结构代替&#xff0c;或者使用字…...

产品推荐 - 基于星嵌 OMAPL138+国产FPGA的DSP+ARM+FPGA三核开发板

1 评估板简介 基于TI OMAP-L138&#xff08;定点/浮点DSP C674xARM9&#xff09; FPGA处理器的开发板&#xff1b; OMAP-L138是TI德州仪器的TMS320C6748ARM926EJ-S异构双核处理器&#xff0c;主频456MHz&#xff0c;高达3648MIPS和2746MFLOPS的运算能力&#xff1b; FPGA…...

【微服务学习笔记(一)】Nacos、Feign、Gateway基础使用

【微服务学习笔记&#xff08;一&#xff09;】Nacos、Feign、Gateway基础使用 总览Nacos安装配置Nacos注册中心服务多级存储模型负载均衡规则环境隔离 配置管理配置拉取配置热更新多服务共享配置 Feign远程调用配置性能优化Fegin使用 统一网关Gateway搭建网关路由断言工厂&…...

使用maven打生产环境可执行包

一、程序为什么要打包 程序打包的主要目的是将项目的源代码、依赖库和其他资源打包成一个可执行的文件或者部署包&#xff0c;方便程序的发布和部署。以下是一些打包程序的重要理由&#xff1a; 方便部署和分发&#xff1a;打包后的程序可以作为一个独立的实体&#xff0c;方便…...

springboot+ssm基于vue.js的客户关系Crm管理系统

系统包含两种角色&#xff1a;管理员、用户&#xff0c;主要功能如下。 ide工具&#xff1a;IDEA 或者eclipse 编程语言: java 数据库: mysql5.7 框架&#xff1a;ssmspringboot都有 前端&#xff1a;vue.jsElementUI 详细技术&#xff1a;springbootSSMvueMYSQLMAVEN 数据库…...

github 中的java前后端项目整合到本地运行

前言: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;不提供完整代码&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 本文章未…...

分布式ID(7):Zookeeper实现分布式ID生成

1 原理 实现方式有两种,一种通过节点,一种通过节点的版本号 节点的特性持久顺序节点(PERSISTENT_SEQUENTIAL) 他的基本特性和持久节点是一致的,额外的特性表现在顺序性上。在ZooKeeper中,每个父节点都会为他的第一级子节点维护一份顺序,用于记录下每个子节点创建的先后顺序…...

钉钉小程序 - - - - - 如何通过一个链接打开小程序内的指定页面

方式1 钉钉小程序 scheme dingtalk://dingtalkclient/action/open_mini_app?miniAppId123&pagepages%2Findex%2Findex%3Fx%3D%25E4%25B8%25AD%25E6%2596%2587 方式2 https://applink.dingtalk.com/action/open_mini_app?type2&miniAppIdminiAppId&corpIdcorpId&…...

Java代码基础算法练习---2024.3.14

其实这就是从我学校的资源&#xff0c;都比较基础的算法题&#xff0c;先尽量每天都做1-2题&#xff0c;练手感。毕竟离我真正去尝试入职好的公司&#xff08;我指的就是中大厂&#xff0c;但是任重道远啊&#xff09;&#xff0c;仍有一定的时间&#xff0c;至少要等我升本之后…...

ubuntu搭建nfs服务centos挂载访问

在Ubuntu上设置NFS服务器 在Ubuntu上&#xff0c;你可以使用apt包管理器来安装NFS服务器。打开终端并运行&#xff1a; sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享&#xff0c;例如/shared&#xff1a; sudo mkdir /shared sud…...

模型参数、模型存储精度、参数与显存

模型参数量衡量单位 M&#xff1a;百万&#xff08;Million&#xff09; B&#xff1a;十亿&#xff08;Billion&#xff09; 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的&#xff0c;但是一个参数所表示多少字节不一定&#xff0c;需要看这个参数以什么…...

遍历 Map 类型集合的方法汇总

1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

QMC5883L的驱动

简介 本篇文章的代码已经上传到了github上面&#xff0c;开源代码 作为一个电子罗盘模块&#xff0c;我们可以通过I2C从中获取偏航角yaw&#xff0c;相对于六轴陀螺仪的yaw&#xff0c;qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...

第25节 Node.js 断言测试

Node.js的assert模块主要用于编写程序的单元测试时使用&#xff0c;通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试&#xff0c;通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...

sqlserver 根据指定字符 解析拼接字符串

DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...

C++.OpenGL (10/64)基础光照(Basic Lighting)

基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...

大模型多显卡多服务器并行计算方法与实践指南

一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...

MySQL中【正则表达式】用法

MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现&#xff08;两者等价&#xff09;&#xff0c;用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例&#xff1a; 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...

iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈

在日常iOS开发过程中&#xff0c;性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期&#xff0c;开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发&#xff0c;但背后往往隐藏着系统资源调度不当…...