左值、右值、左值引用、右值引用传参时的判断方法

2019-04-24  本文已影响0人  hijiang

看网上很多关于左右值的文章,感觉写得都不是很全,所以想总结下:

【一、左右值基本介绍及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&&可能是左值引用或者右值引用;

上一篇 下一篇

猜你喜欢

热点阅读