1. Lvalue and rvalue in C++
Myth: Left values are on the left side of the equal sign, and right values are on the right side of the equal sign.
Definition in C++ 11: An lvalue expression represents the identity (location in memory) of an object, while an rvalue expression represents the value (contents) of the object.
Both lvalues and rvalues are for expressions, lvalues are persistent, and rvalues are ephemeral: lvalues persist after the expression ends, and rvalues are destroyed after the expression ends.
The method of distinguishing lvalue and rvalue: see if the address operation can be performed, if so, it is an lvalue, otherwise it is an rvalue.
Note: Where an rvalue is required, an lvalue can be used instead, but an rvalue cannot be used as an lvalue (that is, a position).
example:
If defined as follows:
- int a = 10;
- int b = 20;
- int *p = &a;
- vector<int> T;
- T.pushback(1);
- string s1 = “Hello”;
- string s2 = "World";
- constint &m =1;
ask:
Are a,b,a+b,a++,++a,p,*p,T[0],100,string("Hello"),s1,s1+s2,m lvalue or rvalue respectively?
answer:
a,b是变量,变量可以看做只有运算对象而没有运算符的表达式,变量表达式都是左值。事实上,变量a,b均是长久的,在生命周期结束才被销毁,且我们能够对a,b进行取地址操作。故a,b均为左值。
a+b是临时变量,在该表达式结束时就被摧毁,且不能对其进行取地址操作,因此a+b为右值。
a++的作用机理是先将a的值拷贝到一个临时变量中,然后将这里临时变量加1,最终返回的是这个临时变量,因此a++为右值。
++a的作用机理是在原数据a上直接加1,最终返回的是原来的那个对象(只不过值加了1),因此++a为左值。
p表示的是指向a的指针,它也是长久的,并且我们能对其进行取地址操作,得到的是指向a的指针的地址。因此p为左值。
*p与a等价,也为左值。
T[0]返回容器T中第一个元素的引用,这是一个int型变量,是长久的,并且能对其进行取地址操作,因此T[0]为左值。
100是个常量,在使用过后就会销毁,并且不能对其进行取地址操作,因此100为右值。
string("Hello")与100类似,也是个常量,在使用过后就会销毁,并且不能对其进行取地址操作,因此string("Hello")为右值。
s1是string类型的变量,与a,b类似,是长久的,并且可以进行取地址操作。因此s1是左值。
s1+s2与a+b类似,是临时变量,在表达式结束就被摧毁,并且不能对其进行取地址操作。因此s1+s2是右值。
m是一个常量类型1的const左值引用,但它本身是一个变量表达式,因此m是左值。
二、左值引用和右值引用
左值引用符:&
右值引用符:&&
左值引用不能绑定到右值对象上,右值引用也不能绑定到左值对象上。
由于右值引用只能绑定到右值对象上,而右值对象又是短暂的、即将销毁的。也就是说右值引用有一个重要性质:只能绑定到即将销毁的对象上。
左值、右值引用的几个例子:
- int i = 42;//如前所述,i是一个左值对象
- int &r = i;//正确,左值引用绑定到左值对象i
- int &&rr = i;//错误,右值引用绑定左值对象
- int &r2 = i * 42;//错误,如前所述i*42是临时变量,是右值,而&r2是左值引用
- int &&rr2 = i * 42;//正确,右值引用绑定右值对象
注意:以上绑定规则有一个例外,如果左值引用是const类型的,则其可以绑定到右值对象上。
对于一个左值,若想使用其右值引用,我们可以用move函数:
- int &&rr3 = std::move(rr1);//正确,显式使用rr1的右值引用
一、c++中的左值和右值
误区:左值位于等号左边,右值位于等号右边。
C++11中的定义:左值表达式表示的是一个对象的身份(在内存中的位置),而右值表达式表示的是对象的值(内容)。
左值和右值都是针对表达式而言的,左值是持久的,右值是短暂的:左值在表达式结束后仍然存在,右值在表达式结束后会被销毁。
区分左值和右值的方法:看能不能进行取地址操作,若能,则为左值,否则为右值。
注意:在需要右值的地方可以用左值来代替,但是不能把右值当成左值(也就是位置)使用。
例子:
若有如下定义:
- int a = 10;
- int b = 20;
- int *p = &a;
- vector<int> T;
- T.pushback(1);
- string s1 = “Hello”;
- string s2 = "World";
- const int &m =1;
问:
a,b,a+b,a++,++a,p,*p,T[0],100,string("Hello"),s1,s1+s2,m分别是左值还是右值?
答:
a,b是变量,变量可以看做只有运算对象而没有运算符的表达式,变量表达式都是左值。事实上,变量a,b均是长久的,在生命周期结束才被销毁,且我们能够对a,b进行取地址操作。故a,b均为左值。
a+b是临时变量,在该表达式结束时就被摧毁,且不能对其进行取地址操作,因此a+b为右值。
a++的作用机理是先将a的值拷贝到一个临时变量中,然后将这里临时变量加1,最终返回的是这个临时变量,因此a++为右值。
++a的作用机理是在原数据a上直接加1,最终返回的是原来的那个对象(只不过值加了1),因此++a为左值。
p表示的是指向a的指针,它也是长久的,并且我们能对其进行取地址操作,得到的是指向a的指针的地址。因此p为左值。
*p与a等价,也为左值。
T[0]返回容器T中第一个元素的引用,这是一个int型变量,是长久的,并且能对其进行取地址操作,因此T[0]为左值。
100是个常量,在使用过后就会销毁,并且不能对其进行取地址操作,因此100为右值。
string("Hello")与100类似,也是个常量,在使用过后就会销毁,并且不能对其进行取地址操作,因此string("Hello")为右值。
s1是string类型的变量,与a,b类似,是长久的,并且可以进行取地址操作。因此s1是左值。
s1+s2与a+b类似,是临时变量,在表达式结束就被摧毁,并且不能对其进行取地址操作。因此s1+s2是右值。
m是一个常量类型1的const左值引用,但它本身是一个变量表达式,因此m是左值。
二、左值引用和右值引用
左值引用符:&
右值引用符:&&
左值引用不能绑定到右值对象上,右值引用也不能绑定到左值对象上。
由于右值引用只能绑定到右值对象上,而右值对象又是短暂的、即将销毁的。也就是说右值引用有一个重要性质:只能绑定到即将销毁的对象上。
左值、右值引用的几个例子:
- int i = 42;//如前所述,i是一个左值对象
- int &r = i;//正确,左值引用绑定到左值对象i
- int &&rr = i;//错误,右值引用绑定左值对象
- int &r2 = i * 42;//错误,如前所述i*42是临时变量,是右值,而&r2是左值引用
- int &&rr2 = i * 42;//正确,右值引用绑定右值对象
注意:以上绑定规则有一个例外,如果左值引用是const类型的,则其可以绑定到右值对象上。
For an lvalue, to use its rvalue reference, we can use the move function:
- int &&rr3 = std::move(rr1); //correct, use rr1's rvalue reference explicitly