C++11---右值引用(深度讲解)

简要介绍

右值引用是C++11的新特性,无论左值引用还是右值引用,都是在给对象取别名

什么是左值 什么是右值

1.左值,左值引用

左值是一个数据的表达式(例如变量或者解引用后的指针),我们可以对其进行取地址和修改赋值,左值可以出现在赋值符号的左边,而右值不能出现在赋值符号的左边,而左值引用就是对左值起别名,

如图

2.右值

右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引
用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能
取地址

右值引用

右值引用就是对右值的引用,给右值取别名

这几个都是常见的右值引用

需要注意的是,在给右值引用后,变量就会变成左值,为什么? 因为需要对其进行修改,如果需要再要将其变成右值,需要加上std::move()将其变成右值

右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可
以取到该位置的地址,也就是说例如:不能取字面量10的地址,但是a引用后,可以对a取地
址,也可以修改rr1。如果不想a被修改,可以用const int&& a 去引用,是不是感觉很神奇,
这个了解一下实际中右值引用的使用场景并不在于此,这个特性也不重要

 左值引用和右值引用比较

左值引用总结:

1. 左值引用只能引用左值,不能引用右值。
2. 但是const左值引用既可引用左值,也可引用右值


右值引用总结:

1. 右值引用只能右值,不能引用左值。
2. 但是右值引用可以move以后的左值。

为什么要有右值引用

从左值引用窥探

前面我们可以看到左值引用既可以引用左值和又可以引用右值,那为什么C++11还要提出右值引
用呢?是不是画蛇添足呢?下面我们来看看左值引用的短板,右值引用是如何补齐这个短板的!
注意以下采用右值引用的移动构造

#pragma once
class string
{
public:
	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
};

左值引用的使用场景:
做参数和做返回值都可以提高效率

如下:

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

左值引用的短板:
但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回
只能传值返回。例如:string to_string(int value)函数中可以看到,这里只能使用传值返回,
传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)。

 这里可以看到,传值返回会至少导致一次拷贝构造(旧编译可能优化没有那么好就是两次了)

因为to_string的返回值是右值,利用这个右值构造ret,如果没有了移动构造,就会去调用拷贝构造,就会给上一个深拷贝,可以看见,这里是有优化空间的

出来吧!我的右值引用和移动语义

解决方案:

在string中增加移动构造,移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不
用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己

string (string&& s)
	:_str(nullptr)
{
    cout<<"这是移动构造"<<endl;
	swap(s);
}
string (const string& s)
    :_str(nullptr)
{
    cout<<"拷贝构造深拷贝"<<endl;
    string tmp._str;
    swap(tmp,s);
}
int main()
{
    string ret2 = bit::to_string(-1234);
    return 0;
}

 再运行一下,就会发现你会调用移动构造,而移动构造里没有开空间,进行拷贝数据,所以效率提高了

(当移动构造和拷贝构造同时出现的时候,因为to_string的返回值是右值,所以编译器就会选择适合的->移动构造)

同样的道理,移动赋值也是这样

string& operator=(string&& s)
{
    cout << "string& operator=(string&& s) -- 移动语义" << endl;
    swap(s);
    return *this;
}
int main()
{
    string ret1;
    ret1 = to_string(1234);
    return 0;
}

// 运行结果:
// string(string&& s) -- 这是移动构造
// string& operator=(string&& s) -- 移动语义

 这里运行后,我们看到调用了一次移动构造和一次移动赋值。因为如果是用一个已经存在的对象
接收,编译器就没办法优化了,to_string函数中会先用str生成构造生成一个临时对象,但是
我们可以看到,编译器很聪明的在这里把str识别成了右值,调用了移动构造。然后在把这个临时
对象做为to_string函数调用的返回值赋值给ret1,这里调用的移动赋值

万能引用 完美转发

