【C++】C++11新特性——右值引用
文章目录
- 一、左值引用、 右值引用
- 1.1 左值与右值
- 1.2 左值引用
- 1.3 右值引用
- 二、右值引用的意义
- 三、移动语句
- 3.1 移动构造
- 3.2 移动赋值
- 3.3 总结
- 四、move问题
- 五、完美转发
- 5.1 万能引用与折叠
- 5.2 完美转发std::forward
一、左值引用、 右值引用
1.1 左值与右值
我们经常能听到左值和右值,那么我们怎么判断左值和右值呢?
C++中还有一个被广泛认同的说法, 那就是可以取地址的、 有名字的就是左值, 反之,不能取地址的、 没有名字的就是右值。
左值可以出现在左边和右边,而右值不能出现在左边。
举个例子:
a
是个左值a+b
就是个右值
因为可以对a
取地址:&a
但是不能对a + b
取地址:&(a + b)
左值和右值不是个变量,而是个数据表达式。
左值一般有:变量名或解引用的指针
右值一般有:字面常量、表达式返回值,函数返回值
1.2 左值引用
左值引用就是给左值起别名。
int main()
{// 左值int a = 0;int* b = nullptr;const int c = 1;const int* d = nullptr;// 左值引用int& ra = a;int*& rb = b;const int& rc = c;const int*& rd = d;// 左值引用不能引用右值,但const左值引用可以//int& e = 10;//int& f = (a + c);const int& e = 10;const int& f = (a + c);return 0;
}
1.3 右值引用
右值引用只能引用右值,不能引用左值。
但是右值引用可以move以后的左值,move可以把ret变成一个将亡值(右值)。
int main()
{// 只能引用右值int&& a = 10;a++;// 右值引用后变左值int ret = 0;//int&& b = ret;// 不能引用左值// 可以引用move后的左值int&& b = std::move(ret);return 0;
}
const引用既可以引用左值也可以引用右值。
二、右值引用的意义
我们以前使用的引用基本上都是左值引用(函数传参,函数传返回值),左值引用的作用主要用来减少拷贝。
但是左值引用并没有彻底的解决:传返回值的时候,如果是一个局部变量,我们就无法引用返回了。
template <class T>
T& fun()
{T a;return a;
}
这里出了作用域后对象自动销毁,所以不能传递回去造成越界访问。
为了解决这种情况,我们就需要右值引用,接下来用我们之前写过的string类举例子,方便观察。
namespace yyh
{class string{public:typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}string(const char* str = ""):_size(strlen(str)), _capacity(_size){_str = new char[_capacity + 1];strcpy(_str, str);}void swap(string& s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}// 拷贝构造string(const string& s){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(){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){push_back(ch);return *this;}const char* c_str() const{return _str;}private:char* _str = nullptr;size_t _size = 0;size_t _capacity = 0;};string Get(){string s = "123456";return s;}
}int main()
{yyh::string ret = yyh::Get();return 0;
}
这里本来应该是Get返回的时候先拷贝一个临时对象,再用这个临时对象拷贝给ret,但是编译器经过优化可以直接拷贝给ret,只调用一次拷贝构造。
不经过优化:
如果对象较小就放到寄存器,如果较大就在两个栈帧之间创建一个栈帧压返回值。
但是就算经过了优化也还是要拷贝,如果要返回的对象非常大,就会有大消耗。
三、移动语句
3.1 移动构造
右值又分为纯右值和将亡值
纯右值:内置类型的表达式的值。
将亡值:自定义类型表达式的值。
对于一个将亡值,与其让它消失,还不如直接把它的值交换走,换给有需要的对象。
所以我们可以写一个拷贝构造的重载。
// 拷贝构造
string(const string& s)
{cout << "string(const string& s) -- 深拷贝" << endl;string tmp(s._str);swap(tmp);
}// 移动构造
string(string&& s)
{cout << "string(string&& s) -- 移动拷贝" << endl;swap(s);
}
这里如果是左值就会匹配拷贝构造,如果是右值就会匹配移动构造。
结果:
3.2 移动赋值
上边解决了拷贝的问题,接下来解决赋值问题。
这里会调用string(const string& s)
是因为operator=
使用的是现代写法。
// 赋值重载
string& operator=(const string& s)
{cout << "string& operator=(string& s) -- 深拷贝" << endl;string tmp(s);swap(tmp);return *this;
}// 移动赋值
string& operator=(string&& s)
{cout << "string& operator=(string&& s) -- 移动赋值" << endl;swap(s);return *this;
}
3.3 总结
左值引用和右值引用减少拷贝的方式不太一样。
左值引用是直接取别名。
右值引用是使用移动构造和移动拷贝间接实现,可以把要返回的临时对象看作将亡值,进行资源转移。
右值引用主要解决的是对象传值返回拷贝问题。也可以解决容器插入数据的拷贝问题(如果插入的是右值就不需要深拷贝)。
四、move问题
我们知道move可以把一个左值变成右值,但是有可能会出现问题
int main()
{yyh::string s1("aaaaa");yyh::string s2(std::move(s1));return 0;
}
移动构造把资源转移后原来的对象的资源就消失了。
void fun2(int&& i)
{cout << "fun2(int&& i)" << endl;
}void fun2(int& i)
{cout << "fun2(int& i)" << endl;
}void fun1(int&& i)
{cout << "fun1(int&& i)" << endl;fun2(i);
}int main()
{fun1(1);return 0;
}
这里为什么在fun1函数中没有调用void fun2(int&& i)
呢?
因为右值引用后就会变成左值,而左值就会对应调用void fun2(int& i)
。
如果我们想让fun1调用void fun2(int&& i)
:
void fun1(int&& i)
{cout << "fun1(int&& i)" << endl;fun2(std::move(i));
}
但是如果有多层嵌套函数就会频繁使用move,所以C++11引入新语法完美转发std::forward
五、完美转发
5.1 万能引用与折叠
上面我们区分左值和右值是通过两个函数的参数来区分。
在普通函数里右值不能引用左值:
void fun1(int&& i)
{cout << "fun1(int&& i)" << endl;
}int main()
{int a = 1;//fun1(a);// 右值不能引用左值return 0;
}
所以C++11提供了万能引用:
// 万能引用
template <class T>
void fun(T&& i)
{cout << "fun(int& i)" << endl;
}int main()
{int a = 1;fun(a);// 左值fun(std::move(a));// 右值const int b = 1;fun(b);// const左值fun(std::move(b));// const右值return 0;
}
这样就可以既引用左值/const左值 也可以引用右值/const右值。
完美转发是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数。
实例化出以下类型的不同函数:
5.2 完美转发std::forward
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }// 万能引用
template <class T>
void fun(T&& i)
{Fun(i);
}int main()
{int a = 1;fun(a);// 左值fun(std::move(a));// 右值const int b = 1;fun(b);// const左值fun(std::move(b));// const右值return 0;
}
我们可以看出这个fun函数把所有的全部转化为左值,不是我们想要的。
所以就有了完美转发std::forward
完美转发适用于这样的场景: 需要将一组参数原封不动的传递给另一个函数。 而且原封不动传递的不仅仅是参数的数值,还包括:左值/右值,const/非const这些性质。
// 万能引用
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }// 万能引用
template <class T>
void fun(T&& i)
{// 完美转发,保持原本属性Fun(std::forward<T>(i));
}int main()
{int a = 1;fun(a);// 左值fun(std::move(a));// 右值const int b = 1;fun(b);// const左值fun(std::move(b));// const右值return 0;
}
相关文章:

