左值引用和右值引用

一、基本概念

  1. 左值(lvalue)和右值(rvalue)

    • 左值指的是有确定存储位置(地址)的对象,通常可以出现在赋值语句左侧。例如:变量名、解引用指针得到的对象、数组元素等都属于左值。

    • 右值一般指临时对象或字面常量,通常没有固定的存储地址,只能出现在赋值语句右侧。例如:字面量(42"hello")、表达式求值产生的临时结果(a + b、函数返回的非引用类型)等都属于右值。

  2. 左值引用(lvalue reference)T&

    • 语法:T&

    • 含义:引用一个左值,必须绑定到一个具有名字且可寻址的对象上。

    • 作用:可以通过引用直接操作原对象,不产生拷贝;常用于函数参数(接收可修改的实参)或延长临时对象的生命周期(使用 const T&)。

  3. 右值引用(rvalue reference)T&&(C++11 引入)

    • 语法:T&&

    • 含义:引用一个右值,只能绑定到临时对象(比如函数返回的非引用类型对象、字面量或者std::move之后的结果)。

    • 作用:为“移动语义(move semantics)”和“完美转发(perfect forwarding)”提供基础,通过接收将要被销毁的临时对象,可以“窃取”其内部资源而不是拷贝。


二、左值引用与右值引用的区别

特性左值引用 T&右值引用 T&&
可绑定的对象只能绑定到 左值(命名变量等)只能绑定到 右值(临时对象、字面常量、std::move产生的中间值)
是否可修改可修改所引用的对象可以修改所引用的临时对象(临时对象本来就要销毁)
延长生命周期const T& 可延长临时对象生命周期T& 不能绑定临时对象绑定临时对象后,可操作临时,直到其生命周期结束
主要用途传递可修改的已有对象,避免拷贝实现移动语义、完美转发,减少不必要的深度拷贝
  • 左值引用 T&

    • 只能引用已有的命名对象。

    • 用途:

      1. 函数参数接收时,可以直接修改传入的实参(例如 void foo(int& x))。

      2. 避免拷贝开销(例如 void print(const std::string& s))。

      3. const T& 可以绑定到右值,用于只读访问且延长临时对象生命周期。

  • 右值引用 T&&

    • 只能引用临时对象(右值)。

    • 用途:

      1. 移动构造/移动赋值:从临时对象“窃取”内部资源,而不做深拷贝。

      2. 完美转发:在模板中,通过 T&&(与 std::forward<T>(…))保持函数参数的值类别(左值/右值)不变。

      3. 禁止绑定左值:直接传递命名对象到 T&& 参数会编译错误,除非显式使用 std::move


三、典型示例

下面通过几个示例来直观说明它们的使用场景和区别。

1. 直接绑定示例

#include <iostream>
#include <string>int main() {int  a = 10;            // a 是左值int& lref = a;          // 左值引用只能绑定到左值// int& lref2 = 20;     // 错误:不能把 int& 绑定到字面量 20(右值)上int&& rref = 20;        // 右值引用只能绑定到右值// int&& rref2 = a;     // 错误:不能把 int&& 绑定到左值 a 上std::string s = "Hello";std::string& ls = s;    // 合法,引用已有 std::string 对象const std::string& lsc = "World"; // const std::string& 可以绑定到右值 "World",临时字符串的生命周期延长至 lsc 作用域结束std::string&& rs = std::string("Temp"); // 右值引用绑定到了临时 std::string("Temp"),可在后续对 rs 进行修改std::cout << "a: " << a << "\n";              // a: 10std::cout << "lref: " << lref << "\n";        // lref: 10std::cout << "rref: " << rref << "\n";        // rref: 20std::cout << "ls: " << ls << "\n";            // ls: Hellostd::cout << "lsc: " << lsc << "\n";          // lsc: Worldstd::cout << "rs: " << rs << "\n";            // rs: Tempreturn 0;
}
  • int& lref = a;:左值引用 lref 绑定到左值 a

  • int&& rref = 20;:右值引用 rref 绑定到字面量 20(右值)。

  • const std::string& lsc = "World";const T& 可以绑定到右值(临时字符串),并将其生命周期延长至 lsc 的作用域结束。

  • std::string&& rs = std::string("Temp");:右值引用 rs 绑定到临时字符串对象,此时可以对该临时对象进行修改(不过它最终会析构)。


2. 作为函数参数的区别

通常我们会看到如下重载示例,用以区分传入的是左值还是右值:

