C++11(二)

目录

左值引用与右值引用

左值引用 

右值引用

右值与左值交叉引用

移动语义 

移动构造

移动赋值

完美转发


本期我们将学习C++11中比较重要的一个知识点------右值引用。

左值引用与右值引用

在学习左值引用和右值引用之前,我们得先知道什么是左值,什么是右值。

我们直接给出定义。

左值:能够取地址的就是左值。

右值:右值又分为纯右值和将亡值。

纯右值:变量相加(a+b),常数(1,2,3),函数传值返回时最终创建的中间对象。

注意:传值返回的函数返回值与返回时最终创建的中间对象不是一个概念,中间对象的生命周期比函数返回值要大。

将亡值:1.传值返回的函数的返回值(这个返回值通常都是自定义对象)。

               2.move以后的自定义对象。

左值引用 

图示如下。

以上所有引用都是左值引用,左值引用说白了就是起别名,在某些自定义类型的传参过程中,通过左值引用还可以减少自定义类型对象的拷贝,可以减少资源的消耗。

右值引用

在C++11中,我们引入了右值引用的概念。

图示如下。

右值引用有意义吗?目前看来确实没有什么意义,但是对于后期的移动语义的语法大有作用。

右值与左值交叉引用

提出两个问题。左值引用是否可以引用右值?右值引用是否可以引用左值?我们一起来探究。

图示如下。

不难发现,左值引用和右值引用都不能直接进行交叉引用。但是const左值引用可以引用右值,move之后的左值可以被右值引用引用。 

那就意味着在c++98,中左值引用可以引用右值,那么既然如此,右值引用的意义又在哪里,这就回到了上一标题中类似的问题,右值引用的意义仍然在移动语义的语法中大有用处。 

移动语义 

移动语义又分为移动构造和移动赋值。

我们以之前模拟时间的string类进行探究,模拟实现代码如下,我们已经实现了移动构造和移动赋值

namespace yjd
{
	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)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;

			string tmp(s._str);
			swap(tmp);
		}

		// 移动构造
		string(string&& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(string&& s) -- 资源转移" << endl;

			this->swap(s);
		}

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

			return *this;
		}

		string& operator=(const string& s)
		{
			cout << "string& operator=(string s) -- 深拷贝" << endl;
			string tmp(s);
			swap(tmp);

			return *this;
		}

		~string()
		{
			//cout << "~string()" << endl;

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

		string operator+(char ch)
		{
			string tmp(*this);
			push_back(ch);

			return tmp;
		}

		const char* c_str() const
		{
			return _str;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};

	string to_string(int value)
	{
		 string str;
		while (value)
		{
			int val = value % 10;
			str += ('0' + val);
			value /= 10;
		}
		reverse(str.begin(), str.end());

		return str;
	}
}

移动构造

图示如下。

移动构造和拷贝构造的区别是什么?
通过下述代码为大家讲解。


int main()
{
     yjd::string s1 = yjd::to_string(123);

	return 0;
}

同样的一段测试代码,在我们屏蔽到移动构造前后运行结果是不一样的。

没有屏蔽前。

屏蔽之后。

为什么屏蔽前后运行结果是不一样的呢?这里面其实大有来头。

我们知道只要是传值返回的函数的返回值一定是通过返回的对象创建出来的中间临时对象。

1.在屏蔽了移动构造之后,在返回调用to_string函数时,to_string函数是一个传值返回的函数,最终返回了str对象,但是str是一个局部对象,出了函数作用域之后会被销毁,所以调用了拷贝构造函数通过str对象创建了中间对象,最终又通过中间对象拷贝构造了s1对象,相当于拷贝构造了两次,但是编译器做了优化,可以直接认为是str对象拷贝构造了对象s1。所以屏蔽之后调用了一次拷贝构造函数,完成了深拷贝。对于string类而言,深拷贝的代价是很大的。

图示如下。

2.在没有屏蔽移动构造之前。 因为to_string返回的是一个临时对象str,我们上文已经讲过,传值返回的返回的临时对象就是将亡值,将亡值也是右值,所以我们通过调用移动构造通过str创建了临时对象,我们又说函数传值返回最终创建的临时对象是纯右值,所以我们又调用移动构造通过临时对象创建了s1。相当于整个过程调用了两次移动构造,但是编译器做了优化,所以整个过程就调用了一次移动构造,认为是str移动构造了s1。