【C++】C++11新特性——右值引用
文章目录一、左值引用、 右值引用1.1 左值与右值1.2 左值引用1.3 右值引用二、右值引用的意义三、移动语句3.1 移动构造3.2 移动赋值3.3 总结四、move问题五、完美转发5.1 万能引用与折叠5.2 完美转发std::forward一、左值引用、 右值引用 1.1 左值与右值 我们经常能听到左值…...
C#基础教程21 正则表达式
文章目录 简介正则表达式语法字符集元字符转义字符量词贪婪匹配和非贪婪匹配正则表达式类Regex类Match方法Matches方法简介 正则表达式是一种描述字符串模式的语言,它可以用来匹配、查找、替换字符串中的模式。在C#中,我们可以使用System.Text.RegularExpressions命名空间下的…...

聚观早报|谷歌发布最大视觉语言模型;王兴投资王慧文ChatGPT项目
今日要闻:谷歌发布全球最大视觉语言模型;马斯克预计Twitter下季度现金流转正;王兴投资王慧文ChatGPT项目;美国拟明年 11 月开展载人绕月飞行;慧与科技宣布收购Athonet谷歌发布全球最大视觉语言模型 近日,来…...

java Spring5 xml配置文件方式实现声明式事务
在java Spring5通过声明式事务(注解方式)完成一个简单的事务操作中 我们通过注解方式完成了一个事务操作 那么 下面 我还是讲一下 基于xml实现声明式事务的操作 其实在开发过程中 大家肯定都喜欢用注解 因为他方便 这篇文章中的xml方式 大家做个了解就好 还是 我们的这张表 记…...