#include <iostream>// 重载 1:接受左值引用
void process(int& x) {std::cout << "process(int&): 接收左值引用, x = " << x << "\n";
}// 重载 2:接受右值引用
void process(int&& x) {std::cout << "process(int&&): 接收右值引用, x = " << x << "\n";
}int main() {int a = 5;process(a);      // 调用 process(int&), 因为 a 是左值process(10);     // 调用 process(int&&), 因为 10 是右值process(std::move(a)); // std::move(a) 将 a 转换成右值, 因此调用 process(int&&)return 0;
}
  • 当实参是一个命名变量a),它是左值,因此会匹配 void process(int&)

  • 当实参是一个字面量临时表达式(如 10std::move(a)),它们是右值,因此会匹配 void process(int&&)


3. 移动构造 vs 拷贝构造

以一个简单的自定义类 MyString 为例,演示移动构造函数与拷贝构造函数的区别。

#include <iostream>
#include <cstring>class MyString {
public:char* data;// 构造函数:从 C 风格字符串构造MyString(const char* s) {size_t len = std::strlen(s);data = new char[len + 1];std::strcpy(data, s);std::cout << "构造 MyString(\"" << data << "\")\n";}// 拷贝构造:深拷贝MyString(const MyString& other) {size_t len = std::strlen(other.data);data = new char[len + 1];std::strcpy(data, other.data);std::cout << "调用 拷贝构造, 源 = \"" << other.data << "\"\n";}// 移动构造:窃取指针,避免拷贝MyString(MyString&& other) noexcept {data = other.data;         // 直接窃取资源指针other.data = nullptr;      // 将源置空,避免析构时释放两次std::cout << "调用 移动构造\n";}// 析构函数~MyString() {if (data) {std::cout << "析构 MyString(\"" << data << "\")\n";delete[] data;} else {std::cout << "析构 MyString(nullptr)\n";}}
};MyString makeString() {MyString temp("临时");return temp; // C++11 以后可进行移动构造而非拷贝
}int main() {MyString s1("Hello");       // 普通构造MyString s2 = s1;           // 拷贝构造MyString s3 = makeString(); // 由于 makeString 返回临时对象,可触发移动构造(或编译器优化)// 可以强制使用移动构造:MyString s4 = std::move(s1);return 0;
}
  • 调用 MyString s2 = s1; 时,实参 s1 是一个左值,因此调用的是 拷贝构造,会分配新内存并拷贝字符串内容。

  • 调用 MyString s3 = makeString(); 时,makeString() 返回的临时对象是一个右值,因此会优先调用 移动构造(如果编译器没有做完全优化)。移动构造只会“窃取”临时对象的内部 char* 指针,而不会再做深拷贝。

  • s1 应用 std::move(s1),即把左值 s1 强制转换为右值,再传递给 MyString s4 = std::move(s1);,也会调用移动构造,将 s1 的内部指针转移给 s4,此时 s1.data 置为 nullptr


4. 完美转发(Perfect Forwarding)示例

在模板中,如果想让函数“如实”地将传入参数的值类别(左值/右值)传给另一个函数,可以使用右值引用和 std::forward。示例:

#include <iostream>
#include <utility> // std::forward, std::movevoid target(int& x) {std::cout << "target(int&) 被调用,x = " << x << "\n";
}void target(int&& x) {std::cout << "target(int&&) 被调用,x = " << x << "\n";
}// 通用转发函数模板
template <typename T>
void wrapper(T&& param) {// 直接传给 target,但保留 param 本身的值类别// 如果原参数是左值,就调用 target(int&); 如果是右值,就调用 target(int&&)target(std::forward<T>(param));
}int main() {int a = 100;wrapper(a);            // a 是左值 -> 调用 target(int&)wrapper(200);          // 200 是右值 -> 调用 target(int&&)int&& rr = 300;wrapper(rr);           // rr 本身虽然声明为右值引用,但 rr 名称是左值 -> 调用 target(int&)wrapper(std::move(rr)); // std::move(rr) 是右值 -> 调用 target(int&&)return 0;
}
  • wrapper(a)a 是左值,模板参数 T 被推断为 int&T&& 变为 int& &&,折叠后仍为 int&,所以 param 是左值引用,std::forward<T>(param) 仍是左值,调用 target(int&)

  • wrapper(200)200 是右值,T 被推断为 intT&&int&&param 是右值引用。std::forward<int>(param) 是右值,调用 target(int&&)

  • int&& rr = 300; 声明了一个右值引用 rr,但 rr 本身是一个命名变量(左值)。所以:

    • wrapper(rr):传入 rr(是左值),和 wrapper(a) 类似,依然调用 target(int&)

    • wrapper(std::move(rr))std::move(rr)rr 强制转换为右值,调用 target(int&&)


