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

news2024/10/7 12:19:30

简要介绍

右值引用是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;
}

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

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

相关文章

深度解析C语言——预处理详解

对C语言有一定了解的同学&#xff0c;相信对预处理一定不会陌生。今天我们就来聊一聊一些预处理的相关知识。预处理是在编译之前对源文件进行简单加工的过程&#xff0c;主要是处理以#开头的命令&#xff0c;例如#include <stdio.h>、#define等。预处理是C语言的一个重要…

leetcode721. 合并账户【两种方法;并查集;dfs】

文章目录 并查集&#xff08;方法一&#xff09;dfs&#xff08;方法二&#xff09;dfs换一种写法 并查集&#xff08;方法一&#xff09; class Solution {unordered_map<string, int> index; // 每个邮箱都有一个唯一编号int root[10010]; // 并查集…

C++算法——滑动窗口

一、长度最小的子数组 1.链接 209. 长度最小的子数组 - 力扣&#xff08;LeetCode&#xff09; 2.描述 3.思路 本题从暴力求解的方式去切入&#xff0c;逐步优化成“滑动窗口”&#xff0c;首先&#xff0c;暴力枚举出各种组合的话&#xff0c;我们先让一个指针指向第一个&…

手机销量分析案例

项目背景 某电商商城随着业务量的发展&#xff0c;积累了大量的用户手机销售订单数据。决策层希望能够通过对这些数据的分析了解更多的用户信息及用户的分布&#xff0c;从而可以指导下一年的市场营销方案以及更加精准的定位市场&#xff0c;进行广告投放。 数据说明 数据时…

JAVA基础02-Java语言基础以及编译准备工作

什么是JAVA语言 Java是一门面向对象的编程语言&#xff0c;不仅吸收了C语言的各种优点&#xff0c;还摒弃了C里难以理解的多继承、指针等概念&#xff0c;因此Java语言具有功能强大和简单易用的两个特征。 &#xff08;可以编写桌面应用程序、Web应用程序、分布式系统和嵌入式…

洛谷P1000超级玛丽游戏题解[Python, Rust, Go]

题目 打印超级玛丽字符图像 小技巧 直接复制题目的超级玛丽符号首行会有空格问题&#xff0c;一直AC不过&#xff0c;一行一行地复制就OK了&#x1f44c;。 Rust 题解 fn main() {println!(" ********************####....#.#..###.....##....###...…

什么是 Nginx?(一)

前段时间在网上看到一个有意思的话题&#xff1a;只知道 Nginx 牛逼&#xff0c;却不知道它怎么支持百万并发&#xff1f; 无论是运维、开发、测试&#xff0c;Nginx 技术栈的学习总是必不可少的&#xff0c;只是不同的岗位掌握的深度与广度不同而已。 Nginx 是开源、高性能、…

MATLAB科研绘图与学术图表绘制从入门到精通

&#x1f482; 个人网站:【 摸鱼游戏】【神级代码资源网站】【工具大全】&#x1f91f; 一站式轻松构建小程序、Web网站、移动应用&#xff1a;&#x1f449;注册地址&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交…

算法学习——LeetCode力扣图论篇1(797. 所有可能的路径、200. 岛屿数量、695. 岛屿的最大面积)

算法学习——LeetCode力扣图论篇1 797. 所有可能的路径 797. 所有可能的路径 - 力扣&#xff08;LeetCode&#xff09; 描述 给你一个有 n 个节点的 有向无环图&#xff08;DAG&#xff09;&#xff0c;请你找出所有从节点 0 到节点 n-1 的路径并输出&#xff08;不要求按特…

golang语言系列:Web框架+路由 之 Gin

云原生学习路线导航页&#xff08;持续更新中&#xff09; 本文是golang语言学习系列&#xff0c;本篇对Gin框架的基本使用方法进行学习 1.Gin框架是什么 Gin 是一个 Go (Golang) 编写的轻量级 http web 框架&#xff0c;运行速度非常快&#xff0c;如果你是性能和高效的追求者…

