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

文章目录

  • 初始化方式
  • 显示查看类型
  • 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.hqwc.cn/news/225645.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

采购申请的库存地点和MRP2的库存地点的关系

F类材料的采购申请的库存地点&#xff0c;带外部采购申请仓储地点 X类材料的采购申请的库存地点,从MRP2的生产仓储地点自动带到计划订单再到生产工单再到采购申请。

俄罗斯山东同乡会会长赵卫星一行莅临百华鞋业考察交流

11月25日&#xff0c;俄罗斯山东同乡会会长赵卫星等专家与市侨联副主席李咏梅&#xff0c;县商务局局长尹纪付、县侨联主席刘志峰、县人才集团总经理刘杰等一行莅临百华鞋业考察调研。百华鞋业总经理郭兴梅全程陪同。 百华鞋业总经理郭兴梅对赵卫星会长一行领导的到来表示热烈…

openmp 处理数据竞争的问题 reduction

类似 多线程竞争&#xff0c;需要加锁来保护类似&#xff0c;但实现原理不同&#xff0c;reduction 并不会像多线程原子操作那样影响效率&#xff0c;因为它使用了高等代数里的单位元和结合律思想&#xff0c;为每个线程定义一个单位元&#xff0c;开始 分段积累运算操作。 1, …

RabbitMQ之延迟消息

文章目录 前言一、死信交换机二、延迟消息死信交换机实现延迟消息图解流程 DelayExchange插件实现延迟消息安装插件声明延迟交换机发送延迟消息 总结 前言 死信交换机、延迟消息 一、死信交换机 当一个队列中的消息满足下列情况之一时&#xff0c;可以成为死信&#xff08;dea…

CentOS 7 部署 MariaDB 的 2 种方法

有两种安装 MariaDB 服务器的方法。您可以安装 CentOS 7 存储库中可用的默认版本&#xff0c;也可以通过手动添加 MariaDB 存储库来安装最新版本。 如果安装过MariaDB或MySQL&#xff0c;使用以下命令彻底删除它们: yum remove mariadb* yum remove mysql* 方法一: 使用 Yum…

Python assert断言函数及用法与while循环详解

Python assert断言函数及用法 断言语句和 if 分支有点类似&#xff0c;它用于对一个 bool 表达式进行断言&#xff0c;如果该 bool 表达式为 True&#xff0c;该程序可以继续向下执行&#xff1b;否则程序会引发 AssertionError 错误。 例如如下程序&#xff1a; s_age inpu…

亚马逊,shein,temu如何避免爆品评分低被强制下架

近期&#xff0c;一些Temu卖家反映产品下架问题&#xff0c;无论是日出千单的爆品还是其他商品&#xff0c;都有可能面临下架的风险。这其中最主要的原因之一是产品质量问题&#xff0c;导致消费者差评较多&#xff0c;评分降至4.2分或4.0分以下时&#xff0c;平台可能会强制下…

EDA实验-----正弦信号发生器的设计(Quartus II )

目录 一、实验目的 二、实验仪器 三、实验原理 四、实验内容 五、实验步骤 六、注意事项 七、实验过程&#xff08;操作过程&#xff09; 1.定制LPM_ROM模块 2.定制LPM_ROM元件 3.计数器定制 4.创建锁相环 5.作出电路图 6.顶层设计仿真 一、实验目的 学习使用Ver…

Matlab R2022b 安装成功小记

Matlab R2022b 安装成功小记 前言一、 下载链接二、 安装过程小记 叮嘟&#xff01;这里是小啊呜的学习课程资料整理。好记性不如烂笔头&#xff0c;今天也是努力进步的一天。一起加油进阶吧&#xff01; 前言 windows 10系统之前安装过Matlab R2010b做基础研究&#xff0c;最…

每日一练 | 华为认证真题练习Day138

1、IPv6地址FE80::2EO:FCFF:FE6F:4F36属于哪一类&#xff1f; A. 组播地址 B. 任播地址 C. 链路本地地址 D. 全球单播地址 2、如果IPv6的主机希望发出的报文最多经过10台路由器转发&#xff0c;则应该修改IPv6报文头中的哪个参数&#xff1f; A. Next Header B. Version …