图示如下。

 那么拷贝构造和移动构造的区别是什么?

区别很大,拷贝构造是重新申请一块资源拷贝资源,相当于两份同样的资源,计算机消耗的内存资源很大。移动构造是直接进行资源的交换,交换前后资源不变,也就意味着计算机内存资源的消耗很少。

在拷贝构造函数中,我们使用了现代写法通过拷贝构造函数创建了临时对象,然后让当前对象与临时对象发生了交换。所以拷贝构造后,被拷贝的对象的资源仍然是存在的。

在移动构造函数中,因为拷贝的对象要么是将亡值,要么是中间对象(纯右值),都是即将要销毁的,所以在拷贝构造函数中,我们直接对拷贝的对象和被拷贝的对象进行了资源交换,所以在移动构造后,被拷贝的对象的资源已经不在了,已经被转移到了拷贝的对象中。

移动赋值

移动赋值代码图示如下。

测试代码如下。

int main()
{
    
	 yjd::string s2 = "hello yjd";
	 yjd::string s3 = "hello world";
	 s3 = s2;
	 s3 = move(s2);
	return 0;
}

 左值赋值调用赋值运算符重载函数,右值赋值调用移动赋值。

运行结果如下。

赋值运算符重载和移动赋值有什么区别呢?区别还是很大的。

1.赋值运算符重载,赋值前后,赋值的对象的资源仍然不变。

2.移动赋值,因为赋值的是要即将被销毁的右值。赋值之后,赋值的对象的资源与被赋值的对象的资源发生了交换。

总的来说,移动构造和移动赋值与拷贝构造和赋值运算符重载的最大区别就是,移动构造和移动赋值不用去创建新的资源,只是资源的转移,代价比较小;但是拷贝构造和运算符重载中都需要进行深拷贝,代价比较大。比如在拷贝构造函数中创建中间对像时,调用构造函数进行深拷贝,在赋值运算符重载函数中,我我们先拷贝构造一个中间对象,在拷贝构造函数中我们又调用构造函数深拷贝创建了个中间对象。

完美转发

何为完美转发?直接给出概念。

完美转发是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数且目标函数总希望将参数按照传递给转发函数的实际类型转给目标函数,而不产生额外的开销。

简单来说,就是函数模板在向其他函数传递自身形参时,如果相应形参是左值,它就应该被转发为左值;如果相应形参是右值,它就应该被转发为右值。

以一段代码展开。

template<class T>
void Func(T& data)
{
	cout << "左值引用" << endl;
}

template<class T>
void Func(const T& data)
{
	cout << "const左值引用" << endl;
}

template<class T>
void Func( T&& data)
{
	cout << "右值引用" << endl;
}

template<class T>
void Func(const T&& data)
{
	cout << "const右值引用" << endl;
}

template<class T>
void PerfectForward(T&& data) //T&& 为万能模板,既可以接收左值也可以接收右值
{
	Func(data);
}

int main()
{
	PerfectForward(10);
	int a = 10;
	PerfectForward(a);
	const int b = 10;
	PerfectForward(b);
	PerfectForward(move(b));
	return 0;
}

在上述代码中,PerfectForward为转发的模板函数,Func为实际目标函数。

 运行结果如下。

为什么我们传入的所有值都是左值引用呢。

这是因为右值再被右值引用之后,就变成了左值,因为可以取地址,如上图所示。所以当我们不论是把右值还是把const右值传递给万能模板之后,data分别被认定成了左值和const左值。

怎么样保证对象本身的属性呢?其实这个问题也就是怎么样保证一个对象传过来的时候是什么类型,发送的时候就是什么类型?

在C++11中,我们通过forward函数实现。

代码如下。

template<class T>
void Func(T& data)
{
	cout << "左值引用" << endl;
}

template<class T>
void Func(const T& data)
{
	cout << "const左值引用" << endl;
}

template<class T>
void Func( T&& data)
{
	cout << "右值引用" << endl;
}

template<class T>
void Func(const T&& data)
{
	cout << "const右值引用" << endl;
}

template<class T>
void PerfectForward(T&& data) //T&& 为万能模板,既可以接收左值也可以接收右值
{
	Func(forward<T>(data));
}

