C++11右值引用及移动构造

区分左值和右值

在学习c++11的右值引用前,大家肯定会有点陌生什么是右值?什么是左值?现在我先来带大家熟悉一下概念。

左值

可以被取地址,也可被修改(const修饰的除外)

可以出现在等号左边,也可以出现在右边

	//a,b,c均为左值int a = 1;//a出现在等号左边const int b = a;//a出现在等号右边int* c = new int;

 右值

举例:字母常量、表达式的返回值、函数的返回值(不能是左值引用返回)等等。

不可以被取地址,不可被修改

只能出现在等号右边,不能出现在等号左边(因为不可被修改)

	int x = 2, y = 1;//下面常见的右值10;"xxxxxx";x + y;fmax(x, y);

如何区分?

误区:许多小伙伴喜欢看这个值在等号的哪边来区分这个值是左值还是右值,其实是不正确的,正确的区分方法应该是判断这值是否能被取地址! 

左值引用:

左值引用也就是对左值取别名,其实我们之前学习的引用就是左值引用,用符号&来声明,比如:

	//a,b,c均为左值int a = 1;//a出现在等号左边int b = a;//a出现在等号右边int* c = new int;//d,e,f均为左值引用int& d = a;int& e = b;int& f = *c;

这里其实和大家之前学的引用一样,就不过多赘述。

右值引用

 右值引用也就是对右值取别名,用符号&&来声明,比如:

	int x = 2, y = 1;//下面是常见的右值10;"xxxxxx";x + y;fmax(x, y);//下面是常见的右值引用int&& a = 10;string&& b = "xxxxx";int&& c = x + y;int&& d = fmax(x, y);

特别注意:

右值引用本身是左值! 右值引用本身是左值! 右值引用本身是左值! 

也就是说上面代码中的 a,b,c,d均是左值!!!

原因很简单,如果右值引用本身还是右值,那么右值引用将毫无意义,无法修改,进行后续操作。

左值引用及右值引用的意义

正所谓知其然,知其所以然。

想要彻底掌握这两种引用的用法,我们就需先了解这两种引用的出现意义和历史渊源。

现在我带大家从c语言讲起:

C语言的弊端:

大家在学习c++初期时,想必都了解过,c++其实是为了解决c语言的大部分弊端,而衍生出来的新语言,那么引用的出现,究竟是为了解决哪些弊端呢?请看如下代码:

#include<iostream>
using namespace std;
void my_swap(int x,int y)
{int t = x;x = y;y = t;
}
int main()
{int x = 1;int y = 2;cout << "x=" << x << " " << "y=" << y << endl;my_swap(x, y);cout << "x=" << x << " " << "y=" << y << endl;return 0;
}

运行结果:

我实现了一个简单的交换函数,为什么最后的值没有交换?

原因很简单,函数里的x,y是形参,形参只是实参的一份临时拷贝,形参的修改不会影响到实参,那么c语言是如何解决这个问题的呢?

没错,使用指针!

#include<iostream>
using namespace std;
void my_swap(int* x,int* y)
{int t = *x;*x = *y;*y = t;
}
int main()
{int x = 1;int y = 2;cout << "x=" << x << " " << "y=" << y << endl;my_swap(&x, &y);cout << "x=" << x << " " << "y=" << y << endl;return 0;
}

左值引用的诞生

但是在c++看来指针繁琐且难以理解,故c++创造了左值引用来解决这类问题:

#include<iostream>
using namespace std;
void my_swap(int& x,int& y)
{int t = x;x = y;y = t;
}
int main()
{int x = 1;int y = 2;cout << "x=" << x << " " << "y=" << y << endl;my_swap(x, y);cout << "x=" << x << " " << "y=" << y << endl;return 0;
}

左值引用还可作返回值:

using namespace std;
class my_string
{
public:my_string(string str = "xxxxx"){_str = str;}my_string& operator+=(char s){_str.push_back(s);return *this;}
private:string _str;
};
int main()
{my_string s1("test ");my_string s2 = s1 += 'a';return 0;
}

如果不用左值引用返回,直接传值返回,那么返回时还要进行一次拷贝,这样大大降低了效率,左值引用的出现,减少了拷贝,大大提高了效率。

注意:

左值引用作返回值时,返回的自定义类型必须出了作用域(函数体)仍然存在,才可使用,不然就会出现野引用。

所以如果我们在函数体里面创建了一个自定义类型,是不能左值引用返回的,因为这个自定义类型是在函数里面创建的,出了函数体就不存在了。比如(错误示范):

	my_string& operator+(char s){string str1(_str);str1.push_back(s);my_string str2(str1);return str2;}

上面str2出了函数体就释放了,所以不能用左值引用返回。 

到这里不难总结出,左值引用虽然在某些情况,减少了拷贝,大大提高了代码的效率,但是不全面,还是有些场景下会出现不可避免的拷贝问题。

右值引用的诞生

c++11更新后为了弥补左值引用的不足,创造出了右值引用,完全彻底避免了不必要的拷贝,没错就是右值引用返回。