常用存储芯片-笔记本上固态硬盘PTS11系列推荐
在存储领域中,除了存储颗粒之外,还有一种极其重要的芯片:存储控制芯片。存储控制芯片是CPU与存储器之间数据交换的中介,决定了存储器最大容量、存取速度等多个重要参数。特别是在AI、5G、自动驾驶时代,对于数据处理及存…...

【AI绘图学习笔记】奇异值分解(SVD)、主成分分析(PCA)
这节的内容需要一些线性代数基础知识,如果你没听懂本文在讲什么,强烈建议你学习【官方双语/合集】线性代数的本质 - 系列合集 文章目录奇异值分解线性变换特征值和特征向量的几何意义什么是奇异值分解?公式推导SVD推广到任意大小矩阵如何求SV…...
【设计模式】模板方法模式和门面模式
模板方法模式和门面模式模板方法模式代码示例门面模式代码示例门面模式的应用场景模板方法模式 模板方法模式非常简单,就是定义了一个固定的公共流程,整个流程有哪些步骤是事先定义好的,具体的步骤则交由子类去实现。属于行为型设计模式。 简…...
Kubernetes未来十年的四大发展趋势
作者:李翔 跟大家已经感受到的一样,Kubernetes已经成为了云计算领域最具统治力的平台,成为了云原生开发的绝对标准,而伴随Kubernetes诞生的CNCF (Cloud Native Computing Foundation) 也因此成为了业界影响力巨大的组织。在成为云…...
一、sql 基础知识、函数和子查询
MySQL 是一种流行的关系型数据库管理系统,使用 SQL 语言进行数据管理和操作。在 MySQL 中,常用的语句包括 SELECT 查询语句、WHERE 条件语句、算术表达式、函数、聚合函数、自定义函数、逻辑表达式、子查询和连接。这些语句可以帮助用户快速地进行数据查…...
产品射频认证笔记
文章目录1. 射频监管认证的目的:1.1 确保 RF 产品在其预期环境中按预期运行1.2 确保射频产品不会干扰其他电子或射频设备2. 射频认证地区规范3. FCC简介4. FCC认证需要准备的内容:5. 射频监管测量会话期间测量以下射频属性:6. 调整射频参数6.…...

做了个springboot接口参数解密的工具,我给它命名为万能钥匙(已上传maven中央仓库,附详细使用说明)
前言:之前工作中做过两个功能,就是之前写的这两篇博客,最近几天有个想法,给它做成一个springboot的start启动器,直接引入依赖,写好配置就能用了 springboot使用自定义注解实现接口参数解密,普通…...

