看网上很多关于左右值的文章,感觉写得都不是很全,所以想总结下:
【一、左右值基本介绍及3种构造表达式】
左值:左值是可以放在赋值号左边可以被赋值的值;左值必须要在内存中有实体;
右值:当在赋值号右边取出值赋给其他变量的值;右值可以在内存也可以在CPU寄存器;
左值引用:左值变量的别名,构建表达式:type & 引用名 = 左值表达式;
右值引用:右值变量的别名,构建表达式:type && 引用名 = 右值表达式;
下面主要讨论集中表达式:
1、赋值表达式 type 左值 = 右值表达式;左值 = 右值表达式;
2、左值绑定表达式 type & 左值引用 = 左值;
3、右值绑定表达式 type && 右值引用 = 右值表达式;
上面三种表达式必须符合其规定;
个人认为:当变量放到表达式的不同位置时,编译器识别的类型不同,可能是左值,可能是右值;同样左值引用和右值引用可能被当成左值或者右值来使用;
例如:
int a = 3;//在int a = 3这种表达式中,表示 “左值 = 右值表达式(3)”;a被识别为左值;符合(1)
int b = a;//变量b,在int b = a中,b为左值,a是左值,在这种表达式里面放右边就变成了右值,所以“ 左值 = 右值表达式“正确;符合(1)
a = 4;//同int a = 3的解释;左值 = 右值表达式;符合(1)
int && c = 3;//“右值引用 = 右值表达式“,这种表达式正确,符合编译器规定;符合(3)
a = c;//“左值 = 右值表达式”,具名右值引用c相当于左值,左值放到赋值表达式右边相当于右值;符合(1)
int && d = a;//错误,按照(3),右边必须是右值表达式,此时编译器只能推断a是左值变量,不会推断为一个表达式, 右值引用 = 左值,不存在这种表达式;
int && d = a +0;//正确,此时a+0被推断为表达式,所以a+0是右值表达式【好像表达式都是右值表达式吧】;
int & e = a;//正确,符合(2)
int & f = a + 0;//错误,a+0是右值表达式,不符合(2):左值引用= 左值 的规定;
注:常量引用以后再分析,可以把它认为是 const type & 引用 = 左值 or 右值表达式,所以它是综合了(2)(3)的万能表达式;
【二、左右值、引用在函数传参时的传参方法】
下面讨论下在函数传参时,左右值引用传递的规则:
函数传递参数时,传入左值引用,右值引用
下面用一个例子来说明:
void use_rreference (int&& p)
{
std::cout << p << std::endl;
}
void use_value (int p)
{
std::cout << p << std::endl;
}
void use_lreference (int& p)
{
std::cout << p << std::endl;
}
int _tmain (int argc, _TCHAR* argv[])
{
int lvalue = 3;
int &lref = lvalue;
int &lrefref = lref;//正确的,可以编译过
int &&rref = 3;
//use_value由于形参是int,所以编译器使用(1)(int tmp = xxx )即:左值 = 右值表达式 来生成函数的参数;
use_value (lvalue); //正确,此时函数需要构造一个零时变量int tmp=value;此时lvalue放到此表达式右边,做为右值使用;
use_value (3); //正确,int tmp=3;右值表达式给左值;
use_value (lref);//正确,int tmp=lref;lref相当于左值;
use_value (rref); //正确,int tmp=rref;左值引用;
//use_lreference由于形参是int&,所以编译器使用(2)(int &tmp=xxx) 即: type & 左值引用 = 左值 来生成入参数;
use_lreference (lvalue); //正确,此时函数需要构造一个零时变量int &tmp=lvalue;lvalue是左值,可以放到这个表达式右边;
use_lreference (3); //错误,此时函数需要构造一个零时变量int &tmp=3?;3是纯右值,不符合;
use_lreference (lref); //正确,此时函数需要构造一个零时变量int &tmp=lref;lref是左值引用,相当于左值,可以绑定给左值引用;
use_lreference (rref);//正确,此时函数需要构造一个零时变量int &tmp=rref;rref是具名右值引用,相当于左值;
//use_rreference由于形参是int &&,所以编译器使用(3)(int &&tmp=xxx)即:type && 右值引用 = 右值表达式 来生成函数的参数;
use_rreference (lvalue); //错误,int &&tmp = lvalue;右值引用无法绑定到左值上;
use_rreference (3);//正确,int &&tmp = 3;纯右值表达式,但是此时,tmp是具名右值引用,函数内部又相当于左值在用了;
use_rreference (lref); //错误,int &&tmp = lref,lref是左值引用,相当于左值,右边值引用无法绑定到左值;
use_rreference (rref); //错误,int &&tmp = rref,rref是具名右值引用,相当于左值,右边值引用无法绑定到左值;
//lrefref 和lref结果一样,就不写了
//总结,函数调用的时候,就是按照其参数的类型,生成对应的 type tmp=实参;表达式来填写参数;对应必须满足左值,右值规定;
return 0;
}
【三、函数模板传参】
函数模板也是函数,所以也是满足2的规定,但是编译器会先按照函数调用参数来设定函数入参类型,然后再按函数入参类型来推断模板参数类型
例如:
template<typename T>
void fun(T t) {
std::cout << t << std::endl;
}
如果调用:
fun(3);
则3是右值表达式,T tmp = 3,所以T是int类型的;
如果调用:
int a = 3;
int & b = a;
fun(b);
则b是左值引用,T tmp = b;b是int类型,则T是int;
对于:
template<typename T>
void fun(T & t) {
std::cout << t << std::endl;
}
如果调用:
fun(3)
编译器报错,原因:3是右值表达式,T & tmp = 3;左值引用不能用右值表达式绑定,不符合(1)(2)(3)中任何一个;
如果调用:
int a = 0;
fun(a);
正确,T &tmp = a;a是左值,左值引用=左值,表达式正确,且推断T为typename a:即int;
如果调用:
int && r = 0;
fun(r);
正确,T &tmp = r;r是具名右值引用,相当于左值,所以 左值引用=左值,正确,推断出T 为int;
对于下面的模板,比较特殊,需要用到引用折叠规则:
template<typename T>
void fun(T && t) {
std::cout << t << std::endl;
}
如果调用:
fun(3);
则,T && tmp = 3;满足右值引用构造表达式,正确,推导出T 为int;
如果调用:
int a = 0;
int & r = a;
fun(r);
则:T&& tmp = r;此时r是左值引用,但是根据引用折叠规则,int& && <=> int &,所以推断T为int&,此时:int& tmp = r;左值引用=左值是可以的,正确;
如果调用:
int && r = 0;
fun(r);
则:T&& tmp = r;r是具名右值引用,相当于左值,推断T为int&,根据引用折叠规则,int &tmp = r; 左值引用=左值,此时T就为int &;
如果调用:
int a = 0;
fun(a);
则 T&& tmp = a;a是左值,根据引用折叠推中,T可以推断为int&即可;
所以对于T&&类型的模板,可能根据调用的不同推断出T为值或左值引用(不可能为右值引用),T&&可能是左值引用或者右值引用;