九.C++ 对引用的学习

一.基本概念

引用即内存的别名

int a = 10;

int& b = a;

引用本身不占用内存,并非实体,对引用的所有操作都是在对目标内存进行操作

引用必须初始化,且不能更换对象

int c = 5;

b = c; // × 仅仅是在对引用的目标内存进行赋值

#include <iostream>
using namespace std;int main() {int mm1 = 20;  // 1. 定义整型变量mm1并初始化为20int& mm2 = mm1; // 2. 定义整型引用mm2,绑定到mm1int mm3 = 15;  // 3. 定义整型变量mm3并初始化为15mm2 = mm3;  // 4. 将mm3的值赋给mm2(即mm1)cout << "mm1: " << mm1 << endl;  // 输出 15cout << "mm2: " << mm2 << endl;  // 输出 15cout << "mm3: " << mm3 << endl;  // 输出 15return 0;
}

详细执行过程:

  1. int mm1 = 20;

    • 定义一个整型变量 mm1,并将其初始值设为 20

    • 内存中:mm1 存储的值是 20

  2. int& mm2 = mm1;

    • 定义一个整型引用 mm2,并将其绑定到 mm1

    • 引用 mm2 并不是一个新的变量,而是 mm1 的别名(另一个名字)。

    • 此时,mm2mm1 指向同一块内存,修改 mm2 就是修改 mm1,反之亦然。

  3. int mm3 = 15;

    • 定义一个整型变量 mm3,并将其初始值设为 15

    • 内存中:mm3 存储的值是 15

  4. mm2 = mm3;

    • mm3 的值(15)赋给 mm2

    • 由于 mm2mm1 的引用,这实际上是将 15 赋给 mm1

    • 执行后:

      • mm1 的值变为 15

      • mm2 的值也变为 15(因为它是 mm1 的引用)。

      • mm3 的值仍然是 15(不受影响)。

最终结果:

  • mm1 的值:15(被 mm2 = mm3 修改)

  • mm2 的值:15(因为它是 mm1 的引用)

  • mm3 的值:15(保持不变)

输出

mm1: 15
mm2: 15
mm3: 15

关键点总结:

  1. 引用(&)的本质

    • 引用是变量的别名,不是独立的变量。

    • int& mm2 = mm1; 表示 mm2mm1 的另一个名字,两者共享同一块内存。

  2. 赋值操作的影响

    • mm2 = mm3; 实际上是将 mm3 的值赋给 mm1(因为 mm2mm1 的引用)。

    • 引用本身没有独立的内存空间,赋值操作会直接影响它绑定的变量。

  3. 与指针的区别

    • 如果是 int* p = &mm1; *p = mm3;,效果和引用类似,但引用更简洁且不能重新绑定。

    • 引用一旦绑定后不能更改绑定的对象,而指针可以指向不同的对象。

引用本身一旦绑定后不能重新绑定到另一个对象,引用不能改变

关于 “引用不能改变” 的说法需要更精确的解释。实际上,引用本身一旦绑定后不能重新绑定到另一个对象,但 引用绑定的对象的内容是可以改变的。这是两个不同的概念:

1. 引用的“不可变性”(不能重新绑定)

  • 引用一旦初始化后,不能指向其他变量

    • 引用在声明时必须初始化,并且之后不能更改它绑定的对象。

    • 例如:

int a = 10;
int b = 20;
int& ref = a;  // ref 绑定到 a
ref = b;       // 这是修改 ref 绑定的对象 a 的值,不是改变 ref 的绑定!// ref = &b;   // 错误!不能重新绑定 ref 到 b(C++ 不允许)
  • 关键点ref = b 是把 b 的值赋给 a(因为 refa 的引用),而不是让 ref 指向 b

2. 引用绑定的对象的内容可以改变

引用本身是别名,修改引用就是修改它绑定的对象

  • 例如:

int a = 10;
int& ref = a;  // ref 是 a 的别名
ref = 20;      // 修改 ref 就是修改 a,a 现在是 20

