【C++进阶学习】第十一弹——C++11(上)——右值引用和移动语义

前言:

前面我们已经将C++的重点语法讲的大差不差了,但是在C++11版本之后,又出来了很多新的语法,其中有一些作用还是非常大的,今天我们就先来学习其中一个很重要的点——右值引用以及它所扩展的移动定义

目录

一、左值引用和右值引用

左值引用 

右值引用

二、左值引用与右值引用的比较

三、右值引用的使用

移动构造

移动赋值

四、总结


一、左值引用和右值引用

左值引用 

左值引用是最常见的引用类型,通常用于绑定到一个左值。左值是一个具有名称的对象,可以取地址,通常出现在赋值操作符的左边。(简单的说,能取地址的就是左值)

语法:

类型 &引用名 = 左值;

示例:

int a = 10;
int &refA = a;  // refA是一个左值引用,绑定到左值a

特点:

  • 左值引用必须初始化,并且只能绑定到左值。
  • 左值引用可以修改绑定的对象。

右值引用

右值引用是C++11引入的新特性,用于绑定到一个右值。右值是一个临时对象,通常没有名称,不能取地址,通常出现在赋值操作符的右边。(右值不能取地址,比如常量)

语法:

类型 &&引用名 = 右值;

示例:

int &&refB = 20;  // refB是一个右值引用,绑定到右值20

特点:

  • 右值引用必须初始化,并且只能绑定到右值。
  • 右值引用主要用于实现移动语义和完美转发。

有一个需要强调的是,常变量虽然也属于常量,但是它可以取地址,所以它属于左值

二、左值引用与右值引用的比较

左值引用:

1. 左值引用只能引用左值,不能引用右值。
2. 但是const左值引用既可引用左值,也可引用右值
int main()
{// 左值引用只能引用左值,不能引用右值。int a = 10;int& ra1 = a;   // ra为a的别名//int& ra2 = 10;   // 编译失败,因为10是右值// const左值引用既可引用左值,也可引用右值。const int& ra3 = 10;const int& ra4 = a;return 0;
}

右值引用:

1. 右值引用只能右值,不能引用左值。
2. 但是右值引用可以move以后的左值。
int main()
{// 右值引用只能右值,不能引用左值。int&& r1 = 10;// error C2440: “初始化”: 无法从“int”转换为“int &&”// message : 无法将左值绑定到右值引用int a = 10;int&& r2 = a;// 右值引用可以引用move以后的左值int&& r3 = std::move(a);return 0;
}

三、右值引用的使用

在上面我们也已经讲到了,左值引用及可以引用左值,又可以引用右值,那么C++11为什么还要设计右值引用呢?下面我们来看一下原因。

我们借助string类来讲解

先来看一下下面所出现的所有代码,可以先思考看看思考思考

