左值引用、右值引用及移动语义

news2024/11/17 6:47:57

个人主页:Lei宝啊 

愿所有美好如期而遇


左值

概念

可以取到地址的值就是左值,并且一般情况下可以修改(const类型左值不可修改)。

左值举例:

//左值
int a = 0;
const int b = 1;
int* p = &a;

右值

概念

不能取到地址的值就是右值,并且右值不能被修改。(字面常量,表达式返回值,函数返回值(非左值引用)),我们之前使用的就是左值引用。

右值举例:

int func()
{
	return 1 + 1;
}

//右值
10;
a + b;
func();

左值引用

概念

左值引用就是给左值的引用,给左值取别名。

	int a = 0;
	const int b = 1;
	int* p = &a;

    //左值引用
	int& c = a;
	const int& d = b;
	int*& e = p;

右值引用

概念

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

	10;
	a + b;
	func();

	//右值引用
	int&& e = 10;
	int&& f = a + b;
	int&& g = func();

互相引用 

左值引用右值需要加const,右值引用左值需要将左值先进行move。

	//左值引用右值
	const int& f = 10;
	//右值引用左值
	int&& g = move(a);

应用与解释

简单来说就是为什么需要右值引用,我们先来看例子:

也就是说,临时对象的产生其实是多余的,所以在没有右值引用时,编译器给出的优化方案就是优化掉临时对象,直接使func中vv拷贝构造main中vv,可即便如此,还是有一次无谓的拷贝,就是func中的vv,他仍要销毁,资源还是浪费了。

接下来使用我们自己实现的string,来对有无右值引用做对比。

#include <iostream>
#include <string>
#include <string.h>
#include <cassert>
#include <algorithm>

using namespace std;

namespace own
{
	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;

			_str = new char[s._capacity+1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}

		// 拷贝赋值
		// s2 = tmp
		string& operator=(const string& s)
		{
			cout << "string& operator=(const 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
	};