继续看到上面的代码,引入一个新名词:将亡值 也就是str2,顾名思义,将亡值也就是即将消亡的值,正如str2出了函数体后直接释放,那么右值引用又是如何解决这个问题的呢?

#include<iostream>
#include<string>
using namespace std;
class my_string
{
public:my_string(string str = "xxxxx"){_str = str;}void swap(my_string& str){std::swap(_str, str._str);}my_string(my_string&& s)//移动构造{swap(s);}my_string&& operator+(char s){string str1(_str);str1.push_back(s);my_string str2(str1);return move(str2);}
private:string _str;
};

右值引用返回+移动构造,通过上述代码不难看出,移动构造其实就是将即将释放的将亡值str2的资源直接通过swap函数转移出来,大大减少了拷贝。

移动赋值同理。

移动构造及移动赋值特点

移动构造和移动赋值也是类的默认成员函数,一般其它的默认成员函数,都是自己不写,编译器自动生成,但这两个默认构造函数略有不同:

        如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任
意一个。那么编译器会自动生成一个默认移动构造
默认生成的移动构造函数,对于内置类
型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,
        如果实现了就调用移动构造,没有实现就调用拷贝构造。
如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中
的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内
置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋
值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造
完全类似)
        如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。

 关于红字部分原因很简单,一般涉及到深拷贝时,就都要实习那析构,拷贝构造,拷贝赋值,移动构造,移动赋值,所以这些函数差不多是绑定在一块的。

完美转发

模板中的&&万能引用:

注意&&如果出现在模板中,那么它代表的不一定是右值引用,而是万能引用,既可以接受左值,又可以接收右值。

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; }
// std::forward<T>(t)在传参的过程中保持了t的原生类型属性。
template<typename T>
void PerfectForward(T&& t)
{Fun(std::forward<T>(t));
}
int main()
{PerfectForward(10); // 右值int a;PerfectForward(a); // 左值PerfectForward(std::move(a)); // 右值const int b = 8;PerfectForward(b); // const 左值PerfectForward(std::move(b)); // const 右值return 0;
}

运行结果:

注意,如果不加forward,那么如果传的是右值那么t本身会变成左值,因为右值引用本身是左值。

不加forward运行结果: 

 

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

软件架构之开发方法

软件架构之开发方法 第6章&#xff1a;开发方法6.1 软件生命周期6.2 软件开发模型6.2.1 瀑布模型6.2.2 演化模型6.2.3 螺旋模型6.2.4 增量模型6.2.5 构件组装模型 6.3 统一过程6.4 敏捷方法6.4.1 极限编程6.4.2 特征驱动开发6.4.3 Scrum6.4.4 水晶方法6.4.5 其他敏捷方法 6.5 软…

开源模型应用落地-FastAPI-助力模型交互-进阶篇(一)

一、前言 FastAPI 的高级用法可以为开发人员带来许多好处。它能帮助实现更复杂的路由逻辑和参数处理&#xff0c;使应用程序能够处理各种不同的请求场景&#xff0c;提高应用程序的灵活性和可扩展性。 在数据验证和转换方面&#xff0c;高级用法提供了更精细和准确的控制&#…

Java线程的创建·启动和休眠

一.线程的创建和启动 Java中创建线程的两种方式 ◆继承java.lang.Thread类 ◆实现java.lang.Runnable接口 ◆使用线程的步骤 继承Thread类创建线程 ◆自定义线程类继承自Thread类 ◆重写run()方法&#xff0c;编写线程执行体 ◆创建线程对象&#xff0c;调用start()方法启动…

数据驱动制造业升级,免费可视化工具成关键

制造业作为国民经济的支柱产业&#xff0c;正经历着前所未有的变革。数据&#xff0c;作为这场变革的核心驱动力&#xff0c;其重要性不言而喻。然而&#xff0c;面对海量且复杂的数据&#xff0c;如何高效、直观地将其转化为有价值的洞察&#xff0c;成为了众多制造企业亟待解…

前端面试题25(css常用的预处理器)

在前端开发领域&#xff0c;CSS预处理器在面试中经常被提及&#xff0c;其中最流行的三种预处理器是Sass、LESS和Stylus。下面分别介绍它们的特点和优势&#xff1a; 1. Sass&#xff08;Syntactically Awesome Style Sheets&#xff09; 优势&#xff1a; 变量&#xff1a;允…

实战Qt开发WordBN笔记软件#01 搭建开发环境:VS2019+Qt6.5+CMake+Git

01 背景 【WordBN字远笔记】是天恩软件工作室开发的一款免费笔记软件&#xff1b;WordBN基于VS2019、Qt6.5开发&#xff0c;使用Qt Quick&#xff08;QML&#xff09;开发语言。 本课程将以【WordBN字远笔记】的界面为实战基础&#xff0c;详细介绍如何基于Qt/QML开发语言&am…

从新手到高手:Scala函数式编程完全指南,Scala IF…ELSE 语句(8)

1、Scala IF…ELSE 语句 Scala IF…ELSE 语句是通过一条或多条语句的执行结果&#xff08;True或者False&#xff09;来决定执行的代码块。 可以通过下图来简单了解条件语句的执行过程: 1.1、if 语句 if 语句有布尔表达式及之后的语句块组成。 语法 if 语句的语法格式如下&…