namespace zda
{class string{public:typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}string(const char* str = ""):_size(strlen(str)), _capacity(_size){//cout << "string(char* str)" << endl;_str = new char[_capacity + 1];strcpy(_str, str);}// s1.swap(s2)void swap(string& s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}// 拷贝构造string(const string& s):_str(nullptr){cout << "string(const string& s) -- 深拷贝" << endl;string tmp(s._str);swap(tmp);}// 赋值重载string& operator=(const string& s){cout << "string& operator=(string s) -- 深拷贝" << endl;string tmp(s);swap(tmp);return *this;}// 移动构造string(string&& s)   //右值引用:_str(nullptr), _size(0), _capacity(0){cout << "string(string&& s) -- 移动语义" << endl;swap(s);}// 移动赋值string& operator=(string&& s)    //右值引用{cout << "string& operator=(string&& s) -- 移动语义" << endl;swap(s);return *this;}~string(){delete[] _str;_str = nullptr;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}void push_back(char ch){if (_size >= _capacity){size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newcapacity);}_str[_size] = ch;++_size;_str[_size] = '\0';}//string operator+=(char ch)string& operator+=(char ch){push_back(ch);return *this;}const char* c_str() const{return _str;}private:char* _str;size_t _size;size_t _capacity; // 不包含最后做标识的\0};string to_string(int value){bool flag = true;if (value < 0){flag = false;value = 0 - value;}string str;while (value > 0){int x = value % 10;value /= 10;str += ('0' + x);}if (flag == false){str += '-';}std::reverse(str.begin(), str.end());return str;}
}

左值引用使用场景:

void func1(bit::string s)
{}
void func2(const bit::string& s)
{}
int main()
{bit::string s1("hello world");// func1和func2的调用我们可以看到左值引用做参数减少了拷贝,提高效率的使用场景和价值func1(s1);func2(s1);// string operator+=(char ch) 传值返回存在深拷贝// string& operator+=(char ch) 传左值引用没有拷贝提高了效率s1 += '!';return 0;
}

左值引用短板:

当函数返回对象为临时变量的时候,左值引用就派不上用场了,就只能传值返回,就需要拷贝至少一次(老一点的编译器为两次)

右值引用和移动语义:
对于上面这种问题,我们就可以通过右值引用和移动语义来实现

移动构造

移动构造的本质就是将参数的右值窃取过来,占为己有,这样它就不用再深度拷贝了,所以叫做移动构造
// 移动构造
string(string&& s):_str(nullptr),_size(0),_capacity(0)
{cout << "string(string&& s) -- 移动语义" << endl;swap(s);
}
int main()
{zda::string ret2 = bit::to_string(-1234);return 0;
}

当返回值是右值时,因为移动构造并没有开辟空间进行深拷贝,所以效率就会更高

需要注意的是,当拷贝构造和移动构造同时存在时,编译器默认的也会调用移动构造,因为编译器会默认调用效率更高的函数

移动赋值

// 移动赋值
string& operator=(string&& s)
{
cout << "string& operator=(string&& s) -- 移动语义" << endl;
swap(s);
return *this;
}
int main()
{zda::string ret1;ret1 = zda::to_string(1234);return 0;
}// 运行结果:
// string(string&& s) -- 移动语义
// string& operator=(string&& s) -- 移动语义

这里运行后发现,调用了一次移动构造和一次移动赋值,因为这里的ret1是一个已经存在的对象,用它来接受函数返回值的时候编译器就无法再优化了,所以会在移动构造后创建一个临时变量,且这个临时变量会被编译器识别为右值,从而调用移动赋值

四、总结

上面我们就简单的先提了一下右值引用的应用:移动语义,下一篇我们再重点讲解一下右值引用的另一个重点语法:完美挥发

感谢各位大佬观看,创作不易,还请各位大佬点赞支持一下!!!

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

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

相关文章

【IoTDB】363万点/秒写入!IoTDB凭何领跑工业时序数据库赛道?

【作者主页】Francek Chen 【专栏介绍】⌈⌈⌈大数据与数据库应用⌋⌋⌋ 大数据是规模庞大、类型多样且增长迅速的数据集合&#xff0c;需特殊技术处理分析以挖掘价值。数据库作为数据管理的关键工具&#xff0c;具备高效存储、精准查询与安全维护能力。二者紧密结合&#xff0…

IEEE 2025 | 重磅开源!SLAM框架用“法向量+LRU缓存”,将三维重建效率飙升72%!

一、前言 当前研究领域在基于扩散模型的文本到图像生成技术方面取得了显著进展&#xff0c;尤其在视觉条件控制方面。然而&#xff0c;现有方法&#xff08;如ControlNet&#xff09;在组合多个视觉条件时存在明显不足&#xff0c;主要表现为独立控制分支在去噪过程中容易引入…

无人机遥控器教练模式技术要点

一、技术要点1.控制权仲裁机制&#xff1a;核心功能&#xff1a;清晰定义主控权归属逻辑&#xff08;默认为学员&#xff0c;但教练随时可接管&#xff09;。切换方式&#xff1a;通常通过教练遥控器上的物理开关&#xff08;瞬时或锁定型&#xff09;或软件按钮触发。切换逻辑…

【跨服务器的数据自动化下载--安装公钥,免密下载】

跨服务器的数据自动化下载功能介绍&#xff1a;上代码&#xff1a;发现好久没写csdn了&#xff0c;说多了都是泪~~ 以后会更新一些自动化工作的脚本or 小tricks&#xff0c;欢迎交流。分享一个最近在业务上写的较为实用的自动化脚本&#xff0c;可以批量从远端服务器下载指定数…

C++-->stl: list的使用

前言list的认识list是可以在固定时间&#xff08;O&#xff08;1&#xff09;&#xff09;内在任意位置进行插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭代。 2. list的底层是双向链表结构&#xff0c;双向链表中每个元素存储在互不相关的独立节点中&#xff0…

本地WSL部署接入 whisper + ollama qwen3:14b 总结字幕

1. 实现功能 M4-1 接入 whisper ollama qwen3:14b 总结字幕 自动下载视频元数据如果有字幕&#xff0c;只下载字幕使用 ollama 的 qwen3:14b 对字幕内容进行总结 2.运行效果 source /root/anaconda3/bin/activate ytdlp &#x1f50d; 正在提取视频元数据… &#x1f4dd; 正在…

《Linux运维总结:Shell脚本高级特性之变量间接调用》

总结&#xff1a;整理不易&#xff0c;如果对你有帮助&#xff0c;可否点赞关注一下&#xff1f; 更多详细内容请参考&#xff1a;Linux运维实战总结 一、变量间接调用 在Shell脚本中&#xff0c;变量间接调用是一种高级特性&#xff0c;它允许你通过另一个变量的值来动态地访问…

ABP VNext + Akka.NET:高并发处理与分布式计算

ABP VNext Akka.NET&#xff1a;高并发处理与分布式计算 &#x1f680; 用 Actor 模型把高并发写入“分片→串行化”&#xff0c;把锁与竞态压力转回到代码层面的可控顺序处理&#xff1b;依托 Cluster.Sharding 横向扩容&#xff0c;Persistence 宕机可恢复&#xff0c;Strea…

[激光原理与应用-250]:理论 - 几何光学 - 透镜成像的优缺点,以及如克服缺点

透镜成像是光学系统中应用最广泛的技术&#xff0c;其通过折射原理将物体信息转换为图像&#xff0c;但存在像差、环境敏感等固有缺陷。以下是透镜成像的优缺点及针对性改进方案&#xff1a;一、透镜成像的核心优点高效集光能力透镜通过曲面设计将分散光线聚焦到一点&#xff0…

测试匠谈 | AI语音合成之大模型性能优化实践

「测试匠谈」是优测云服务平台倾心打造的内容专栏&#xff0c;汇集腾讯各大产品的顶尖技术大咖&#xff0c;为大家倾囊相授开发测试领域的知识技能与实践&#xff0c;让测试工作变得更加轻松高效。 本期嘉宾介绍 Soren&#xff0c;腾讯TEG技术事业群质量工程师&#xff0c;负责…

用天气预测理解分类算法-从出门看天气到逻辑回归

一、生活中的决策难题&#xff1a;周末郊游的「天气判断」 周末计划郊游时&#xff0c;你是不是总会打开天气预报反复确认&#xff1f;看到 "25℃、微风、无雨" 就兴奋收拾行李&#xff0c;看到 "35℃、暴雨" 就果断取消计划。这个判断过程&#xff0c;其…

HTTPS服务

HTTPS服务 一、常见的端口 http ------ 80 明文 https ------ 443 数据加密 dns ------ 53 ssh ------ 22 telent ------ 23 HTTPS http ssl或者tls &#xff08;安全模式&#xff09; 二、原理&#xff1a; c&#xff08;客户端…

【Android笔记】Android 自定义 TextView 实现垂直渐变字体颜色(支持 XML 配置)

Android 自定义 TextView 实现垂直渐变字体颜色&#xff08;支持 XML 配置&#xff09; 在 Android UI 设计中&#xff0c;字体颜色的渐变效果能让界面看起来更加精致与现代。常见的渐变有从左到右、从上到下等方向&#xff0c;但 Android 的 TextView 默认并不支持垂直渐变。…

CANopen Magic调试软件使用

一、软件安装与硬件连接1.1 系统要求操作系统&#xff1a;Windows 7/10/11 (64位)硬件接口&#xff1a;支持Vector/PEAK/IXXAT等主流CAN卡推荐配置&#xff1a;4GB内存&#xff0c;2GHz以上CPU1.2 安装步骤运行安装包CANopen_Magic_Setup.exe选择安装组件&#xff08;默认全选&…

前端css学习笔记3:伪类选择器与伪元素选择器

本文为个人学习总结&#xff0c;如有谬误欢迎指正。前端知识众多&#xff0c;后续将继续记录其他知识点&#xff01; 目录 前言 一、伪类选择器 1.概念 2.动态选择器&#xff08;用户交互&#xff09; 3.结构伪类 &#xff1a;first-child&#xff1a;选择所有兄弟元素的…

深入探索 PDF 数据提取:PyMuPDF 与 pdfplumber 的对比与实战

在数据处理和分析领域&#xff0c;PDF 文件常常包含丰富的文本、表格和图形信息。然而&#xff0c;从 PDF 中提取这些数据并非易事&#xff0c;尤其是当需要保留格式和颜色信息时。幸运的是&#xff0c;Python 社区提供了多个强大的库来帮助我们完成这项任务&#xff0c;其中最…

Springboot注册过滤器的三种方式(Order 排序)

一、使用 Component Order&#xff08;简单但不够灵活&#xff09; 适用于全局过滤器&#xff0c;无需手动注册&#xff0c;Spring Boot 会自动扫描并注册。 Component Order(1) // 数字越小&#xff0c;优先级越高 public class AuthFilter implements Filter {Autowired /…

电脑硬件详解

前几天我的风扇转的很快&#xff0c;而且cpu占用率很高&#xff0c;然后我在想怎么回事&#xff0c;然后就浅浅研究了一下电脑的硬件。 笔记本主板&#xff1a; 台式机主板&#xff1a; 图1&#xff1a; 图2&#xff1a; 电脑硬件详解 电脑的硬件是组成计算机系统的物理设…

力扣47:全排列Ⅱ

力扣47:全排列Ⅱ题目思路代码题目 给定一个可包含重复数字的序列 nums &#xff0c;按任意顺序 返回所有不重复的全排列。 思路 又是任意顺序和所有不重复的排列&#xff0c;显而易见我们要使用回溯的办法。 首先是回溯的结束条件即新数组的长度等于nums的长度。这道题的难点…

学习笔记091——如何实现web登录时,密码复杂度校验?(后端)

1、创建工具类 /*** 密码复杂度校验* param password 密码*/ public static void validatePassword(String password) {// 至少8位if (password.length() < 8) {throw new IllegalArgumentException("密码长度至少为8位");}// 包含大小写字母if (!password.matche…