java项目基于Springboot和Vue的高校心理教育辅导系统的设计与实现

今天要和大家聊的是基于Springboot和Vue的高校心理教育辅导系统的设计与实现 &#xff01;&#xff01;&#xff01; 有需要的小伙伴可以通过文章末尾名片咨询我哦&#xff01;&#xff01;&#xff01; &#x1f495;&#x1f495;作者&#xff1a;李同学 &#x1f495;&…

C++完美转发(适合小白)

我们知道&#xff0c;C中有左值引用和右值引用&#xff0c;首先我们要知道什么是左值什么是右值。 左值&#xff1a;表达式结束后依然存在的持久对象。左值可以出现在赋值语句的左边或右边。例如&#xff0c;变量和函数返回的引用都是左值。左值通常有持久的地址&#xff0c;可…

Python深度学习034:cuda的环境如何配置

文章目录 1.安装nvidia cuda驱动CMD中看一下cuda版本:下载并安装cuda驱动2.创建虚拟环境并安装pytorch的torch_cuda3.测试附录1.安装nvidia cuda驱动 CMD中看一下cuda版本: 注意: 红框的cuda版本,是你的显卡能装的最高的cuda版本,所以可以选择低于它的版本。比如我的是11…

Go项目结构整洁实现|GitHub 3.5k

一、前言 hi&#xff0c;大家好&#xff0c;这里是白泽。今天给大家分享一个GitHub &#x1f31f; 3.5k 的 Go项目&#xff1a;go-backend-clean-arch https://github.com/amitshekhariitbhu/go-backend-clean-architecture 这个项目是一位老外写的&#xff0c;通过一个 HTT…

HTML基本元素

文章目录 如何制作标题如何制作文字如何做粗体字检查我们程序码给输出文字添加属性 HTML 一个HTML标签包含着&#xff1a; 起始标签&#xff1a;它包含了元素的名字&#xff0c;夹在一对 <、>&#xff08;尖括号&#xff09;之间。它指明元素从何处开始生效。结束标签&am…

《Git版本控制管理》笔记

第三章 git --version查看版本号git --help查看帮助文档裸双破折号分离参数 git diff -w master origin – tools/Makefile将当前目录或任何目录转化为Git版本库 git init 初始化之后项目目录中&#xff0c;有名为.git的文件git status 查看git状态git commit 提供日志消息和作…

整型之韵,数之舞:大小端与浮点数的内存之旅

✨✨欢迎&#x1f44d;&#x1f44d;点赞☕️☕️收藏✍✍评论 个人主页&#xff1a;秋邱’博客 所属栏目&#xff1a;人工智能 &#xff08;感谢您的光临&#xff0c;您的光临蓬荜生辉&#xff09; 1.0 整形提升 我们先来看看代码。 int main() {char a 3;char b 127;char …

Java基础核心Map

在Java中&#xff0c;Map是一种用于存储键值对&#xff08;key-value pairs&#xff09;的集合类型。它提供了一种将键映射到值的方式&#xff0c;其中每个键在Map中都是唯一的。Map接口是java.util包中的一部分。 常用实现类&#xff1a; HashMap: 基于哈希表实现的Map&#…

java中split(“.“)失效问题

来源&#xff1a;比较版本号_牛客题霸_牛客网 在写到这道算法题的时候&#xff0c;发现一个问题&#xff0c; String[] leftversion1.split("."); 返回结果为空&#xff0c;经过查阅得知&#xff0c;是split中的正则表达式里的问题&#xff0c;这个 . 代表的意思是…

KIMI官方精选提示词,好牛的感觉啊啊啊!

晚上好&#xff0c;我是云桃桃。一个希望帮助更多朋友快速入门 WEB 前端的程序媛。 云桃桃 1枚程序媛&#xff0c;大专生&#xff0c;2年时间从1800到月入过万&#xff0c;工作5年买房。 分享成长心得。 255篇原创内容-公众号 后台回复“前端工具”可获取开发工具&#xff0…