C++:C++11新特性---右值引用

news2024/11/24 5:18:26

文章目录

  • 初始化方式
  • 显示查看类型
  • initializer_list
  • decltype
  • 左值引用和右值引用
    • move
    • 左右值引用的场景
  • 万能引用和完美转发

本篇总结C++11新特性

初始化方式

C++11对参数列表的初始化有了更明确的定义,可以这样进行定义

// 列表初始化
void test1()
{
	// 旧版本
	int x = 0;
	// 新版本
	int y{ 0 };
	int z = { 0 };
	int arr[]{ 10,20,30 };
	int* pa = new int[5]{ 1,2,3,4,5 };
}

在对类的赋值的时候,也可以这样进行赋值

struct t
{
	int a;
	int b;
};

void test2()
{
	t* pt = new t[5]{ 1,2 };
}

显示查看类型

// 查看类型
void test3()
{
	int a = 1;
	cout << typeid(a).name() << endl;
	auto it = map<int, int>().begin();
	cout << typeid(it).name() << endl;
}

运行结果:

int
class std::_Tree_iterator<class std::_Tree_val<struct std::_Tree_simple_types<struct std::pair<int const ,int> > > >

initializer_list

这是什么呢?如何理解这个类型?先看一下在什么场景中会出现这个东西

void test4()
{
	auto lit = { 1,2,3,4 };
	cout << typeid(lit).name() << endl;
}

那么这个东西是干什么的呢?有什么用呢?

在这里插入图片描述
在C++11中,对于STL的各类容器的构造函数中,新增了这样的构造方式,有点类似于一个数组,它里面可以存储任意类型的数据,然后可以交给vector来实现构造,因此下面就要对{}进行一个对比

void test5()
{
	// 利用initializer_list进行初始化
	vector<int> v{ 1,2,3,4,5 };
	auto lit = { 1,2,3,4 };
	vector<int> vc(lit);

	// 调用参数初始化列表进行初始化
	int arr[]{ 1,2,3,4,5 };
}

这两种写法看似,但是实际上底层是完全不同的两种实现的方式

decltype

// decltype的一些使用使用场景
template<class T1, class T2>
void F(T1 t1, T2 t2)
{
	decltype(t1 * t2) ret;
	cout << typeid(ret).name() << endl;
}

void test6()
{
	const int x = 1;
	double y = 2.2;
	decltype(x * y) ret;
	decltype(&x) p;
	cout << typeid(ret).name() << endl;
	cout << typeid(p).name() << endl;
	F(1, 'a');
}

简单来说,就是可以把你要新定义的一个类型进行人为的定义,定义成一个你想让它变成的类型,使用场景也不算多,但是可以这样进行使用

左值引用和右值引用

首先要解决一个问题,什么是左值?

之前可能会说,左值就是等号左边的值,这肯定是不对的,左值是一个表示数据的表达式,可以获取它的地址,也可以进行赋值,通常来说它出现在赋值符号的左边,右值不能出现在赋值符号的左边,定义的时候,const修饰符修饰的变量不可以对它进行赋值,但是可以对它取地址,因此,可以说左值引用就是给左值的引用,给左值取别名

常见的左值

int* p = new int(0);
int b = 1;
const int c = 2;

因此我们说,可以对这些起一个别名

int*& rp = p;
int& rb = b;
const int& rc = c;
int& pvalue = *p;

常见的右值

10;
x + y;
fmin(x, y);

而对这些起别名就是右值引用

int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x, y);

对于右值引用来说,可以理解为,右值本身是不能取地址的,但是给右值取别名之后,右值就会被存储到某些地方,此时就可以取到它的地址

左值引用只能引用左值,不能引用右值,如果想引用右值需要用const

int main()
{
  // 左值引用只能引用左值,不能引用右值。
  int a = 10;
  int& ra1 = a;  // ra为a的别名
  //int& ra2 = 10;  // 编译失败,因为10是右值
  // const左值引用既可引用左值,也可引用右值。
  const int& ra3 = 10;
  const int& ra4 = a;
  return 0;
}

move

右值只能引用右值,不能引用左值,但是右值可以引用move之后的左值

void test7()
{
	// 右值引用只能右值,不能引用左值。
	int&& r1 = 10;
	// error C2440: “初始化”: 无法从“int”转换为“int &&”
	// message : 无法将左值绑定到右值引用
	int a = 10;
	//int&& r2 = a;
	// 右值引用可以引用move以后的左值
	int&& r3 = std::move(a);
}

