1)C++入门级小知识,分享给将要学习或者正在学习C++开发的同学。
2)内容属于原创,若转载,请说明出处。
3)提供相关问题有偿答疑和支持。
左值和右值的概念:
早期的c语言中关于左值和右值的定义:
左值是可以位于赋值运算符"="左侧的表达式(当然,左值也可以位于"="的右侧);
右值是不可以位于赋值运算符 "="左侧的表达式;
区分左值和右值的一个简单办法是:看能不能对表达式取地址,如果能,则为左值、当然也可以充当右值,如果不能为右值。
如下:
int a;
int b;
a = b;
b = a;
以上a和b都可以位于运算符=的左侧,因此a,b都是左值;
int c;
c = 100; //ok
100 = c; //err
以上常量100只能位于运算符=的右侧因此常量100是右值
不过在 C++ 里面,左值和右值不能这样定义。根据《C++ Primer》的说法,左值和右值可以这样区分:
一个表达式是左值还是右值,取决于我们使用的是它的值还是它在内存中的位置(作为对象的身份)。
也就是说一个表达式具体是左值还是右值,要根据实际在语句中的含义来确定。
如下:
int foo=100;
int bar;
// 将 foo 的值赋给 bar,保存在 bar 对应的内存中
// foo 在这里作为表达式是右值;bar 在这里作为表达式是左值
// 但是 foo 作为对象,既可以充当左值又可以充当右值
bar = foo;
因为 C++ 中的对象本身可以是一个表达式,所以这里有一个重要的原则,即
在大多数情况下,需要右值的地方可以用左值来替代,但需要左值的地方,一定不能用右值来替代。
又有一个重要的特点,即
左值存放在对象中,有持久的状态;而
右值要么是字面常量,要么是在表达式求值过程中创建的临时对象,没有持久的状态
左值引用和右值引用的概念:
左值引用是常见的引用,所以一般在提到「对象的引用」的时候,指得就是左值引用。
如果我们将一个对象的内存空间绑定到另一个变量上,那么这个变量就是左值引用。
在建立引用的时候,我们是将内存空间绑定,因此我们使用的是一个对象在内存中的位置,这是一个左值。
因此,我们不能将一个右值绑定到左值引用上。另一方面,由于常量左值引用保证了我们不能通过引用改变对应内存空间的值,因此我们可以将右值绑定在常量引用上。
如下是左值引用:(左值引用只能绑定到左值上,const引用除外)
int foo=42;
int& bar = foo; // OK: foo 在此是左值,将它的内存空间与 bar 绑定在一起
int& baz = 42; // Err: 42 是右值,不能将它绑定在左值引用上
const int& qux = 42; // OK: 42 是右值,但是编译器可以为它开辟一块内存空间,绑定在 qux 上
c++11中新加了右值引用&&:
对右值进行绑定的引用就是右值引用,他的语法是这样的A&&,通过双引号来表示绑定类型为A的右值。通过&&我们就可以很方便的绑定右值了;
如下是右值引用:(右值引用也是引用,但是它只能且必须绑定在右值上)。
int foo=42;
int& bar = foo; // OK: 将 foo 绑定在左值引用上
int&& baz = foo; // Err: foo 可以是左值,所以不能将它绑定在右值引用上
int&& qux = 42; // OK: 将右值 42 绑定在右值引用上
int&& quux = foo * 1; // OK: foo * 1 的结果是一个右值,将它绑定在右值引用上
int& garply = foo++; // Err: 后置自增运算符返回的是右值,不能将它绑定在左值引用上
int&& waldo = foo--; // OK: 后置自减运算符返回的是右值,将它绑定在右值引用上
由于右值引用只能绑定在右值上,而右值要么是字面常量,要么是临时对象,所以:
右值引用的对象,是临时的,即将被销毁;并且
右值引用的对象,不会在其它地方使用。
我们思考一个问题:右值引用本身是左值还是右值?或者可以先思考一下它的对偶问题:左值引用本身是左值还是右值?
先看下面的代码:
int foo(42);
int& bar = foo; // bar 是对 foo 的左值引用
int& baz = bar; // baz 是对 bar 的左值引用,因而 bar 是右值
int qux = ++foo; // 前置自增运算符返回左值引用,在这里赋值给 qux,此时左值引用作为右值
观察上面代码,不难发现,左值引用本身既可以是左值,又可以是右值。它具体是左值还是右值,依然取决于它作为表达式时候的作用。更仔细地观察可以发现,
如果左值引用作为一个变量被保存下来了,那么它就可以是左值(当然也可以起到右值的作用);而如果左值引用是一个临时变量(例如函数的返回值),那么它就是右值。
同理可以用在右值引用上。
class Type;
void foo(Type&& bar) {
// 将右值引用作为 Type 的构造函数的参数
// 此时匹配 Type::Type(const Type& orig), 即拷贝构造函数
// bar 是左值
Type baz(bar);
}
Type&& qux();
quux = qux(); // qux 的返回值是 Type 类型的右值引用,此时右值引用是右值
和左值引用一样,右值引用本身也既可以作为左值也可以作为右值。并且,同样的是:如果右值引用作为变量被保存下来了,那么应该把它当做是一个左值看待;否则应当作为右值看待。
因此,不论是左值引用还是右值引用,都有
当引用作为变量被保存下来,那么它是左值;否则
它是右值。