模板中的万能引用

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<typename T>
void PerfectForward(T&& t)//万能引用 传左值的时候就变成 int& t 右值就是int&&
{
    Fun(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;
}

std::forward完美转发

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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

算法打卡day32|贪心算法篇06|Leetcode 738.单调递增的数字、968.监控二叉树

算法题 Leetcode 738.单调递增的数字 题目链接:738.单调递增的数字 大佬视频讲解&#xff1a;单调递增的数字视频讲解 个人思路 这个题目就是从例子中找规律&#xff0c;例如 332&#xff0c;从后往前遍历&#xff0c;32不是单调递增将2变为9,3减1&#xff0c;变成了329&…

【Django开发】前后端分离美多商城项目第5篇:用户部分,起源【附代码文档】

美多商城项目4.0文档完整教程&#xff08;附代码资料&#xff09;主要内容讲述&#xff1a;美多商城&#xff0c;项目准备1.B2B--企业对企业,2.C2C--个人对个人,3.B2C--企业对个人,4.C2B--个人对企业,5.O2O--线上到线下,6.F2C--工厂到个人。项目准备&#xff0c;配置1. 修改set…

Kubernetes(k8s):部署、使用 metrics-server

Kubernetes&#xff08;k8s&#xff09;&#xff1a;部署、使用 metrics-server 一、metrics-server简介二、部署metrics-server2.1、 下载 Metrics Server 部署文件2.2、修改metrics-server.yaml 文件2.3、 部署 Metrics Server2.4、 检查 Metrics Server 三、使用 Metrics Se…

Boost之Log: (3)、简单封装

设计目标: 1、每个Logging source对应一个目录&#xff0c;可以设置日志文件数&#xff0c;日志大小&#xff0c;目录名&#xff0c;文件名等 2、所有logging source日志目录都在一个根目录下。 3、可以动态创建和删除logging source 4、打印出日期时间和日志严重等级 示例代码…

从python角度解析selenium原理

1、selenium工作流程 2、selenium工作原理 &#xff08;1&#xff09;客户端和服务端之间实际是通过http协议进行通信&#xff0c;服务端的接口文档可参考&#xff1a;https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#sessionsessionidelement &#xff08;2&…

softmax函数的功能及用法

Softmax函数是一种常用的激活函数&#xff0c;通常用于多分类问题的输出层。其功能是将一个具有任意实数值的向量&#xff08;通常称为“logits”&#xff09;转换为一个概率分布&#xff0c;其中每个元素的值表示对应类别的概率。 Softmax函数的公式如下&#xff1a; 给定一…

windows下通过vscode访问ubuntu(绝大部分Linux下开发所采用的方案)

前言 本篇博客是介绍VSCode远程连接Ubuntu进行开发的解决方案&#xff0c;前提是安装好了VMWare&#xff0c;Ubuntu&#xff0c;windows下的VSCode。 嵌入式驱动学习专栏将详细记录博主学习驱动的详细过程&#xff0c;未来预计四个月将高强度更新本专栏&#xff0c;喜欢的可以关…

库存超卖问题分析

3.5 库存超卖问题分析 有关超卖问题分析&#xff1a;在我们原有代码中是这么写的 if (voucher.getStock() < 1) {// 库存不足return Result.fail("库存不足&#xff01;");}//5&#xff0c;扣减库存boolean success seckillVoucherService.update().setSql(&quo…

Nginx 高级

文章目录 Nginx反向代理概念配置 负载均衡概念配置 动静分离概念配置 网关防盗链keepalivednginx跨域 Nginx 反向代理 概念 反向代理&#xff08;Reverse Proxy&#xff09;方式是指以代理服务器来接受internet上的连接请求&#xff0c;然后将请求转发给内部网络上的服务器&…

深入理解数据结构第二弹——二叉树(2)——堆排序及其时间复杂度

看这篇前请先把我上一篇了解一下&#xff1a;深入理解数据结构第一弹——二叉树&#xff08;1&#xff09;——堆-CSDN博客 前言&#xff1a; 相信很多学习数据结构的人&#xff0c;都会遇到一种情况&#xff0c;就是明明最一开始学习就学习了时间复杂度&#xff0c;但是在后期…

电商-广告投放效果分析(KMeans聚类、数据分析-pyhton数据分析

电商-广告投放效果分析&#xff08;KMeans聚类、数据分析&#xff09; 文章目录 电商-广告投放效果分析&#xff08;KMeans聚类、数据分析&#xff09;项目介绍数据数据维度概况数据13个维度介绍 导入库&#xff0c;加载数据数据审查相关性分析数据处理建立模型聚类结果特征分析…

Ceph学习 - 1.存储知识

文章目录 1.存储基础1.1 基础知识1.1.1 存储基础1.1.2 存储使用 1.2 文件系统1.2.1 简介1.2.2 数据存储1.2.3 存储应用的基本方式1.2.4 文件存储 1.3 小结 1.存储基础 学习目标&#xff1a;这一节&#xff0c;我们从基础知识、文件系统、小节三个方面来学习。 1.1 基础知识 1.…

day01 51单片机

51单片机学习 1 51单片机概述 1.1 51单片机简介 目前使用的51单片机一般是宏晶STC89系列,这其中流传最广的版本,也是我们课程的主角,就是STC89C52RC。 1.2 命名规则 1.3 单片机最小应用系统 2 点亮LED灯 2.1 硬件原理图 这个原理图非常简单,VCC接保护电阻R1,串联LED1最…

IOTX:未来市场爆发点的RWA协议?DePIN赛道被低估的龙头

从基本面来看&#xff0c;IoTeX的目标是创建一个连接的世界&#xff0c;在这个世界中&#xff0c;每个人都能控制自己的数据、设备和身份。通过区块链技术&#xff0c;IoTeX旨在解锁智能设备和数据的潜力&#xff0c;支持新一代的现实世界Dapp和数字资产的发展。IOTX始终致力于…

个性化内容的力量:Kompas.ai如何帮你定制内容

在当今的数字化营销环境中&#xff0c;个性化内容已经成为品牌与消费者建立深层次联系的关键。个性化内容不仅能够更好地满足用户的需求&#xff0c;还能够加深用户的品牌体验&#xff0c;从而提高用户满意度和忠诚度。本文将深入探讨个性化内容在提升用户参与度和忠诚度方面的…

Incus:新一代容器与虚拟机编排管理引擎

Incus是什么&#xff1f; Incus是一个用于编排管理应用型容器、系统型容器及虚拟机实例的管理工具。它是对 Canonical LXD 的继承与发展&#xff0c;引入了更多的存储驱动支持。 Incus项目的产品地址&#xff1a;Linux Containers - Incus - Introduction 在 LXC-Incus 项目…

Java练习

这个练习我用到了继承&#xff0c;多态和封装。 1.继承&#xff1a; Animal 类是一个抽象类&#xff0c;它有两个子类 Dog 和 Cat。 Dog 和 Cat 分别继承自 Animal 类&#xff0c;因此它们可以使用 Animal 类中定义的属性和方法&#xff0c;同时也可以有自己特有的属性和方法。…

鸿蒙OS元服务开发:【(Stage模型)学习窗口沉浸式能力】

一、体验窗口沉浸式能力说明 在看视频、玩游戏等场景下&#xff0c;用户往往希望隐藏状态栏、导航栏等不必要的系统窗口&#xff0c;从而获得更佳的沉浸式体验。此时可以借助窗口沉浸式能力&#xff08;窗口沉浸式能力都是针对应用主窗口而言的&#xff09;&#xff0c;达到预…

基于springboot+vue+Mysql的教学视频点播系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

pymc,一个灵活的的 Python 概率编程库!

目录 前言 安装与配置 概率模型 贝叶斯推断 概率分布 蒙特卡罗采样 贝叶斯网络 实例分析 PyMC库的应用场景 1. 概率建模 2. 时间序列分析 3. 模式识别 总结 前言 大家好&#xff0c;今天为大家分享一个超强的 Python 库 - pymc Github地址&#xff1a;https://gith…