左右值引用的场景

那左右值引用能干啥呢?

左值引用

对比下面两种传参方式

void func(const string& str);
void func(string str);

从传参的效率上就不一样了,对于传引用来说,每次传参的代价是很低的,只需要把变量的地址给过去就可以了,但是对于传值来说就不一样了,传参的代价是相当高的,需要把原始的参数拷贝给新的值

也就是说,左值引用做参数减少了拷贝,提高效率的使用场景和价值

左值引用的缺陷

那左值引用有什么缺陷呢?

第一个是,当函数返回的对象是一个局部对象的时候,是不可以使用传引用返回的,因为这个变量出了作用域就被销毁了,因此不能使用传左值引用,只能进行传值返回,例如下面的场景

	mystring::string to_string(int x)
	{
		mystring::string ret;
		while (x)
		{
			int val = x % 10;
			x /= 10;
			ret += ('0' + val);
		}
		reverse(ret.begin(), ret.end());
		return ret;
	}

此时的string是一个局部变量,如果使用传引用返回会访问一个不存在的地址,这是不被允许的,但如果采用传值返回,又会导致增加了拷贝构造的次数,并且可能还是两次

在新的编译器中,函数得到的这个string在返回的时候,会直接赋给新的值,调用一次拷贝构造,而在旧一点的编译器中,从函数的返回值会先拷贝构造一次到一个临时常量,再从这个临时常量拷贝构造一次到外部定义的string中

而使用移动构造,可以解决这个问题,编译器会默认使用最匹配的参数进行调用,因此会优先使用移动构造

搭配move函数

move函数的作用,就是单纯的把左值转换成右值引用,由此来实现移动语义

那这样有什么用呢?该如何理解这个意思呢?

如果有下面的代码:

string s2(s1)

这样的语句代表的含义是,调用拷贝构造,这里的s1是一个左值

但是如果要是改成这样

string s2(move(s1))

这样就不一样了,把s1放move函数中,这样就可以把s1当成一个右值,而右值是可以调用移动构造的,这样就可以不用调用拷贝构造浪费空间,而是可以直接的把值置换到我们所需要的s2里面,但是这样其实是不好的,这样会导致,虽然确实把s2的值填充了,但是却把s1的值架空了

简单来说,移动构造就是把资源全部偷过来,把原来的资源都架空

再举一个例子:

int main()
{
list<string> lt;
string s1("1111");
// 这里调用的是拷贝构造
lt.push_back(s1);
// 下面调用都是移动构造
lt.push_back("2222");
lt.push_back(std::move(s1));
return 0;
}

如果只是简单的调用s1,那么s1会被当成是一个左值,而左值会调用的是拷贝构造,但是如果把它强制转换成右值,那么就会调用的是移动构造,很明显,移动构造的使用成本是要比拷贝构造低很多的

万能引用和完美转发

前面关于右值引用中和前面有比较大不同的一点就是出现了&&符号,如果把这个符号看成是右值引用的标识符,也是不对的,C++11在模板中也新增了关于&&符号,这个符号代表的是万能引用,而不是右值引用,简单来说就是,既能接收左值也能接收右值

模板的万能引用只是提供了一个可以接收左值和右值的能力,一般来说是不可以两个都接收的
实际的使用中,引用类型的作用会限制接收的类型,会变成左值,而如果想要在传递的过程中保持右值的属性,就需要用到万能引用和完美转发

下面做一个实验来验证功能

void check(int& t)
{
	cout << "左值引用" << endl;
}

void check(int&& t)
{
	cout << "右值引用" << endl;
}

void check(const int& t)
{
	cout << "const左值引用" << endl;
}

void check(const int&& t)
{
	cout << "const右值引用" << endl;
}

void test8()
{
	const int a = 1;
	const int& b = 1;
	int c = 0;
	check(a);
	check(b);
	check(c);
	check(1);
	check(move(a));
	check(move(b));
}

在这里插入图片描述

结果也是符合预期的,把上述代码进行适当更改

void check(int& t)
{
	cout << "左值引用" << endl;
}

void check(int&& t)
{
	cout << "右值引用" << endl;
}

void check(const int& t)
{
	cout << "const左值引用" << endl;
}

void check(const int&& t)
{
	cout << "const右值引用" << endl;
}

