新C++(14):移动语义与右值引用
当你在学习语言的时候,是否经常听到过一种说法,"="左边的叫做左值,"="右边的叫做右值。这句话对吗?从某种意义上来说,这句话只是说对了一部分。
---前言
一、什么是左右值?
通常认为:
左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋
值(使用空间),左值可以出现赋值符号的左边,也可以出现在等号右边。
右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值等等。右值可以出现在赋值符号的右边,但是不能出现在赋值符号的左边,右值不能取地址。
//x \ y 都是左值 都可以取地址double x = 1.1, y = 2.2;int a = 10,b = 20;//以下都是右值 都不用取地址10;x + y;func();
二、左右值引用
(1)左值引用
type& x;
在我们学习引用的时候,一定会和C语言的指针联系到一起。我们来看看下面的swap代码吧。
void SwapByPtr(int* a,int* b)
{int tmp = *a;*a = *b;*b = tmp;
}void SwapByVal(int a, int b)
{int tmp = a;a = b;b = a;
}
结果我想你一定知道的!函数传值与函数传地址是不同的!一个是一份拷贝,一个是记录的地址,可以访问原变量。
但是我们知道,指针是有它的缺陷,如果不是一位资深程序员,甚至你是,也得对指针的使用报以"敬畏之心"。因此,在C++中引入了新的语法,"引用"。虽然它底层仍然是用指针实现的,但是却比指针用起来更加方便。
void SwapByRef(int& a,int& b)
{int tmp = a;a = b;b = a;
}
(2)右值引用
我们时常说"引用","引用",其实都是"左值引用"。为了区别左值引用呢,右值引用的语法格式上是这样的。
type &&;
int a = 10;int& ra = a; //左值引用int&& rra = 10; //右值引用
(3)左右值引用的特性
左值引用:
①只能引用左值,不能引用右值
②但是const左值引用 可以引用右值也可以引用左值
int a = 10;int& ra = a; //只能引用左值int& rb1 = 10; //不能引用右值 ×//既可以引用左值、也可以引用右值const int& rb2 = a;const int& rb2 = 10;
右值引用:
①右值引用只能引用右值,不能引用左值
②标准库中提供move()函数,可以将一个左值变为右值
int a = 10;int&& rra1 = 10; //只能引用右值 int&& rra2 = a; //不能引用左值 ×//move后可以 a变成了右值int&& rra3 = std::move(a);
右值不能取地址,但是右值引用能够取地址!!
右值当然没有地址,但是我们给右值取引用时,那么这个右值引用就该有它的地址,并且可以对它引用的对象进行修改。如果你不想允许让对右值引用的值发生改变,请给它+"const"吧。
为什么这么设计呢?这和右值引用的场景有关,也就是我们之后要细讲的。
当然,这很符合我们的预期。
三、左右值引用的应用场景
也许你会疑问,已经有了左值引用,为什么还需要右值引用呢?右值引用一定有它存在必要的场景。在此之前,我们就先来列举列举左值引用的使用场景吧。
左值引用场景:
①函数传参防拷贝。
②函数返回值 引用返回。
//函数传参防拷贝
vector<int>& Func(vector<int>& ret)
{ret.push_back(1);//...//函数引用返回值return ret;
}
当要进行左值引用返回时,唯一一个条件时,该对象出了作用域仍然存在!那如果该对象就是在函数体内创建的,出了作用域它就会销毁,但其拷贝的代价又很大。遇到这样的情况,我们应该怎么处理呢?
(1)移动赋值与移动构造
我们首先实现一个to_string的函数,用来将一个数字,转换为自定义字符串。
//to_string函数string to_string(int value){bool flag = true;if (value < 0){flag = false;value = 0 - value;}dy::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;}
//自定义string 类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);}// s1.swap(s2)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)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; // 不包含最后做标识的\0};
我们此时用一个整数,使用to_string函数,得到一个自定义string类型。
int main()
{int x = 1234;dy::string ret = dy::to_string(x);return 0;
}
但是,我们为了一个在to_string函数类,一个出作用域就会销毁的对象,为了得到它其中的资源,就得付出"深拷贝"一份的代价,未免有些太大。
如果仅仅是拷贝内置类型来说,那么微乎其微,但如果深拷贝对象是map、set呢?也许你仅仅只需要得到这个即将销毁对象的"根节点"即可,而非是在return返回时,让该对象拷贝构造临时对象而付出巨大代价。
秉持这样的想法,我们为该自定义类设计一个新的拷贝构造函数。
//移动赋值与移动构造string(string&& s){cout << "string(string&& s): 移动构造" << endl;swap(s);}string& operator=(string&& s){cout << "string& operator=(string s) 移动赋值" << endl;swap(s);return *this;}
我们为该类增加这两个函数,并再次运行相同的代码。
这是为什么??该对象的"拷贝"没有选择去调用"深拷贝"?那么,我们不得不搞懂以下的三个问题!
能够搞懂上述的问题,我们也就能够预知编译器会选择怎样做。
那如果是以下这样的调用,会打印出什么呢?
int main()
{ dy::string ret2;ret2 = dy::to_string(123);return 0;
}
小结:
左值引用与右值引用减少拷贝的方式是不一样的:
左值引用是直接起作用的,就是给一个变量取别名。
右值引用是间接起作用的,利用移动构造、移动赋值实现的是一种资源的转移。而被转移的资源也叫做"将亡值"。也就是出了这个作用域,就会销毁的对象。
四、左右值引用的其他应用
(1)完美转发
在前文已经提到过,一旦给右值取别名时,那么该右值引用名义上虽然是右值的别名,但本质是一个可以取地址、甚至可以改变的左值。我们来看看如下的代码。
void Func(int& x)
{cout << "左值引用" << endl;
}void Func(int&& x)
{cout << "右值引用" << endl;
}void GetFunc(int&& x)
{Func(x);
}int main()
{int a = 10;GetFunc(10);return 0;
}
唔,我们分别重载了两个函数Func,一个是用来接收左值引用的、一个是来接收右值引用的,我们传进来的是一个右值10,那么很显然调用后打印的是 "右值引用"。
当右值引用作为参数时,虽然名义上接收的是右值,但是向下传递时,已经改变为了左值。但是我们就想让它保持原有的属性。
C++库中给提供了一个函数转发
std::forward<type>();
我们也就可以看到如我们的预期结果。
(2)万能引用
函数参数有左值引用、也有右值引用,C++中也有模板,那是否模板也有模板左值引用与模板右值引用呢? 是的!
template<class T>
void PerfectFunc(T& x)
{Func(x);
}
template<class T>
void PerfectFunc(const T& x)
{Func(x);
}
但其实这都用得不多。因为接下来的操作可能会惊掉你的下把。
template<class T>
void PerfectFunc(T&& x)
{Func(x);
}
这什么鬼???
在有模板的情况下;
template<class T>
void Func(T&& ..);
就叫做 "万能引用!"
当然,如果你没好好阅读上文,你可能还会惊奇,为什么只会调用左值引用与const左值引用。我们只需要让向下传入的值保持原属性即可。
由此可见,我们能万能引用的情况下,肯定不会去选择"T&"这单调的左值引用参数。
总结:
①左右值区分的最根本方法是,能否取地址,能否使用它的空间。
②左值引用只能引用左值,右值引用只能引用右值。但是const 左值引用可以引用左值 也可以引用右值。
③右值一定没有地址并且不能修改,但是右值引用有它自己的地址,非const可以进行修改。
④左值引用的防拷贝方式更加直接显著。右值引用防拷贝的方式是间接的,也叫"资源转移"。
⑤std::move()可以将一个左值变为右值。std::forward<T>()能保持参数的原属性。
本篇到此结束,感谢你的阅读。
祝你好运,向阳而生~
相关文章:
新C++(14):移动语义与右值引用
当你在学习语言的时候,是否经常听到过一种说法,""左边的叫做左值,""右边的叫做右值。这句话对吗?从某种意义上来说,这句话只是说对了一部分。---前言一、什么是左右值?通常认为:左值是一个表示数据的表达式(…...
TCP相关概念
目录 一.滑动窗口 1.1概念 1.2滑动窗口存在的意义 1.3 滑动窗口的大小变化 1.4丢包问题 二.拥塞控制 三.延迟应答 四.捎带应答 五.面向字节流 六.粘包问题 七.TIME_WAIT状态 八.listen第2个参数 九.TCP总结 一.滑动窗口 1.1概念 概念:双方在进行通信时&a…...
MySQL锁篇
MySQL锁篇 一、一条update语句 我们的故事继续发展,我们还是使用t这个表: CREATE TABLE t (id INT PRIMARY KEY,c VARCHAR(100) ) EngineInnoDB CHARSETutf8;现在表里的数据就是这样的: mysql> SELECT * FROM t; —------- | id | c | —…...
SWF (Simple Workflow Service)简介
Amazon Simple Workflow Service (Amazon SWF) 提供了给应用程序异步、分布式处理的流程工具。 SWF可以用在媒体处理、网站应用程序后端、商业流程、数据分析和一系列定义好的任务上。 举个例子,下图表明了一个电商网站的工作流程,其中涉及了程序执行的…...
java(Class 常用方法 获取Class对象六种方式 动态和静态加载 类加载流程)
ClassClass常用方法获取Class对象六种方式哪些类型有Class对象动态和静态加载类加载流程加载阶段连接阶段连接阶段-验证连接阶段-准备连接阶段-解析初始化阶段获取类结构信息Class常用方法 第一步:创建一个实体类 public class Car {public String brand "宝…...
【数据结构】线性表和顺序表
Yan-英杰的主页 悟已往之不谏 知来者之可追 目录 1.线性表 2.顺序表 2.1 静态顺序表 2.2 动态顺序表 2.3移除元素 1.线性表 线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线…...
Ubuntu数据库安装(mysql)
##1.下载mysql-apt-config_0.8.22-1_all.deb并且安装 wget https://dev.mysql.com/get/mysql-apt-config_0.8.22-1_all.deb sudo dpkg -i mysql-apt-config_0.8.22-1_all.deb##2.更新apt-updata sudo apt update##3.如果出现如下图情况执行以下命令 [外链图片转存失败,源站可…...
MyBatis-Plus的入门学习
MyBatis-Plus入门学习简介特性快速开始MyBatis-Plus的注解详解Tableld主键生成策略1、数据库自动增长 AUTO2、UUID3、Redis生成id4、MP主键自动生成TableNameTableField自动填充测试方法:update乐观锁select查所有根据id查多个id批量查询简单条件查询(通…...
华为OD机试题 - 内存池(JavaScript)
更多题库,搜索引擎搜 梦想橡皮擦华为OD 👑👑👑 更多华为OD题库,搜 梦想橡皮擦 华为OD 👑👑👑 更多华为机考题库,搜 梦想橡皮擦华为OD 👑👑👑 华为OD机试题 最近更新的博客使用说明本篇题解:内存池题目输入输出示例一输入输出说明Code解题思路版权说明华为…...
数据库索引原理
数据库索引的作用是做数据的快速检索,而快速检索实现的本质是数据结构。像二叉树、红黑树、AVL树、B树、B树、哈希等数据结构都可以实现索引,但其中B树效率最高。MySQL数据库索引使用的是B树。二叉树:二叉树中,左子树比根节点小&a…...
字符函数和字符串函数详解(1)
目录前言strlen函数strlensizeofstrcpy函数strcat函数strcmp函数总结前言 最近要调整状态,写的文章质量不佳让大家失望,我现在也在反思我在做什么,我会什么,我学了什么。等我想明白的那天,我一定能跟大家顶峰相见的&a…...
【数据分析:工具篇】NumPy(1)NumPy介绍
【数据分析:工具篇】NumPy(1)NumPy介绍NumPy介绍NumPy的特点数组的基本操作创建数组索引和切片数组运算NumPy介绍 NumPy(Numerical Python)是Python的一个开源的科学计算库,它主要用于处理大规模的多维数组…...
mysql时区问题
设置mysql容器时间与服务器时间一致 问题背景: 今天测试发现一个问题,时间不一致,当工单入库时,其创建时间和更新时间应该是一样的,即使不一样最多只会错几秒的时间;实际上两个时间相差的大概8小时&#…...
磨金石教育摄影技能干货分享|高邮湖上观花海
江苏高邮,说到这里所有人能想到的,就是那烟波浩渺的高邮湖。高邮在旅游方面并不出名,但是这里的自然人文景观绝对不输于其他地方。高邮不止有浩瀚的湖泊,春天的油菜花海同样壮观。春日的午后,与家人相约游玩࿰…...
mysql navicat忘记密码
mysql忘记密码是常用的事情,那么如何解决它呢?1、首先将MySQL的服务关闭,两种方法:(1)打开命令行cmd输入net stop mysql命令即可关闭MySQL服务。(2)打开任务管理器,找到服…...
Git的下载、安装、配置、使用、卸载
前言 我是跟着狂神老师学的。该博客仅用于笔记所用。 下面是老师的B站和笔记 B站:https://www.bilibili.com/video/BV1FE411P7B3?p1&vd_source9266cf72b1f398b63abe0aefe358d7d6 笔记:https://mp.weixin.qq.com/s/Bf7uVhGiu47uOELjmC5uXQ 一、准备工…...
【博客631】监控网卡与进程网络IO使用情况
监控进程的网络IO使用情况 1、vnstat 由于 vnstat 依赖于内核提供的信息,因此执行以下命令来验证内核是否提供了 vnStat 所期望的所有信息: # vnstat --testkernel This test will take about 60 seconds. Everything is ok.不带任何参数的 vnstat 将…...
【Leetcode】【简单】35. 搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 示例 1: 输入: nums [1,3,5,6], target 5 输出: 2 示例 2: 输入:…...
sql面试题
mysql优化 优化准则: 建表时:合理选择字段的类型,单表字段数量 sql查询尽量单表操作,避免复杂操作,复杂的多表通过java代码实现 构建复合索引优化,索引尽量可以覆盖主要业务查询 sql避免索引失效 避免大…...
SQL 进阶刷题笔记
SQL 进阶刷题笔记 一、MySQL 进阶 这里主要是 MySQL 刷题相关笔记,方便后面温习和查阅,希望可以帮到大家!!! 题1 请计算每张SQL类别试卷发布后,当天5级以上的用户作答的人数uv和平均分avg_score࿰…...
[网鼎杯 2020 朱雀组]Think Java
SqlDict.java ,其中sql语句处存在sql注入漏洞 package .sqldict;import cn.abc.core.sqldict.Row; import cn.abc.core.sqldict.Table; import java...
AIR32F103(十) 在无系统环境和FreeRTOS环境集成LVGL
目录 AIR32F103(一) 合宙AIR32F103CBT6开发板上手报告AIR32F103(二) Linux环境和LibOpenCM3项目模板AIR32F103(三) Linux环境基于标准外设库的项目模板AIR32F103(四) 27倍频216MHz,CoreMark跑分测试AIR32F103(五) FreeRTOSv202112核心库的集成和示例代码AIR32F103(六) ADC,I2S…...
SpringBoot接口 - 如何统一异常处理
SpringBoot接口如何对异常进行统一封装,并统一返回呢?以上文的参数校验为例,如何优雅的将参数校验的错误信息统一处理并封装返回呢?为什么要优雅的处理异常如果我们不统一的处理异常,经常会在controller层有大量的异常…...
如何使用Python进行数据可视化
数据可视化是一种将数据呈现为图形或图表的技术,它有助于理解和发现数据中的模式和趋势。Python是一种流行的编程语言,有很多库可以帮助我们进行数据可视化。在本文中,我们将介绍使用Python进行数据可视化的基本步骤。 第一步:导…...
vue -- 自定义指令钩子函数补充 自定义过滤器filter参数
自定义指令补充 自定义指令通过钩子函数的形式来实现自定义的功能 这里是几个常用的钩子函数以及它的方法: bind:只调用一次,指令第一次绑定到元素时调用,在这里可以进行一次性的初始化设置。 inserted:被绑定元素插…...
Qt不会操作?Qt原理不知道? | Qt详细讲解
文章目录Qt界面开发必备知识UI界面与控件类型介绍Qt设计器原理控件类型的介绍信号与槽机制处理常用控件创建与设置常见展示型控件创建与设置常见动作型控件创建与设置常见输入型控件创建与设置常见列表控件创建于设置Qt中对象树的介绍项目源码结构刨析.pro.hmain.cpp.cppQt界面…...
LeetCode-面试题 17.05. 字母与数字【前缀和,哈希表】
LeetCode-面试题 17.05. 字母与数字【前缀和,哈希表】题目描述:解题思路一:前缀和。数字为-1,字母为1。我们需要找到的子数组是前缀和之差为0的,例如s[right]-s[left]0,那么s[right]s[left],变为…...
华为OD机试题 - 叠放书籍(JavaScript)| 机考必刷
更多题库,搜索引擎搜 梦想橡皮擦华为OD 👑👑👑 更多华为OD题库,搜 梦想橡皮擦 华为OD 👑👑👑 更多华为机考题库,搜 梦想橡皮擦华为OD 👑👑👑 华为OD机试题 最近更新的博客使用说明本篇题解:叠放书籍题目输入输出示例一输入输出Code解题思路版权说明华为O…...
【数据库概论】第十一章 数据库并发控制
第十一章 并发控制 在多处理机系统中,每个处理机可以运行一个事务,多个处理机可以同时运行多个事务,实现多个事务并行运行,这就是同时并发方式。当多个用户并发存取数据库时会产生多个事务同时存取同一事务的情况,如果…...
Nginx配置实例-反向代理案例二
实现效果:使用nginx反向代理,根据访问的路径跳转到不同端口服务 nginx监听端口为9000, 访问 http://127.0.0.1:9000/edu/ 直接跳转到127.0.0.1:8080 访问 http://127.0.0.1:9000/vod/ 直接跳转到127.0.0.1:8081 一、准备工作 1. 准备两个tom…...
北海教网站建设/北京疫情最新情况
文章目录BT A2DP、AVRCPA2DPAVRCPBT A2DP、AVRCP A2DP A2DP 全名是 Advenced Audio Distribution Profile 蓝牙音频传输模型协议。 A2DP 规定了使用蓝牙非同步传输信道方式,传输高质量音乐文件数据的协议堆栈软件和使用方法,基于该协议就能通过以蓝牙方…...
陕西网站制作公司/淘宝代运营
Platform: RK3399OS: Android 7.1.2Kernel: v4.4.126首先在Init.rc中添加开机服务我们这里是在system/core/rootdir/init.rc中添加组和用户都是root,只执行一次.条件是在开机完成后启动服务-start fbchangeAndroid 5.0 之后Android 系统强化了SELINUX 功能ÿ…...
政府网站旅游栏目建设/网络营销的营销策略
最近,Eclipse(Eclipse-JEE3.5)运行十分缓慢(可能插件安装过多),因此,得到了个机会调优一下,以便提高工作效率下图是未经任何调整eclipse的gc情况(使用jvisualvm命令&…...
手机端 网站 模板/百度一下网页入口
一.选择题1.关于C程序的叙述,错误的说法是(A) C程序总是从主函数开始执行(B) C程序中定义的第一个函数是主函数(C) 在主函数中可以调用其他函数(D) 一个C程序可以包括多个函数2.C语言的基本数据类型包括。(A) char (B) struct (c)…...
临汾网站开发/nba最新交易汇总
一、Kafka 命令行操作 1. Kafka 是要用到zookeeper,那么zookeeper帮Kafka做了什么事呢? ①. 启动zookeeper ②. 从来没启动Kafka的zookeeper节点没有 borkers yexiang@hadoop2:<zookeeper-3.4.10>$ zkCli.sh -server hadoop3:2181 ...[zk: hadoop3:2181(CONNECTED…...
公司网站建设浩森宇特/百度上怎么免费开店
Linux 中C语言getcwd()函数的用法先来看该函数的声明:#includechar *getcwd(char *buf,size_t size);介绍:参数说明:getcwd()会将当前工作目录的绝对路径复制到参数buffer所指的内存空间中,参数size为buf的空间大小。普通的用法会是这样:#def…...