常见误区澄清

  • 误区:“引用不能改变” → 实际上是指 引用不能重新绑定,但可以修改它绑定的对象的内容。

    正确说法:

    • 引用 不能重新绑定(不能指向其他对象)。

    • 引用 可以修改它绑定的对象的内容(因为它是别名)。

总结

  • 引用一旦绑定后不能重新绑定(不能指向其他变量),这是它的“不可变性”。

  • 引用绑定的对象的内容可以改变(通过引用直接修改)。

 mm2 = mm3
  • mm2mm1 的引用,所以 mm2 = mm3 就是 mm1 = mm3

  • 修改的是 mm1 的值,mm2 仍然绑定到 mm1(不能重新绑定)。

二.引用的常属性必须和目标的常属性“一致”(个别情况也可以不一致)

const int e = 10;

int& f = e; // ×

const int& g = e; // √

可以限定更加严格(别名可以比真名更加严格)

int a = 10;const int& h = a;   // OK

三.const 修饰一个常量(常引用)

在 C++ 中,const 引用const T&)是一种非常重要的特性,它结合了引用的效率和 const 的安全性。

1. 基本概念
  • const 引用

    • 是一个对常量对象的引用,不能通过该引用修改所绑定的对象。

    • 语法:const T& ref = obj;

    • 作用:提供对对象的只读访问,同时避免拷贝(与普通引用类似)。

    与普通引用的区别

特性普通引用 (T&)const 引用 (const T&)
能否修改对象可以修改绑定的对象不能修改绑定的对象
绑定对象类型必须绑定可修改的对象可以绑定 const 或非 const 对象
安全性可能意外修改数据提供只读访问,更安全
2. 核心知识点
(1) const 引用可以绑定到 const 和非 const 对象
  • 可以绑定到非 const 对象(提供只读访问):

int x = 10;
const int& ref = x;  // ref 是 x 的 const 引用
// ref = 20;         // 错误!不能通过 ref 修改 x
  • 可以绑定到 const 对象(合法且安全):

const int y = 20;
const int& ref = y;  // ref 是 y 的 const 引用
// ref = 30;         // 错误!y 本身就是 const
  • 普通引用不能绑定到 const 对象

const int z = 30;
int& ref = z;        // 错误!普通引用不能绑定 const 对象
(2) const 引用可以延长临时对象的生命周期
  • 临时对象(如函数返回值、表达式结果)通常只能绑定到 const 引用

  • 用途:避免不必要的拷贝,同时保证安全性(不能修改临时对象)

10;   // 声明周期很短,正常执行过了封号后 10这个内存地址就不在了int& ri = 10;  // 报错      非常引用的初始值必须为左值,普通引用不能绑定临时对象const int& ri = 10;  // 正确,有了这个别名之后,声明周期就变长了,会跟着别名的生命周期改变

了解一个左值和右值的概念:

具体名字的内存,能够取地址 --》 左值 (非常左值[无const修复] | 常左值[有const修复])

匿名内存,不能取地址值 --》 右值 (认为直接更改右值没有意义) 比如 10;

常引用可以作为任何东西的别名

特点:

1.引用可以延长右值的生命周期

2.常引用 即 万能引用

3.引用的生命周期不能长于目标

int a = 10;int& ra = a;	// OK  引用ra可以是a的别名
const int& ri = a;  // OK  常引用 ri 可以是 a的别名const int b = 20;int& bb = b;   // ERROR  const int& bb = b;   // OK  常引用bb可以是常量b的别名const int& rf = 10;  // OK  常引用rf可以是常量10的别名int foo(){}const int& hg = foo();   // OK
(3) const 引用作为函数参数(推荐用法)
  • 传递大对象时避免拷贝,同时防止函数内部修改参数:

void printValue(const std::string& str) {// str = "new value";  // 错误!不能修改std::cout << str << std::endl;
}int main() {std::string s = "Hello";printValue(s);  // 传递 const 引用,避免拷贝
}

 