void func1(int t)
{
	check(t);
}

void test8()
{
	const int a = 1;
	const int& b = 1;
	int c = 0;
	func1(move(a));
}

此时运行结果是左值引用,这是为什么?其原因是进入func1函数后,函数参数就从右值变成左值了,此时它的右值属性就会消失,因此出现了下面的用法:

template<class T>
void func1(T&& t)
{
	check(t);
}

此时运行结果是const左值引用,这说明它保持了const属性,但是依旧没有保持右值的属性,右值的属性依旧被退化成左值了

再进行改良

template<class T>
void func1(T&& t)
{
	check(forward<T>(t));
}

此时运行结果就是const右值引用了,而新增的这个forward其实就是完美转发,它可以保障原来的属性,把原来这个值的属性转发出去,严格意义来说保持的是左值和右值,而如果没有万能引用运行结果依旧会丢失const属性

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

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

相关文章

前端大屏适配几种方案

记录一下前端大屏的几种适配方案。 我们是1920*1080的设计稿。 目录 目录 一、方案一&#xff1a;remfont-size 二、方案二&#xff1a;vw&#xff08;单位&#xff09; 三、方案三&#xff1a;scale&#xff08;缩放&#xff09;强烈推荐 1、根据宽度比率进行缩放 2、动…

点云凹凸缺陷检测 最高层点云 点云聚类

文章目录 0. 数据说明1. 凹凸缺陷基本内容2. 详细检测思路 结果&#xff1a; 0. 数据说明 如上图所示&#xff0c;需要检测的内容为红色框内标出的缺陷部分。简单示例如下红色线条。 但是&#xff0c;由于噪声的影响&#xff0c;点云的平面度并不好&#xff0c;且横梁边缘处存…

4/150:寻找两个正序数组的中位数⭐

题目&#xff1a;寻找两个正序数组的中位数 给定两个大小分别为 m 和 n 的正序&#xff08;从小到大&#xff09;数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。 算法的时间复杂度应该为 O(log (mn)) 。 题解1&#xff1a;暴力 暴力思路简介&#xff0c;…

在数据库中进行表内容的修改(MYSQL)

根据表中内容&#xff0c;用命令语句创建数据库&#xff0c;表格&#xff0c;以及插入&#xff0c;修改&#xff0c;删除表格中的内容。 创建数据库&#xff1a;zrzy mysql> create database zrzy; 引用zrzy数据库&#xff1a; mysql> use zrzy; 创建student_info表&…

蓝桥杯day02——移动机器人

1.题目 有一些机器人分布在一条无限长的数轴上&#xff0c;他们初始坐标用一个下标从 0 开始的整数数组 nums 表示。当你给机器人下达命令时&#xff0c;它们以每秒钟一单位的速度开始移动。 给你一个字符串 s &#xff0c;每个字符按顺序分别表示每个机器人移动的方向。L 表…

随时随地,打开浏览器即可体验的在线PS编辑器

即时设计 即时设计是国产的专业级 UI 设计工具&#xff0c;不限平台不限系统&#xff0c;在浏览器打开即用&#xff0c;能够具备 Photoshop 的设计功能&#xff0c;钢笔、矢量编辑、矩形工具、布尔运算等设计工具一应俱全&#xff0c;是能够在线使用的 Photoshop 免费永久工具…

Redis实战命令

实战命令 单值缓存 set key value get key 对象缓存 &#xff08;1&#xff09;set user:1 value(json格式) &#xff08;2&#xff09;mset user:1:name junfeng user:1:age 18 mget user:1:name user:1:age 分布式锁 分布式锁解决了什么问题&#xff1f; 分布式锁解…

63 权限提升-Linux脏牛内核漏洞SUID信息收集

目录 演示案例:Linux提权自动化脚本利用-4个脚本Linux提权SUID配合脚本演示-AliyunLinux提权本地配合内核漏洞演示-MozheLinux提权脏牛内核漏洞演示-Aliyun&#xff0c;Vulnhub涉及资源: linux提权相对windows提权方法相对少一些&#xff0c;linux提权方法相对来讲有七八种方式…

WIFI HaLow技术引领智能互联,打破通信限制

在过去十年里&#xff0c;WIFI技术已在家庭和企业中建立起了庞大的网络&#xff0c;连接了数十亿智能互联设备&#xff0c;促进了信息的迅速传递。然而&#xff0c;当前的WIFI标准存在一些挑战&#xff0c;包括协议范围的限制和整体功能的受限&#xff0c;导致在较远距离进行通…