int main()
{
	PerfectForward(10);
	int a = 10;
	PerfectForward(a);
	const int b = 10;
	PerfectForward(b);
	PerfectForward(move(b));


	return 0;
}

运行结果如下。

通过forward函数我们实现了完美转发,即保留了对象的属性。

以上便是本期的所有内容。

本期内容到此结束^_^ 

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

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

相关文章

【python】四帧差法实现运动目标检测

四帧差法是一种运动目标检测技术&#xff0c;它通过比较连续四帧图像之间的差异来检测运动物体。这种方法可以在一定的程度上提高检测的准确性。 目录 1 方案 2 实践 ① 代码 ② 效果图 1 方案 具体的步骤如下&#xff1a; ① 读取视频流&#xff1a;使用cv2.VideoCapture…

SpringBoot开发(二)Spring Boot项目构建、Bootstrap基础知识

1. Spring Boot项目构建 1.1. 简介 基于官方网站https://start.spring.io进行项目的创建. 1.1.1. 简介 Spring Boot是基于Spring4框架开发的全新框架&#xff0c;设计目的是简化搭建及开发过程&#xff0c;并不是对Spring功能上的增强&#xff0c;而是提供了一种快速使用Spr…

PMP–一、二、三模–分类–12.采购管理

文章目录 技巧十二、采购管理 一模12.采购管理--3.控制采购--输出--风险登记册--每个被选中的卖方都会带来特殊的风险。随着早期风险的过时以及新风险的出现&#xff0c;在项目执行期间对风险登记册进行变更。 供应商还未开始做&#xff0c;是一个风险&#xff0c;当做风险进行…

栈和队列(C语言)

目录 数据结构之栈 定义 实现方式 基本功能实现 1&#xff09;定义&#xff0c;初始化栈 2&#xff09;入栈 3&#xff09;出栈 4&#xff09;获得栈顶元素 5)获得栈中有效元素个数 6&#xff09;检测栈是否为空 7&#xff09;销毁栈 数据结构之队列 定义 实现方…

B站pwn教程笔记-1

因为没有垃圾处理机制&#xff0c;适合做编译&#xff0c;不会有堵塞 c语言市场占有率还是比较高的。 Windows根据后缀识别文件&#xff0c;linux根据文件头识别 55:16 编译过程 一步&#xff1a;直接gcc编译.c文件 这只是其中的一些步骤 gcc -S 转变为汇编。但其实这时候还…

jQuery小游戏

jQuery小游戏&#xff08;一&#xff09; 嘻嘻&#xff0c;今天我们来写个jquery小游戏吧 首先&#xff0c;我们准备一下写小游戏需要准备的佩饰&#xff0c;如果&#xff1a;图片、音乐、搞怪的小表情 这里我准备了一些游戏中需要涉及到的图片 游戏中使用到的方法 eval() 函…

Batch Normalization学习笔记

文章目录 一、为何引入 Batch Normalization二、具体步骤1、训练阶段2、预测阶段 三、关键代码实现四、补充五、参考文献 一、为何引入 Batch Normalization 现在主流的卷积神经网络几乎都使用了批量归一化&#xff08;Batch Normalization&#xff0c;BN&#xff09;1&#xf…

JavaSec系列 | 动态加载字节码

视频教程在我主页简介或专栏里 目录&#xff1a; 动态加载字节码 字节码 加载远程/本地文件 利用defineClass()直接加载字节码 利用TemplatesImpl加载字节码 动态加载字节码 字节码 Java字节码指的是JVM执行使用的一类指令&#xff0c;通常被存储在.class文件中。 加载远程…

第十四讲 JDBC数据库

1. 什么是JDBC JDBC&#xff08;Java Database Connectivity&#xff0c;Java数据库连接&#xff09;&#xff0c;它是一套用于执行SQL语句的Java API。应用程序可通过这套API连接到关系型数据库&#xff0c;并使用SQL语句来完成对数据库中数据的查询、新增、更新和删除等操作…

JVM面试题解,垃圾回收之“分代回收理论”剖析

一、什么是分代回收 我们会把堆内存中的对象间隔一段时间做一次GC&#xff08;即垃圾回收&#xff09;&#xff0c;但是堆内存很大一块&#xff0c;内存布局分为新生代和老年代、其对象的特点不一样&#xff0c;所以回收的策略也应该各不相同 对于“刚出生”的新对象&#xf…