四、总结

  1. 绑定限制

    • T& 只能绑定到左值,不能绑定到右值(除非加上 const,即 const T& 可以绑定到右值,用于只读)。

    • T&& 只能绑定到右值,不能直接绑定到左值(除非对左值使用 std::movestd::forward 强制转换为右值)。

  2. 主要用途

    • 左值引用 T&:主要用于接收已有对象并进行读取/修改,避免拷贝开销。

    • 右值引用 T&&:主要用于实现“移动语义”,在可以销毁的临时对象上直接窃取资源;同时在模板中用于“完美转发”以保留参数的值类别。

  3. 性能意义

    • 使用 T&& 实现移动构造/移动赋值,可以显著减少对大对象(如容器、字符串等)的深拷贝,从而提升性能。

    • std::forward<T>T&& 配合可以让模板函数在转发参数时不丢失值类别,避免不必要的拷贝或移动。

  4. 注意事项

    • 虽然 T&& 只能绑定右值,但当 T 已推断为引用类型(如 int&)时,T&& 会出现“引用折叠”(reference collapsing)现象:

      • 如果 TU&,那么 T&& 等价于 U&

      • 如果 TU,那么 T&& 就是 U&&

    • 在函数重载中,若同时存在 void f(int&)void f(int&&),传入的实参的值类别会直接影响调用哪个重载。

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

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

相关文章

django入门-orm数据库操作

一&#xff1a;下载数据库依赖项mysqlclient pip install mysqlclient 二&#xff1a;django配置文件配置数据库链接 路径&#xff1a;mysite2\mysite2\settings.py DATABASES {default: {ENGINE: django.db.backends.mysql,NAME: data, # 数据库名称USER: root, …

国标GB28181设备管理软件EasyGBS视频平台筑牢文物保护安全防线创新方案

一、方案背景​ 文物作为人类文明的珍贵载体&#xff0c;具有不可再生性。当前&#xff0c;盗窃破坏、游客不文明行为及自然侵蚀威胁文物安全&#xff0c;传统保护手段存在响应滞后、覆盖不全等局限。随着5G与信息技术发展&#xff0c;基于GB28181协议的EasyGBS视频云平台&…

NetSuite Bundle - Dashboard Refresh

儿童节快乐&#xff01; 今朝发一个Bundle&#xff0c;解决一个NetSuite Dashboard的老问题。出于性能上的考虑&#xff0c;NetSuite的Dashboard中的Portlet&#xff0c;只能逐一手工刷新。有人基于浏览器做了插件&#xff0c;可以进行自动刷新。但是在我们做项目部署时&#…

<PLC><socket><西门子>基于西门子S7-1200PLC,实现手机与PLC通讯(通过websocket转接)

前言 本系列是关于PLC相关的博文,包括PLC编程、PLC与上位机通讯、PLC与下位驱动、仪器仪表等通讯、PLC指令解析等相关内容。 PLC品牌包括但不限于西门子、三菱等国外品牌,汇川、信捷等国内品牌。 除了PLC为主要内容外,PLC相关元器件如触摸屏(HMI)、交换机等工控产品,如…

【AI论文】推理语言模型的强化学习熵机制

摘要&#xff1a;本文旨在克服将强化学习扩展到使用 LLM 进行推理的主要障碍&#xff0c;即策略熵的崩溃。 这种现象在没有熵干预的RL运行中一直存在&#xff0c;其中策略熵在早期训练阶段急剧下降&#xff0c;这种探索能力的减弱总是伴随着策略性能的饱和。 在实践中&#xff…

手动删除网页上的禁止复制事件

以Edge浏览器为环境、以网络上一个文档为例。 右击页面&#xff0c;打开【检查】工具。选择元素&#xff0c;打开【事件侦听器】&#xff1a; 展开copy&#xff0c;删除里面的事件&#xff1a; 选中文字&#xff0c;进行复制

element级联地址选择器

一、实现过程总览 组件替换&#xff1a;将原有的输入框&#xff08;el-input&#xff09;替换为级联选择器&#xff08;el-cascader&#xff09;&#xff0c;并配置基础属性。数据适配&#xff1a;引入 JSON 地址数据&#xff0c;通过cascaderProps映射数据字段&#xff08;如…

【leetcode-两数之和】

一、题目 二、题解 &#xff08;1&#xff09;双指针 这里要注意j<length的 //声明两个指针int length nums.length;for (int i 0; i < length; i) {for (int j i 1; j<length;j){if (j<length && nums[i]nums[j]target){return new int[]{i,j};}}}r…

CLion社区免费后,使用CLion开发STM32相关工具资源汇总与入门教程

Clion下载与配置 Clion推出社区免费&#xff0c;就是需要注册一个账号使用&#xff0c;大家就不用去找破解版版本了&#xff0c;jetbrains家的IDEA用过的都说好&#xff0c;这里嵌入式领域也推荐使用。 CLion官网下载地址 安装没有什么特别&#xff0c;下一步就好。 启动登录…