优势:

  • 高效(无拷贝)。

  • 安全(函数不能意外修改参数)。

(4) const 引用与返回值
  • 函数返回 const 引用:

    • 可以避免返回临时对象的拷贝,同时防止调用者修改返回值:

const std::string& getString() {static std::string s = "Hello";return s;  // 返回 static 变量的 const 引用
}

注意:如果返回局部变量的引用,会导致悬空引用(未定义行为)!  

const std::string& getLocalString() {std::string s = "Hello";return s;  // 错误!s 是局部变量,函数结束后被销毁
}
(5) const 引用与重载
  • const 引用可以区分重载函数

void func(const int& x) { std::cout << "const ref" << std::endl; }
void func(int& x) { std::cout << "non-const ref" << std::endl; }int main() {int a = 10;const int b = 20;func(a);  // 调用非 const 版本func(b);  // 调用 const 版本
}

 

  • 编译器会根据参数是否为 const 选择正确的重载版本。

(6) const 引用与移动语义
  • const 引用不能用于移动语义

    • 移动语义需要修改源对象(“窃取”资源),而 const 引用禁止修改:

std::string s = "Hello";
const std::string& ref = s;
// std::string moved = std::move(ref);  // 错误!ref 是 const

 正确做法:传递非 const 引用或值:

std::string moved = std::move(s);  // 正确
3. 最佳实践
  1. 优先使用 const 引用传递参数

    • 避免拷贝,同时保证函数不会意外修改参数。

    • 示例:

void process(const std::vector<int>& data) { ... }
  1. 返回 const 引用时注意生命周期

  • 只能返回全局/静态对象的引用,不能返回局部变量的引用。

  1. 区分 const 和非 const 重载

  • 提供更灵活的接口(如 std::vectoroperator[]const 和非 const 版本)。

  1. 避免滥用 const 引用

  • 如果需要修改参数,使用普通引用。

  • 如果需要返回可修改的对象,返回值或非 const 引用。

4. 常见误区
  • 误区 1:“const 引用不能绑定到临时对象”

    • 纠正const 引用可以绑定到临时对象(普通引用不能)。

  • 误区 2:“const 引用可以修改绑定的对象”

    • 纠正const 引用不能修改绑定的对象(这是它的核心特性)。

  • 误区 3:“const 引用总是比值传递好”

    • 纠正:如果对象很小(如 int),值传递可能更高效(避免引用开销)。

5. 总结
特性const 引用 (const T&)
绑定对象可以绑定 const 或非 const 对象
能否修改对象不能(提供只读访问)
临时对象绑定可以绑定临时对象(普通引用不能)
函数参数推荐用于大对象,避免拷贝且保证安全性
返回值可以返回 const 引用(但需注意生命周期)
重载区分可以与非 const 引用重载
移动语义不能用于移动语义(因为不能修改源对象)

核心原则

  • const 引用 = 高效 + 安全(避免拷贝,防止意外修改)。

  • 普通引用 = 高效 + 可修改(需要修改参数时使用)。

  • 值传递 = 简单 + 安全(小对象或需要独立副本时使用)。

