c++左值引用和右值引用,c++ 左值引用
有左值引用为什么还要右值引用?在通常的编码过程中,为了减少数据的拷贝,提高性能,我们一般通过引用传递参数,比如:
void func(const int a){ } int main(){ func(10);//可以int a=20func(a);//可以返回0;}如果在上面的程序中我们去掉了func函数中的const修饰会怎么样?我们发现func(10)被调用;过不去,为什么?
在C中,带有const修改的引用变成了常量左值引用,可以绑定右值。如果去掉const修改,就不是常量左值引用了,所以不能绑定右值。
func(10);编译是失败的,因为没有const修改的函数func表示引用A可以修改,但是func(10);传过来的10不能修改,产生了矛盾,无法编制。
面对这种情况,正确的价值参照的作用就发挥出来了。
右值引用是C 11推出的重要特性之一,和Lambda表达式一样。它的引入解决了C中大量的历史问题,省去了std:vector、std:string等额外的开销。
什么是正确的价值观,那么什么是正确的价值观呢?
根据我们以往的编程经验,一般认为等号左边的值是左值,等号右边的值是右值。然而,事实并非如此。
int a=10int b=a;例如,在异常代码中,如果表达式左边的值被认为是左值,表达式右边的值是右值,那么变量A在第一行代码中是左值,但变量A在第二行代码中变成了右值。
这显然是一个矛盾。那么如何区分左右值呢?
在C Primer中,左右值总结如下:
当一个对象作为正确的值时,使用该对象的值(内容);当一个对象用作左值时,使用该对象的标识(在内存中的位置)。
大概意思是左值有内存地址,生存生命周期长,而右值一般得不到内存地址,生命周期短。以上面的代码为例,
变量A和B都可以通过取地址符号得到具体的内存地址,所以变量A和B都是左值,而10是普通的字面量,取地址符号不能得到具体的内存地址,所以10是右值。
然而,除了字符串的文字值不是右值之外,常见的文字值是右值。比如hello world,这个字符串,我们取地址符号就可以得到地址。
如何在语法中使用正确的值?右值是在左值的基础上加一个符号,即参考右值:
Void func(int a){ //左值引用}void fuc(int a){ //右值引用} int main(){ int a=10;//右值指返回0;}权利价值引用的一个特点是可以延长权利价值的生命周期,如以下程序:
int getX(){ return 10;} int main(){ int a=getX();//错误常量int aa=getX();//正确,常量左边的值是指int b=getX();//右值指返回0;}在上面的程序中,我们通过用right value引用B,延长了函数getX返回值的生命周期。延长临时对象的生命周期并不是这里正确的值引用的最终目的。它的真正目标应该是减少对象复制和提高程序性能。
主页面
main.cppusing命名空间stdclass A { public:A(){ cout constructor endl;} A(const A a) {cout 复制构造函数 endl} ~ A(){ cout destructor endl;}};A getA(){ return A();} int main(){ A a1=getA();cout - endl;a a2=getA();//右值引用,RVO关闭时减少一份。返回0;}以上程序在C 11的基础上关闭RVO优化。使用命令g main . CPP-STD=c11-o main-fno-elite-constructors编译后,执行主可执行文件并找到以下输出:
构造函数复制构造函数析构函数复制构造函数析构函数复制构造函数析构函数复制构造函数析构函数析构函数析构函数通过比较,发现实际使用的是正确的值引用,比普通引用减少了一倍的副本。在这一点上,有些人可能会说RVO优化就足够了。为什么需要正确的价值参考?其实这只是说明正确的价值参考的一个小场景,它的用途远不止于此。
对于复制构造函数来说,移动一个参数是一个左值引用,而不是一些函数返回的临时对象,而且在复制构造函数中,往往会进行深度复制,也就是一般不会销毁实际对象。相反,移动构造器接受一个正确的值,其核心思想是通过传递实参对象的数据来构造目标对象。
也就是说,移动的构造函数将修改参数对象。一般来说,调用移动构造函数后,argument对象的相关变量资源会被转移,原argument的变量会被清空,即该argument不能再使用。所以称之为偷换构造函数比移动构造函数更合适。
那么在什么情况下会出现移动构造的调用呢?比如在C 11的STL容器中,会根据具体情况自动调用移动构造函数,比如下面这个例子:
#include iostream #include vector使用命名空间stdclass { public:a(strings):name(new string(s)){ cout constructor endl;} a (const a a): name(新字符串(* a.name)) {cout 复制构造函数 endl} A(A A){ name=A . name;a.name=nullptr//Empty cout“move constructor”endl;} ~ A(){ cout destructor endl;删除姓名;name=nullptr} public:string * name;};int main(){ vector A vector;vector . push _ back(A( world ));//移动构造函数,如果有,如果没有,调用复制构造函数返回0;}处理除了自动调用STL容器中的移动构造函数,我们还可以通过std:move或者类型转换static_cast手动调用移动构造函数,例如对于上面的A类:
int main(){ A A( hello );a b=STD:move(a);//如果不移动构造函数,会自动调用复制构造函数A C=static _ cast A。//如果不移动构造函数,会自动调用复制构造函数返回0。}注意:像复制赋值操作符的复制构造函数一样,移动构造函数也用于这个移动赋值操作符。因为与参数相关的变量在move语义中通常为空,所以需要注意move赋值操作符避免将自身赋值给自身的情况。
由于右值引用,左值、右值和垂死值在C 11中被更具体地分类。
上图中,我们很容易理解左值和纯右值。如前所述,左值一般有内存地址,生命周期长,是左值,而像整数1,浮点1.1这些是不能通过取地址符号得到的。
特定的内存地址通常是正确的值。让人眼花缭乱的是,这个将死值既可以代表一个左值,也可以代表一个右值。发生了什么事?
万能参考和折叠参考所谓万能参考,就是既能参考左值又能参考右值的参考。例如:
``无效测试(整数){
//右值引用,有明确的类型
}
无效测试(int ){
//左值引用
}
模板类型名T
无效测试(T ){
//通用引用,因为模板需要类型派生
}
int getNum(){
返回20;
}
int main() {
int num 1=getNum();//右值引用
auto num 2=getNum();//通用引用,类型派生
返回0;
}
在上面的评论中,我们发现,只要发生类型派生,就会被通用引用。在T和auto的初始化过程中,会发生类型派生,所以它们是通用引用。在这个派生过程中,如果初始化的源对象是左值,目标对象将派生出左值引用;相反,如果源对象是一个正确的值,将推导出正确的值引用。在C 11中,有一套引文叠加推导的规则,叫做引文折叠。通过这一套规则,我们可以推导出通用引用的最终类型是什么,如下图:【通用参考推送规则】(https://s2.51cto.com/images/blog/202203/31150158 _ 624551e6d51e871267.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type _ zmfuz 3 poz w5 nagvpdgk=/resize,m_fixed,W_750)从图中可以看出,在推导过程中,只有当实际类型为非引用类型或右值引用类型时,最终推导出的才是右值引用,即#完美转发上面介绍了万能参考,它的一个重要用途就是完美转发。所谓完美转发,就是一个函数模板可以将自己的参数“完美”地转发给内部调用的其他函数,既能准确地转发参数的值,又能保证转发的参数左右值不变。在C 11中,你可以通过使用标准库:void test(int t){
//左值引用
Cout左值endl
}
无效测试(int t){
//右引用
Cout右值endl
}
模板类型名T
void function forward(T T){
//Forwarded,根据传入的值类型调用不同的测试
test(STD:forward T(T));
}
模板类型名T
无效函数正常(T t){
//没有转发,一直调用左值测试。
测试(t);
}
int main() {
int a=20
func normal(1);//右值,但是调用左值的测试
func normal(a);//左侧值
cout-endl;
func forward(1);//右值
func forward(a);//左侧值
返回0;
}
输出:左值
左值右值
左侧值
在上面的例子中,我们发现函数funcNormal 不管传递的是左值还是右值,最终都会调用左值的测试函数,而函数funcForward 根据传递的参数类型是左值还是右值,调用不同的测试函数,这就是完美转发的威力。#推荐阅读【《C++之指针扫盲》】(https://mp.weixin.qq.com/s/ZKJHSMJ43Qhrodfgcqkenq)【《C++之智能指针》】(3359mp.weixin.qq.com/s/MJCQKL _ ombv 89 fxhi(https://mp.weixin.qq.com/s/qH3uOhrC5gUijAWWWIq07g)关注我,一起进步。生活不仅仅是编码!【微信扫码关注】(https://s2.51cto.com/images/blog/202203/31150158 _ 624551e6f2b1e81427.jpg?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type _ zmfuz 3 poz w5 nagvpdgk=/resize,m_fixed,W _ 750),转载请联系作者授权,否则将追究法律责任。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。