Rust 学习笔记:关于 Cargo 的练习题

Rust 学习笔记&#xff1a;关于 Cargo 的练习题 Rust 学习笔记&#xff1a;关于 Cargo 的练习题问题一问题二问题三问题四问题五问题六问题七 Rust 学习笔记&#xff1a;关于 Cargo 的练习题 参考视频&#xff1a; https://www.bilibili.com/video/BV1xjAaeAEUzhttps://www.b…

高速收发器

一、高速收发器 1.FPGA高速收发器&#xff1a;GTP,GTX,GTH,GTZ 2.每个Quad有4对高速收发器GT(4个TX和4个RX)和一个COmmon 3.走差分&#xff0c;提高抗干扰性 4.CPLL是每个lane私有的&#xff0c;QPLL是整个Quad的所有通道共享的 5.每个MGT的bank有两对差分参考时钟 6.CPLL的时钟…

Rust 变量与可变性

文章目录 变量与可变性常量遮蔽&#xff08;Shadowing&#xff09; 变量与可变性 Rust中变量默认是不可变的&#xff0c;这是 Rust 鼓励你编写更安全、易于并发代码的众多方式之一。不过&#xff0c;你仍然可以选择让变量可变。让我们来探讨 Rust 为什么鼓励你优先使用不可变性…

sourcetree无法获取远程所有的tag

2025年5月29日11:30:17 sourcetree widnwos v3.4.23版本 突然发现线上的代码库里有很多新打的tag&#xff0c;但是sourcetree死活无法拉去所有的tag&#xff0c;尝试卸载重新安装也不行&#xff0c;全网也找了还是不知道&#xff0c;但是mac版本好像没有这个问题 方法1&…

《深度探索C++对象模型》阅读笔记(完整版)

《深度探索C对象模型》阅读笔记&#xff08;完整版&#xff09; 文章目录 《深度探索C对象模型》阅读笔记&#xff08;完整版&#xff09;1. 关于对象&#xff08;Object Lessons&#xff09;1.1 C对象模型&#xff08;The C Object Model&#xff09;1.1.1 语言中的对象模型1.…

从Docker拉取镜像一直失败超时解决办法

项目场景&#xff1a; 在ubuntu中&#xff0c;使用docker拉去镜像时&#xff0c;一直超时&#xff0c;拉去失败。 问题描述 原因分析&#xff1a; 国外服务器网络不好导致。 解决方案&#xff1a; 解决方案1 设置国内源 我这边测试&#xff0c;更改以后仍然失败 阿里云提供…

KONG根据请求参数限流

背景 价格接口 /search 同时支持缓存查价和实时查价&#xff0c;主要通过searchType字段区分这两种请求。 searchType 为空时为缓存查价&#xff0c;QPS很高。searchType 不为空时为实时查价&#xff0c;但QPS远低于普通查价。 如果直接对该接口限流&#xff0c;当流量波动超…

通俗易懂解析:@ComponentScan 与 @MapperScan 的异同与用法

在 Spring 和 MyBatis 集成开发中&#xff0c;ComponentScan 和 MapperScan 是两个核心注解&#xff0c;但它们的用途和工作机制截然不同。本文将通过通俗的语言和示例代码&#xff0c;带您轻松掌握它们的区别和使用方法。 一、基础概念 ComponentScan&#xff1a;Spring 的“通…

39. 自动化异步测试开发之编写异步业务函数、测试函数和测试类(函数写法)

39. 自动化异步测试开发之编写异步业务函数、测试函数和测试类&#xff08;函数写法&#xff09; 一、异步业务函数解析 1.1 页面导航函数 async def get(async_driver, url: str http://secure.smartbearsoftware.com/samples/testcomplete12/WebOrders/Login.aspx):await…

Qt 无边框窗口实现拖动与窗口控制(最小化/最大化/关闭)

在 Qt 中&#xff0c;使用 Qt::FramelessWindowHint 可以创建无边框窗口&#xff0c;但这样会导致窗口无法拖动&#xff0c;并且系统默认的标题栏按钮&#xff08;最小化、最大化、关闭&#xff09;也会消失。本文将介绍如何实现无边框窗口的鼠标拖动功能&#xff0c;并添加自定…

Linux中的System V通信标准-共享内存、消息队列以及信号量

在Linux系统中&#xff0c;System V IPC&#xff08;Inter-Process Communication&#xff09;提供了一系列进程间通信的机制&#xff0c;包括共享内存、消息队列和信号量。这些机制在系统中发挥了重要作用&#xff0c;帮助进程之间进行数据交换和同步。本文将详细介绍这些机制…