电脑如何访问手机文件?

手机和电脑已经深深融入了我们的日常生活&#xff0c;无时无刻不在为我们提供服务。除了电脑远程操控电脑外&#xff0c;我们还可以在电脑上轻松地访问Android或iPhone手机上的文件。那么&#xff0c;如何使用电脑远程访问手机上的文件呢&#xff1f; 如何使用电脑访问手机文件…

ThinkPHP 8模型与数据的插入、更新、删除

【图书介绍】《ThinkPHP 8高效构建Web应用》-CSDN博客 《2025新书 ThinkPHP 8高效构建Web应用 编程与应用开发丛书 夏磊 清华大学出版社教材书籍 9787302678236 ThinkPHP 8高效构建Web应用》【摘要 书评 试读】- 京东图书 使用VS Code开发ThinkPHP项目-CSDN博客 编程与应用开…

【MySQL】数据库基础知识

欢迎拜访&#xff1a;雾里看山-CSDN博客 本篇主题&#xff1a;【MySQL】数据库基础知识 发布时间&#xff1a;2025.1.21 隶属专栏&#xff1a;MySQL 目录 什么是数据库为什么要有数据库数据库的概念 主流数据库mysql的安装mysql登录使用一下mysql显示数据库内容创建一个数据库创…

【线性代数】基础版本的高斯消元法

[精确算法] 高斯消元法求线性方程组 线性方程组 考虑线性方程组&#xff0c; 已知 A ∈ R n , n , b ∈ R n A\in \mathbb{R}^{n,n},b\in \mathbb{R}^n A∈Rn,n,b∈Rn&#xff0c; 求未知 x ∈ R n x\in \mathbb{R}^n x∈Rn A 1 , 1 x 1 A 1 , 2 x 2 ⋯ A 1 , n x n b 1…

高等数学学习笔记 ☞ 微分方程

1. 微分方程的基本概念 1. 微分方程的基本概念&#xff1a; &#xff08;1&#xff09;微分方程&#xff1a;含有未知函数及其导数或微分的方程。 举例说明微分方程&#xff1a;&#xff1b;。 &#xff08;2&#xff09;微分方程的阶&#xff1a;指微分方程中未知函数的导数…

HarmonyOS基于ArkTS卡片服务

卡片服务 前言 Form Kit&#xff08;卡片开发框架&#xff09;提供了一种在桌面、锁屏等系统入口嵌入显示应用信息的开发框架和API&#xff0c;可以将应用内用户关注的重要信息或常用操作抽取到服务卡片&#xff08;以下简称“卡片”&#xff09;上&#xff0c;通过将卡片添加…

Java复习第四天

一、代码题 1.相同的树 (1)题目 给你两棵二叉树的根节点p和q&#xff0c;编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同&#xff0c;并且节点具有相同的值&#xff0c;则认为它们是相同的。 示例 1: 输入:p[1,2,3]&#xff0c;q[1,2,3] 输出:true示例 2: 输…

全面了解 Web3 AIGC 和 AI Agent 的创新先锋 MelodAI

不管是在传统领域还是 Crypto&#xff0c;AI 都是公认的最有前景的赛道。随着数字内容需求的爆炸式增长和技术的快速迭代&#xff0c;Web3 AIGC&#xff08;AI生成内容&#xff09;和 AI Agent&#xff08;人工智能代理&#xff09;正成为两大关键赛道。 AIGC 通过 AI 技术生成…

新能源汽车充电桩选型以及安装应用

摘要:随着当前经济的不断发展,国家的科技也有了飞速的进步,传统的燃油汽车已经不能适应当前社会的发展,不仅对能源造成巨大的消耗,还对环境造成了污染,当前一种新型的交通运输工具正在占领汽车市场。在环境问题和能源问题愈发严重的当今社会,节能减排已经成为全世界的共同课题,…

一个vue项目npm install失败的问题解决方案

vue的项目一直是史上最难的最烦的问题&#xff0c;今天给别人做毕设单子想在gitee上拉项目二开的时候&#xff0c;由于很久没写过vue项目已经生疏了&#xff0c;在拿到项目之后我还是例行完成最常见的步骤&#xff1a; 1、npm init -y 初始化 2、npm install 用npm把这个项目…