	own::string to_string(int value)
	{
		bool flag = true;
		if (value < 0)
		{
			flag = false;
			value = 0 - value;
		}

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

这里博主使用g++编译器,并且关闭了部分优化,在Visual  Studio 2022中,优化非常大,我们看不出他的过程,无法更好地进行对比,所以这里在Linux下进行演示,并使用-fno-elide-constructors关闭g++的编译优化。

举例一:仅有左值引用

一:
int main()
{
    own::string ret = own::to_string(1234);
    return 0;
}

这也就是我们上面得出的结果。

二:
int main()
{
    own::string ret;
    ret = own::to_string(-1234);

    return 0;
}

这种编译器是不会优化的,所以一般来说将他们写在一行上。

举例二:加入右值引用

		// 移动构造 -- 右值(将亡值)
		string(string&& s)
		{
			cout << "string(string&& s) -- 移动拷贝" << endl;
			swap(s);
		}

我们这里为什么要加入右值引用呢?首先,右值也叫做将亡值,也就是即将销毁的值。我们希望能够将这个将亡值利用起来,拿走他的资源。

我们可以想象一下,可以拿走左值的资源吗?左值引用左值,左值可能仍要被使用,如果这么被swap拿走资源是不可以的,但是将亡值,也就是对右值这样做却是我们希望看到的。 

一:
int main()
{
    own::string ret = own::to_string(1234);
    return 0;
}

这和我们上面的结果是一致的。

二:
int main()
{
    own::string ret;
    ret = own::to_string(-1234);

    return 0;
}

加入移动赋值拷贝

		// 移动赋值
		string& operator=(string&& s)
		{
			cout << "string& operator=(string&& s) -- 移动拷贝" << endl;
			swap(s);

			return *this;
		}
三:
int main()
{
    own::string ret;
    ret = own::to_string(-1234);

    return 0;
}

看了这么久也许你有一个疑惑,移动构造和移动赋值,都要交换右值的资源,但是右值不是不能被修改吗?

那么这里我们说:右值引用 引用右值后的属性为左值。

也就是说,一个右值,被右值引用后,那个右值引用的属性将变成左值,于是swap中的s属性是左值,也就可以传过去了。

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

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

相关文章

虚拟ECU:汽车空调压缩机控制系统

2024年是“十四五”的“关键一年”&#xff0c;在中国居民生活水平不断上升的趋势下&#xff0c;人们对汽车的需求已不再局限于简单的代步工具&#xff0c;而对其整体的舒适度和体验度也有着越来越高的要求。作为提升驾车与乘车体验的重要部分&#xff0c;汽车的空调系统在电动…

3D软件坐标系速查

本文介绍不同3D软件的世界坐标系之间的差异及其工作原理。 基本上&#xff0c;游戏引擎和3D软件包最重要的问题是根据软件的坐标轴系统创建资产&#xff0c;正确缩放它们并根据要完成的工作设置枢轴系统。 坐标系正确性的定义可能会根据模型导入的游戏引擎或 3D 软件而变化。…

Python Flask-Mail实现邮件发送

一、邮件发送的扩展 关于如何找到flask发送邮件的插件&#xff1f;&#xff0c;上一篇已经分享了如何找到第三方插件&#xff0c;也找到了插件flask-mail的使用文档&#xff0c;那我们就来实战吧 二、根据文档&#xff0c;总结发送邮件的流程 从文档中可以总结出发送邮件的步…

【JavaScript】JavaScript 程序流程控制 ⑦ ( do-while 循环概念 | do-while 循环语法结构 )

文章目录 一、while 循环1、while 循环概念2、do-while 循环语法结构 二、do-while 循环代码示例1、打印 1-5 数字2、打印 1-10 累加和 一、while 循环 1、while 循环概念 JavaScript 中的 do-while 循环 是 while 循环的变体 , 是 一种 后测试 循环 , 该循环的 循环体 至少执行…

Lazada、shopee自养号测评环境搭建需要准备哪些?

在如今电商竞争日益激烈的环境下&#xff0c;卖家们深知流量对店铺转化率的至关重要性。依赖平台内部的流量已不再足够&#xff0c;越来越多的卖家开始探索其他方式来提升销售业绩和品牌知名度。其中&#xff0c;自养号测评成为了一种备受关注的策略&#xff0c;通过搭建独立运…

零基础自学C语言|文件操作

✈为什么使用文件&#xff1f; 如果没有文件&#xff0c;我们写的程序的数据是存储在电脑的内存中&#xff0c;如果程序退出&#xff0c;内存回收&#xff0c;数据就丢失了&#xff0c;等再次运行程序&#xff0c;是看不到上次程序的数据的&#xff0c;如果要将数据进行持久化…

OSPF-区域间路由计算

一、概述 前面学习了我们学习了Router-LSA和Network-LSA&#xff0c;它们都只能在区域内进行泛洪&#xff0c;而且我们之前一直主要是单区域学习。OSPF的核心是骨干区域Area 0&#xff0c;其它都为非骨干区域。但是在大型网络中&#xff0c;单区域OSPF会存在一定的问题&#xf…

Hack The Box-Headless

目录 信息收集 nmap whatweb WEB web信息收集 dirmap 可利用点分析 xss攻击 RCE漏洞 提权 get user get root 信息收集 nmap 端口探测┌──(root㉿ru)-[~/kali/hackthebox] └─# nmap -p- 10.10.11.8 --min-rate 10000 …

使用docker-compose搭建wordpress博客

1、从远程仓库拉取worldpress镜像到本地 2、新建一个项目&#xff0c;然后在新建的项目目录里面新建一个docker-compose.yml模版文件。 3、编写docker-compose.yml文件 4、docker-compose up 运行项目。 5、在浏览器测试 使用docker-compose搭建wordpress博客实验成功。

三相四线智能电表直接接入式接线图

大家好&#xff0c;今天我们要聊的是三相四线智能电表的直接接入式接线方式。别担心&#xff0c;我会用简单易懂的语言来解释这个看似复杂的主题。 首先&#xff0c;我们得知道什么是三相四线电表。在我们的生活中&#xff0c;电力供应通常分为单相和三相。三相电&#xff0c;就…

Python中的数据类型有四类八种如何理解?

在Python中&#xff0c;数据类型大致可以分为四大类&#xff0c;包含了八种基本的数据类型&#xff0c;这些分类有助于理解和使用Python进行编程。这四大类分别是&#xff1a; 数字类型 (Numeric Types): 整型 (int): 表示没有小数部分的整数&#xff0c;可以是正数、负数或零。…

【爬虫基础】第2讲 使用Urllib库创建第一个爬虫程序

Urllib 是 Python 的标准库&#xff0c;它提供了一系列用于处理 URL 的函数和类&#xff0c;包括发送 HTTP 请求、处理 HTTP 响应、解析 URL 等功能。可以使用 urllib 来编写简单的网络爬虫。 request&#xff1a;它是最基本的HTTP请求模块&#xff0c;可以用来模拟发送请求。只…

如何利用OpenCV4.9离散傅里叶变换

返回&#xff1a;OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇:如何利用OpenCV4.9 更改图像的对比度和亮度 下一篇: 目标 我们将寻求以下问题的答案&#xff1a; 什么是傅里叶变换&#xff0c;为什么要使用它&#xff1f;如何在 OpenCV 中做到这一点…

Spring6 (1)

Spring 1、简介&#xff1a;2、第一个程序2、set注入2.1 简单数据类型2.2测试2.3 注入Properties2.4 p命名空间注入2.5 c命名空间注入2.6 util注入2.6 引入外部配置文件 1、简介&#xff1a; 自己的理解&#xff1a;spring其实就是一个容器&#xff0c;也可以说是一个框架&…

腾讯云轻量4核8G12M服务器配置4C8G12M详解

4核8G是云服务器的参数&#xff0c;代表云服务器的硬件配置和网络带宽&#xff0c;4核代表CPU、8G是指内存、12M代表带宽值为12Mbps&#xff0c;腾讯云百科txybk.com以腾讯云轻量应用服务器4核8G12M带宽配置为例&#xff0c;来详细介绍下服务器参数&#xff1a; 4c8g是什么意思…

高阶数据结构 <红黑树>

本文已收录至《数据结构(C/C语言)》专栏&#xff01; 作者&#xff1a;ARMCSKGT 目录 前言正文红黑树简介红黑树整体结构红黑树节点的定义红黑树主体类设计红黑树的插入函数情况一&#xff1a;变色情况二&#xff1a;变色旋转单旋情况双旋情况 完整插入代码 关于红黑树红黑树检…

鸿蒙OS实例:同步获取应用配置的【versionCode和versionName】

1.同步方式获取 首先需要导包&#xff1a; import bundleManager from ohos.bundle.bundleManager 工具类&#xff1a; public static async getVersionName(): Promise<string> {try {let bundleInfo await bundleManager.getBundleInfoForSelf(bundleManager.Bundle…

树状数组及应用

目录 1.树状数组的概念与基本编码 1.1.引导 1.2.lowbit(x) 1.3.树状数组的编码 2.树状数组的基本应用 2.1.单点修改&#xff0b;区间查询 2.2.区间修改单点查询 例题&#xff1a; 2.3.区间修改&#xff0b;区间查询 例题&#xff1a; 如果数列A是静态不变的&#xff…

并发学习25--多线程 ThreadPoolExecutor

类图&#xff1a;定义了一些重要的接口和实现类 线程池的几种状态&#xff1a; ThreadPoolExecutor构造方法 1.救急线程 线程池中会有核心线程和救急线程&#xff1b;救急线程数最大线程数-核心线程数。而救急线程会在阻塞队列已经占满的情况下&#xff0c;执行下一个即将要被…

RK3568-开启ptp服务

硬件支持 mac或者phy需要支持ptp驱动支持 CONFIG_PTP_1588_CLOCK=y虚拟机端:虚拟机只支持软件时间戳。 安装ptp服务:sudo apt-get install linuxptp 修改ptp服务:sudo vi /lib/systemd/system/ptp4l.service,修改为ens33网口,-S表示使用软件时间戳。forlinx@ubuntu:~$ s…