建议:合理使用 const 引用可以显著提升代码的性能和安全性!

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

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

    相关文章

    7.2.1_顺序查找

    知识总览&#xff1a; 顺序查找&#xff1a; 算法思想&#xff1a; 从头到脚挨个找或者从脚到头挨个找适用于线性表(顺序存储和链式存储都适用)&#xff0c;又叫线性查找 实现&#xff1a; 1个数组elem指向数组的起始位置&#xff0c;索引从0开始遍历数组直到找到目标值返回…

    视觉SLAM基础补盲

    3D Gaussian Splatting for Real-Time Radiance Field Rendering SOTA方法3DGS contribution传统重建基于点的渲染NeRF 基础知识补盲光栅化SFM三角化极线几何标准的双目立体视觉立体匹配理论与方法立体匹配的基本流程李群和李代数 李群和李代数的映射李代数的求导李代数解决求导…

    如何利用 Redis 实现跨多个无状态服务实例的会话共享?

    使用 Redis 实现跨多个无状态服务实例的会话共享是一种非常常见且有效的方案。无状态服务本身不存储会话信息&#xff0c;而是将用户的会话数据集中存储在外部存储中&#xff08;如 Redis&#xff09;&#xff0c;这样任何一个服务实例都可以通过查询外部存储来获取和更新用户的…

    《chipyard》docker使用

    一、启动/重启服务 二、登入/退出 容器对象查看 sudo docker ps -a # 查看容器列表 登入已例化的容器 sudo docker exec -it -u root 737ed3ddd5ff bash # 737ed3ddd5ff<容器名称/ID> 三、容器编辑 删除单个容器 sudo docker stop <容器ID> #停止容器 s…

    浏览器工作原理06 [#]渲染流程(下):HTML、CSS和JavaScript是如何变成页面的

    引用 浏览器工作原理与实践 简单回顾下上节前三个阶段的主要内容&#xff1a;在HTML页面内容被提交给渲染引擎之后&#xff0c;渲染引擎首先将HTML解析为浏览器可以理解的DOM&#xff1b;然后根据CSS样式表&#xff0c;计算出DOM树所有节点的样式&#xff1b;接着又计算每个元素…

    AI书签管理工具开发全记录(十三):TUI基本框架搭建

    文章目录 AI书签管理工具开发全记录&#xff08;十三&#xff09;&#xff1a;TUI基本框架搭建前言 &#x1f4dd;1.TUI介绍 &#x1f50d;2. 框架选择 ⚙️3. 功能梳理 &#x1f3af;4. 基础框架搭建⚙️4.1 安装4.2 参数设计4.3 绘制ui4.3.1 设计结构体4.3.2 创建头部4.3.3 创…

    CC7利用链深度解析

    CommonsCollections7&#xff08;CC7&#xff09;是CC反序列化利用链中的重要成员&#xff0c;由Matthias Kaiser在2016年发现。本文将从底层原理到实战利用&#xff0c;全面剖析这条独特而强大的利用链。 一、CC7链技术定位 1.1 核心价值 无第三方依赖&#xff1a;仅需JDK原…

    openvino使用教程

    OpenVINO使用教程 本专栏内容支持平台章节计划 本专栏内容 OpenVINO 是一款开源工具包&#xff0c;用于在云端、本地和边缘部署高性能 AI 解决方案。我们可以使用来自最热门模型框架的生成式和传统 AI 模型来开发应用程序。充分利用英特尔 硬件的潜力&#xff0c;使用openvino…

    ESP8266(NodeMcu)+GPS模块+TFT屏幕实现GPS码表

    前言 去年写过一篇关于使用esp8266(nodemcu)gps模块oled屏幕diy的gps定位器的文章.点击回顾 .无奈OLED屏幕太小了,最近刚好有时间又折腾使用TFT屏幕diy了一款gps码表 效果如图 材料准备 依旧是请出我们的两位老演员 nocdmcu一块. GPS定位模块(我买的大夏龙雀的DX-GP10-GP…

    解决获取视频第一帧黑屏问题

    文章目录 解决获取视频第一帧黑屏问题核心代码 解决获取视频第一帧黑屏问题 废话不多说&#xff0c;直接上代码&#xff1a; <script setup> const status ref(请点击“添加视频”按钮添加视频) const videoElement ref(document.createElement(video)) const curren…

    通过BUG(prvIdleTask、pxTasksWaitingTerminatio不断跳转问题)了解空闲函数(prvIdleTask)和TCB

    一、前言与问题 在基于 FreeRTOS 的嵌入式系统中&#xff0c;我使用 STM32F1 开发一个 MQTT 客户端应用&#xff0c;涉及两个主要任务&#xff1a; ATRecvParser&#xff1a;负责解析 Wi-Fi 模块的 AT 命令响应&#xff08;如 OK、ERROR 和 IPD 数据&#xff09;。MQTT_Clien…

    继MySQL之后的技术-JDBC-从浅到深-02

    目录 概念 编程六部曲 SQL注入和statement 工具类的封装 JDBC事务 模糊查询 批处理 数据库连接池 Apache-DBUtils BasicDao 概念 JDBC为访问不同的数据库提供了统一的接口&#xff0c;为使用者屏蔽了细节问题。 Java程序员使用JDBC&#xff0c;可以连接任何提供了JD…

    【配置 YOLOX 用于按目录分类的图片数据集】

    现在的图标点选越来越多&#xff0c;如何一步解决&#xff0c;采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集&#xff08;每个目录代表一个类别&#xff0c;目录下是该类别的所有图片&#xff09;&#xff0c;你需要进行以下配置步骤&#x…

    浅谈python如何做接口自动化

    工具与环境准备 开发工具 PyCharm专业版&#xff1a;支持项目视图、代码导航、调试功能和主流框架开发官方资源&#xff1a;JetBrains PyCharm 数据库操作 使用mysqlclient库操作MySQL&#xff08;Django官方推荐&#xff09;安装命令&#xff1a;pip install mysqlclient1.3.…

    知识图谱技术概述

    一、概述 知识图谱&#xff08;Knowledge Graph&#xff09; 是一种基于图结构的语义网络&#xff0c;用于表示实体及其之间的关系&#xff0c;旨在实现更智能的知识表示和推理。它通过将现实世界中的各类信息抽象为 “实体-关系-实体” 的三元组结构&#xff0c;构建出复杂的知…

    NodeJS Koa 后端用户会话管理,JWT, Session,长短Token,本文一次性讲明白

    前言 前几天&#xff0c;我写了一篇文章&#xff0c;《我设计的一个安全的 web 系统用户密码管理流程》。其中着重点是讲的如何利用非对称加密进行安全的设计&#xff0c;并在讲述了原理之后&#xff0c;又写了 《node 后端和浏览器前端&#xff0c;有关 RSA 非对称加密的完整…

    0.5S 级精度背后:DJSF1352-RN-6 如何让储能电站的每 1kWh 都「有迹可循」?

    1、背景 在能源转型的时代洪流里&#xff0c;大型储能电站作为保障电网稳定运行、平衡能源供需的核心基础设施&#xff0c;其战略价值愈发凸显。而储能电站的高效运转&#xff0c;始终离不开精准的电能计量体系支撑。今日为您重点推介一款针对 1500V 储能系统研发的专业电能表…

    Linux运维笔记:服务器安全加固

    文章目录 背景加固措施1. 修改用户密码2. 使用公钥认证替代密码登录3. 强化系统安全4. 扫描与清理残留威胁5. 规范软件管理&#xff08;重点&#xff09; 注意事项总结 提示&#xff1a;本文总结了大学实验室 Linux 电脑感染挖矿病毒后的安全加固措施&#xff0c;重点介绍用户密…

    Pycharm 配置解释器

    今天更新了一版pycharm&#xff0c;因为很久没有配置解释器了&#xff0c;发现一直失败。经过来回试了几次终于成功了&#xff0c;记录一下过程。 Step 1 Step 2 这里第二步一定要注意类型要选择python 而不是conda。 虽然我的解释器是conda 里面建立的一个环境。挺有意思的

    【Linux】awk 命令详解及使用示例:结构化文本数据处理工具

    【Linux】awk 命令详解及使用示例&#xff1a;结构化文本数据处理工具 引言 awk 是一种强大的文本处理工具和编程语言&#xff0c;专为处理结构化文本数据而设计。它的名称来源于其三位创始人的姓氏首字母&#xff1a;Alfred Aho、Peter Weinberger 和 Brian Kernighan。 基…