LT7911UX 国产原装 一拖三 edp 转LVDS 可旋转 可缩放

2.一般说明 该LT7911UX是一种高性能Type-C/DP1.4a到MIPI或LVDS芯片的VR/显示应用。HDCP RX作为HDCP转发器的上游&#xff0c;可以与其他芯片的HDCP TX配合实现转发器功能。 对于DP1.4a输入&#xff0c;LT7911UX可配置为1/2/4通道。自适应均衡使其适用于长电缆应用&#xff0c;最…

python对象

类 我们目前所学习的对象都是Python内置的对象但是内置对象并不能满足所有的需求&#xff0c;所以我们在开发中经常需要自定义一些对象类&#xff0c;简单理解它就相当于一个图纸。在程序中我们需要根据类来创建对象类就是对象的图纸&#xff01;我们也称对象是类的实例&#…

给您介绍工控CAN总线

CAN是什么 CAN&#xff0c;全称Controller Area Network&#xff0c;即控制器局域网&#xff0c;是一种由Bosch公司在1983年开发的通信协议。它主要用于汽车和工业环境中的电子设备之间的通信。CAN协议定义了物理层和数据链路层的通信机制&#xff0c;使得不同的设备能够通过CA…

LabVIEW开发商业软件的多角度分析与注意事项

在使用LabVIEW开发商业软件时&#xff0c;有许多方面需要考虑和注意&#xff0c;包括项目管理、架构设计、性能优化、用户体验、安全性、维护与支持等。以下是从多个角度详细分析在LabVIEW中开发商业软件需要注意的事项。 项目管理 需求分析&#xff1a;确保深入了解客户需求&a…

BFS:边权相同的最短路问题

一、边权相同最短路问题简介 二、迷宫中离入口最近的出口 . - 力扣&#xff08;LeetCode&#xff09; class Solution { public:const int dx[4]{1,-1,0,0};const int dy[4]{0,0,1,-1};int nearestExit(vector<vector<char>>& maze, vector<int>& e…

使用C++实现ATM系统,谈谈思路及代码实现

&#x1f3c6;本文收录于「Bug调优」专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&&…

文华财经盘立方期货通鳄鱼指标公式均线交易策略源码

文华财经盘立方期货通鳄鱼指标公式均线交易策略源码&#xff1a; 新建主图幅图类型指标都可以&#xff01; VAR1:(HL)/2; 唇:REF(SMA(VAR1,5,1),3),COLORGREEN; 齿:REF(SMA(VAR1,8,1),5),COLORRED; 颚:REF(SMA(VAR1,13,1),8),COLORBLUE;

Unity实现安卓App预览图片、Pdf文件和视频的一种解决方案

一、问题背景 最近在开发app项目&#xff0c;其中有个需求就是需要在app软件内显示图片、pdf和视频&#xff0c;一开始想的解决方案是分开实现&#xff0c;也就是用Image组件显示图片&#xff0c;找一个加载pdf的插件和播放视频的插件&#xff0c;转念一想觉得太麻烦了&#x…

张量分解(2)——张量运算(内积、外积、直积、范数)

&#x1f345; 写在前面 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;这里是hyk写算法了吗&#xff0c;一枚致力于学习算法和人工智能领域的小菜鸟。 &#x1f50e;个人主页&#xff1a;主页链接&#xff08;欢迎各位大佬光临指导&#xff09; ⭐️近…

vue3中使用 tilwindcss报错 Unknown at rule @tailwindcss

解决方法&#xff1a; vscode中安装插件 Tailwind CSS IntelliSense 在项目中的 .vscode中 settings.json添加 "files.associations": {"*.css": "tailwindcss"}

桌面快充插线板+伸缩数据线,轻松实现1+1>2

手机、平板、笔记本等电子设备已成为我们日常工作和学习的必备工具。然而,随着设备数量的增加,充电问题也日益凸显。桌面空间有限,多个快充头不仅显得杂乱无章,而且效率低下,无法满足我们高效办公的需求。 在这样的背景下,倍思Nomos氮化镓100W桌面充电站凭借其创新的设计和强大…

HR8870:H桥PWM直流电机驱动IC性能指标和应用方案选型

HR8870芯片描述 HR8870是一款直流有刷电机驱动器&#xff0c;适用于打印机、电器、工业设备以及其他小型机器。两个逻辑输入控制H桥驱动器&#xff0c;该驱动器由四个N-MOS组成&#xff0c;能够以高达4.5A的峰值电流双向控制电机。利用电流衰减模式&#xff0c;可通过对输入进行…

C++基础语法之重载引用和命名空间等

1.C关键字 c的关键字比我们的c语言的关键字多&#xff0c;c包容C语言并对C语言进行了补充&#xff0c;但是我们对关键字的学习是在我们后面逐渐学习的。这里我们的只是提供一个表格对齐了解一下。 2.命名空间 我们c出现了命名空间的概念&#xff0c;用关键字namespace来定义。…