Python开发——工具篇 Pycharm的相关配置,Python相关操作 持续更新

前言 本篇博客是python开发的工具篇相关&#xff0c;介绍pycharm的使用和相关配置&#xff0c;收录python的相关操作&#xff0c;比如如何启动jupyter。 目录 前言引出Pycharmpycharm如何不同等级日志显示不同颜色设置不同pycharm的python环境 Python操作如何启动Jupyter 总结…

SpringBoot : ch11 整合RabbitMQ

前言 在当今的互联网时代&#xff0c;消息队列成为了构建高可靠、高性能系统的重要组件之一。RabbitMQ作为一个可靠、灵活的消息中间件&#xff0c;被广泛应用于各种分布式系统中。 本篇博客将介绍如何使用Spring Boot整合RabbitMQ&#xff0c;实现消息的发送和接收。通过这种…

【测试开发】web自动化测试框架需求设计 !

1、实现目的 基于BS架构&#xff0c;模拟用户&#xff08;鼠标、键盘&#xff09;操作&#xff0c;达到快速、重复执行测试用例&#xff1b; 便于回归测试&#xff0c;快速覆盖主线用例或功能&#xff1b; 线上或线下巡检测试&#xff0c;结合持续集成&#xff0c;及时发现运…

万户协同办公平台ezoffice SendFileCheckTemplateEdit.jsp接口存在SQL注入漏洞 附POC

@[toc] 万户协同办公平台ezoffice SendFileCheckTemplateEdit.jsp接口存在SQL注入漏洞 附POC 免责声明:请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失,均由使用者本人负责,所产生的一切不良后果与文…

解决electron-builder打包不成功只能输出tgz文件的问题

现象&#xff1a; 对应项目里配的指令&#xff1a; 但就是死活不成功&#xff0c;只能输出tgz压缩文件。 最后一咬牙下载了官方的electron-quick-start拿来试试&#xff0c;结果还是一样。 一时间没想法了。 后来突然脑袋灵光一闪&#xff0c;去他妈的直接npx 执行看看&…

geemap学习笔记016:获取图像的基本属性和描述性信息

前言 遥感数据中通常包含众多信息&#xff0c;例如图像获取的时间、云覆盖量、以及每个波段的最大值最小值等等。 1 导入库并显示地图 import ee import geemapMap geemap.Map() Map2 添加图像数据 centroid ee.Geometry.Point([-122.4439, 37.7538]) #创建一个点坐标lan…

数组filter()方法的使用

输入价格后失去焦点就展示符合条件的商品&#xff0c;没有符合条件的商品就弹框提示 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-…

法线制作神器crazybump 和 Normal Map Generator

UE4系列文章目录 文章目录 UE4系列文章目录前言一、crazybump是什么&#xff1f;二、Normal Map Generator是什么&#xff1f; 前言 我们在使用UE4、UE5制作材质时&#xff0c;经常会遇到使用法线图。使用深度图生成法线图&#xff0c;一般会使用photoShop等软件&#xff0c;对…

07-学成在线修改/查询课程的基本信息和营销信息

修改/查询单个课程信息 界面原型 第一步: 用户进入课程列表查询页面,点击编辑按钮编辑课程的相关信息 第二步: 进入编辑界面显示出当前编辑课程的信息,其中课程营销信息不是必填项,修改成功后会自动进入课程计划编辑页面 查询课程信息 请求/响应数据模型 使用Http Client测…

Kubernetes入门篇

文章目录 &#x1f50a;博主介绍&#x1f964;本文内容Kubernetes是什么&#xff1f;为什么需要 Kubernetes&#xff0c;它能做什么&#xff1f;Kubernetes 版本进化Kubernetes 概念Kubernetes 中 3 个重要概念 &#x1f4e2;文章总结&#x1f4e5;博主目标 &#x1f50a;博主介…

如何优化索引?

前缀索引 这个操作是为了减少索引长度&#xff0c;即占用空间的。这样一个页可以多存一些索引&#xff0c;查找时候就会更快了。但是前缀索引有俩缺点&#xff0c;一个是ORDER BY或GROUP BY时候没法用&#xff0c;另一个是没法用做覆盖索引&#xff08;因为索引本来自己都不全…