【Flutter从入门到入坑】Flutter 知识体系
学习 Flutter 需要掌握哪些知识? 终端设备越来越碎片化,需要支持的操作系统越来越多,从研发效率和维护成本综合考虑,跨平台开发一定是未来大前端的趋势,我们应该拥抱变化。而 Flutter 提供了一套彻底的移动跨平台方案…...

顺序表的基本操作
目录 一.什么是顺序表 二.顺序表的基本操作 1.初始化 2.增容 3.尾插 4.头插 5.尾删 6.头删 7.指定位置插入 8.指定位置删除 9.打印 10.查找 11.销毁 一.什么是顺序表 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组…...

设计模式——创建型模型——单列模式(8种实现)
前言: 👏作者简介:我是笑霸final,一名热爱技术的在校学生。 📝个人主页:个人主页1 || 笑霸final的主页2 📕系列专栏:计算机基础专栏 📧如果文章知识点有错误的地方&#…...
【软考中级】软件设计师笔记
计算机系统的性能一般包括两个方面:一方面是它的可用性,也就是计算机系统能正常工作的时间,其指标可以是能够持续工作的时间长度,也可以是在一段时间内,能正常工作的时间所占的百分比 另一方面是处理能力,又…...
包教包会的ES6
自学参考:http://es6.ruanyifeng.com/ 一、ECMAScript 6 简介 ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大…...

python学习——【第四弹】
前言 上一篇文章 python学习——【第三弹】 中学习了python中的流程控制语句,这篇文章我们接着学习python中的序列。先给大家介绍不可变序列 字符串和可变序列 列表,下一篇文章接着补充元组,集合和字典。 序列 指的是一块可以存放多个值的…...

Web3中文|无聊猿Otherside元宇宙启动第二次旅行
3月9日消息,无聊猿Bored Ape Yacht Club母公司Yuga Labs公布了其Otherside元宇宙游戏平台第二次测试的最新细节。Yuga Labs公司称,“第二次旅行”将于3月25日举行,由四位Otherside团队长带领完成近两小时的游戏故事。本次旅行对Otherdeed NFT…...
SpringCloud-7_OpenFeign服务调用
OpenFeign介绍OpenFeign是什么1.OpenFeign是个声明式WebService客户端,使用OpenFeign让编写Web Service客户端更简单2.它的使用方法是定义一个服务接口然后在上面添加注解3.OpenFeign也支持可拔插式的编码器和解码器4.Spring Cloud对OpenFeign进行了封装使其支持了S…...
解决docker容器之间网络互通
docker容器之间相互访问 1.查看当前的网络 Copy [roothost ~]# docker network ls NETWORK ID NAME DRIVER SCOPE 3dd4643bb158 bridge bridge local 748b765aca52 host host …...

使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练
前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1):从基础到实战的深度解析-CSDN博客,但实际面试中,企业更关注候选人对复杂场景的应对能力(如多设备并发扫描、低功耗与高发现率的平衡)和前沿技术的…...

【2025年】解决Burpsuite抓不到https包的问题
环境:windows11 burpsuite:2025.5 在抓取https网站时,burpsuite抓取不到https数据包,只显示: 解决该问题只需如下三个步骤: 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
unix/linux,sudo,其发展历程详细时间线、由来、历史背景
sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...

Springboot社区养老保险系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...

中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...

代码规范和架构【立芯理论一】(2025.06.08)
1、代码规范的目标 代码简洁精炼、美观,可持续性好高效率高复用,可移植性好高内聚,低耦合没有冗余规范性,代码有规可循,可以看出自己当时的思考过程特殊排版,特殊语法,特殊指令,必须…...
探索Selenium:自动化测试的神奇钥匙
目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...

【深度学习新浪潮】什么是credit assignment problem?
Credit Assignment Problem(信用分配问题) 是机器学习,尤其是强化学习(RL)中的核心挑战之一,指的是如何将最终的奖励或惩罚准确地分配给导致该结果的各个中间动作或决策。在序列决策任务中,智能体执行一系列动作后获得一个最终奖励,但每个动作对最终结果的贡献程度往往…...