C++的表达式要不然是右值(rvalue),要不然就是左值(lvalue)。这两个名词是从C语言继承过来的,原本是为了帮助记忆:左值可以位于赋值语句的左侧,右值则不能。

在C++语言中,二者的区别就没那么简单了。一个左值表达式的求值结果是一个对象或者一个函数,然而以常量对象为代表的某些左值实际上不能作为赋值语句的左侧运算对象。此外,虽然某些表达式的求值结果是对象,但它们是右值而非左值。可以做一个简单的归纳:当一个对象被用作右值的时候,用的是对象的值(内容);当对象被用作左值的时候,用的是对象的身份(在内存中的位置)左值与右值的根本区别在于是否允许取地址&运算符获得对应的内存地址

不同的运算符对运算对象的要求各不相同,有的需要左值运算对象,有的需要右值运算对象;返回值也有差异,有的得到左值结果,有的得到右值结果。一个重要的原则(有一种例外的情况),是在需要右值的地方可以用左值来代替,但是不能把右值当成左值(也就是位置)使用。当一个左值被当成右值使用时,实际使用的是它的内容(值)。

运算符用到左值的包括:(1).赋值运算符需要一个(非常量)左值作为其左侧运算对象,得到的结果也仍然是一个左值。(2).取地址符作用于一个左值运算对象,返回一个指向该运算对象的指针,这个指针是一个右值。(3). 内置解引用运算符、下标运算符、迭代器解引用运算符、string和vector的下标运算符的求值结果都是左值。(4). 内置类型和迭代器的递增递减运算符作用于左值运算对象,其前置版本所得的结果也是左值。

使用关键字decltype的时候,左值和右值也有所不同。如果表达式的求值结果是左值,decltype作用于该表达式(不是变量)得到一个引用类型。举个例子,假定p的类型是int*,因为解引用运算符生成左值,所以decltype(*p)的结果是int&。另一方面,因为取地址运算符生成右值,所以decltype(&p)的结果是int**,也就是说,结果是一个指向整型指针的指针。

赋值运算符的左侧运算对象必须是一个可修改的左值。赋值运算符的结果是它的左侧运算对象,并且是一个左值。相应的,结果的类型就是左侧运算对象的类型。如果赋值运算符的左右两个运算对象类型不同,则右侧运算对象将转换成左侧运算对象的类型。

递增和递减运算符有两种形式:前置版本和后置版本。这两种运算符必须作用于左值运算对象。前置版本将对象本身作为左值返回,后置版本则将对象原始值的副本作为右值返回。

箭头运算符作用于一个指针类型的运算对象,结果是一个左值。点运算符分成两种情况:如果成员所属的对象是左值,那么结果是左值;反之,如果成员所属的对象是右值,那么结果是右值。

当条件运算符的两个表达式都是左值或者能转换成同一种左值类型时,运算的结果是左值;否则运算的结果是右值。

对于逗号运算符来说,首先对左侧的表达式求值,然后将求值结果丢弃掉。逗号运算符真正的结果是右侧表达式的值。如果右侧运算对象是左值,那么最终的求值结果也是左值。

左值(lvalue):是指那些求值结果为对象或函数的表达式。一个表示对象的非常量左值可以作为赋值运算符的左侧运算对象。

右值(rvalue):是指一种表达式,其结果是值而非值所在的位置。

函数返回类型:函数的返回类型决定函数调用是否是左值。调用一个返回引用的函数得到左值,其它返回类型得到右值。可以像使用其它左值那样来使用返回引用的函数的调用,特别是,我们能为返回类型是非常量引用的函数的结果赋值。把函数调用放在赋值语句的左侧可能看起来有点奇怪,但其实这没什么特别的。返回值是引用,因此调用是个左值,和其它左值一样它也能出现在赋值运算符的左侧。如果返回类型是常量引用,我们不能给调用的结果赋值,这一点和我们熟悉的情况是一样的。

严格来说,当我们使用术语”引用(reference)”时,指的其实是”左值引用(lvalue reference)”。C++11中新增了一种引用,右值引用(rvalue reference),这种引用主要用于内置类。

为了支持移动操作,C++11引入了一种新的引用类型----右值引用(rvalue reference)。所谓右值引用就是必须绑定到右值的引用通过&&而不是&来获得右值引用右值引用有一个重要的性质----只能绑定到一个将要销毁的对象。因此,可以自由地将一个右值引用的资源”移动”到另一个对象中。

左值和右值是表达式的属性。一些表达式生成或要求左值,而另外一些则生成或要求右值。一般而言,一个左值表达式表示的是一个对象的身份,而一个右值表达式表示的是对象的值。

类似任何引用,一个右值引用也不过是某个对象的另一个名字而已。对于常规引用(为了与右值引用区分开来,可以称之为左值引用(lvalue reference)),不能将其绑定到要求转换的表达式、字面常量或是返回右值的表达式。右值引用有着完全相反的绑定特性:可以将一个右值引用绑定到这类表达式上,但不能将一个右值引用直接绑定到一个左值上

返回左值引用的函数,连同赋值、下标、解引用和前置递增/递减运算符,都是返回左值的表达式的例子。可以将一个左值引用绑定到这类表达式的结果上。

返回非引用类型的函数,连同算术、关系、位以及后置递增/递减运算符,都生成右值。不能将一个左值引用绑定到这类表达式上,但可以将一个const的左值引用或者一个右值引用绑定到这类表达式上

左值持久,右值短暂:左值有持久的状态,而右值要么是字面常量,要么是在表达式求值过程中创建的临时对象。

由于右值引用只能绑定到临时对象,可知:所引用的对象将要被销毁;该对象没有其它用户。这两个特性意味着,使用右值引用的代码可以自由地接管所引用的对象的资源。右值引用指向将要销毁的对象。因此,我们可以从绑定到右值引用的对象”窃取”状态。

变量是左值:变量可以看作只有一个运算对象而没有运算符的表达式,虽然我们很少这样看待变量。类似其它任何表达式,变量表达式也有左值/右值属性。变量表达式都是左值。带来的结果就是,我们不能将一个右值引用绑定到一个右值引用类型的变量上。其实有了右值表示临时对象这一观察结果,变量是左值这一特性并不令人惊讶。毕竟,变量是持久的,直至离开作用域时才被销毁。变量是左值,因此我们不能将一个右值引用直接绑定到一个变量上,即使这个变量是右值引用类型也不行。

标准库std::move函数:虽然不能将一个右值引用直接绑定到一个左值上,但可以显示地将一个左值转换为对应的右值引用类型。我们还可以通过调用一个名为move的新标准库函数来获得绑定到左值上的右值引用,此函数定义在头文件utility中。move调用告诉编译器:有一个左值,但希望像一个右值一样处理它。我们必须认识到,在调用move之后,我们不能对移后源对象的值做任何假设。我们可以销毁一个移后源对象,也可以赋予它新值,但不能使用一个移后源对象的值

移动构造函数和移动赋值运算符接受一个(通常是非const的)右值引用;而拷贝版本则接受一个(通常是const的)普通左值引用。

左值引用(lvalue reference):可以绑定到左值的引用。右值引用(rvalue reference):指向一个将要销毁的对象的引用。

引用限定符(referencequalifier):用来指出一个非static成员函数可以用于左值或右值的符号。限定符&和&&应该放在参数列表之后或const限定符之后(如果有的话)。被&限定的函数只能用于左值;被&&限定的函数只能用于右值。

如果一个函数参数是指向模板参数类型的右值引用(如,T&&),则可以传递给它任意类型的实参。如果将一个左值传递给这样的参数,则函数参数被实例化为一个普通的左值引用(T&)。

右值引用是C++11中最重要的新特性之一,它解决了C++中大量的历史遗留问题,使C++标准库的实现在多种场景下消除了不必要的额外开销(如std::vector, std::string),也使得另外一些标准库(如std::unique_ptr,std::function)成为可能。即使你并不直接使用右值引用,也可以通过标准库,间接从这一新特性中受益。

右值引用的意义通常解释为两大作用:移动语义(Move Sementics)和完美转发(Perfect Forwarding)。它的主要目的有两个方面:(1)、消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率;(2)、能够更简洁明确地定义泛型函数。

右值引用可以使我们区分表达式的左值和右值。

右值引用主要就是解决一个拷贝效率低下的问题,因为针对于右值,或者打算更改的左值,我们可以采用类似与unique_ptr的move(移动)操作,大大的提高性能(move semantics)。另外,C++的模板推断机制为参数T&&做了一个例外规则,让左值和右值的识别和转向(forward)非常简单,帮助我们写出高效并且简捷的泛型代码(perfect forwarding)。

下面是从其他文章中copy的测试代码,详细内容介绍可以参考对应的reference:

#include "lvalue_rvalue.hpp"
#include <iostream>
#include <string>
#include <utility>namespace lvalue_rvalue_ {namespace {
char& get_val(std::string& str, std::string::size_type ix)
{return str[ix];
}
}int test_lvalue_rvalue_1()
{
{// 赋值运算符的左侧运算对象必须是一个可修改的左值:int i = 0, j = 0, k = 0; // 初始化而非赋值const int ci = i; // 初始化而非赋值// 下面的赋值语句都是非法的//1024 = k; // 错误:字面值是右值//i + j = k; // 错误:算术表达式是右值//ci = k; // 错误:ci是常量(不可修改的)左值
}{int i = 0;int& r = i; // 正确:r引用i//int&& rr = i; // 错误:不能将一个右值引用绑定到一个左值上//int& r2 = i * 42; // 错误:i*42是一个右值const int& r3 = i * 42; // 正确:我们可以将一个const的引用绑定到一个右值上int&& rr2 = i * 42; // 正确:将rr2绑定到乘法结果上
}{// 不能将一个右值引用绑定到一个右值引用类型的变量上int&& rr1 = 42; // 正确:字面常量是右值//int&& rr2 = rr1; // 错误:表达式rr1是左值//int rr = &&rr1; // 不能将一个右值引用直接绑定到一个左值上int i = 5;int&& rr3 = std::move(i); // ok}// 调用一个返回引用的函数得到左值,其它返回类型得到右值std::string s{ "a value" };std::cout << s << std::endl; // a valueget_val(s, 0) = 'A';std::cout << s << std::endl; // A valuereturn 0;
}/
// reference: https://msdn.microsoft.com/en-us/library/f90831hc.aspx
int test_lvalue_rvalue_2()
{int i, j;int *p = new int;// Correct usage: the variable i is an lvalue.i = 7;// Incorrect usage: The left operand must be an lvalue (C2106).//7 = i; // C2106//j * 4 = 7; // C2106// Correct usage: the dereferenced pointer is an lvalue.*p = i;delete p;const int ci = 7;// Incorrect usage: the variable is a non-modifiable lvalue (C3892).//ci = 9; // C3892// Correct usage: the conditional operator returns an lvalue.((i < 3) ? i : j) = 7;return 0;
}//
// reference: http://www.bogotobogo.com/cplusplus/C11/4_C11_Rvalue_Lvalue.php
namespace {class cat {};int square(int x) { return x*x; }int square2(int& x) { return x*x; }
}int test_lvalue_rvalue_3()
{
{ // lvalue examplesint i = 7;  // i: lvalueint *pi = &i;  // i is addressablei = 10;  // we can modify itclass cat {};cat c;   // c is an lvalue for a user defined type
}{ // rvalue examplesint i = 7;  // i: lvalue but 7 is rvalueint k = i + 3;  // (i+3) is an rvalue//int *pi = &(i + 3); // error, it's not addressable//i + 3 = 10;   // error - cannot assign a value to it//3 = i;        // error - not assignablecat c;c = cat();   // cat() is an rvalue}{int sq = square(10);  // square(10) is an rvalue
}{int i = 7;square(i);  // OKsquare(7);//square2(7);  // error, 7 is an rvalue and cannot be assigned to a reference
}{int a = 7; // a is lvalue & 7 is rvalueint b = (a + 2); // b is lvalue & (a+2) is rvalueint c = (a + b); // c is lvalue & (a+b) is rvalueint * ptr = &a; // Possible to take address of lvalue//int * ptr3 = &(a + 1);  // Compile Error. Can not take address of rvalue
}return 0;
}//
// reference: http://en.cppreference.com/w/cpp/language/reference
namespace {
void double_string(std::string& s)
{s += s; // 's' is the same object as main()'s 'str'
}char& char_number(std::string& s, std::size_t n)
{return s.at(n); // string::at() returns a reference to char
}
}int test_lvalue_rvalue_4()
{// 1. Lvalue references can be used to alias an existing object (optionally with different cv-qualification):std::string s = "Ex";std::string& r1 = s;const std::string& r2 = s;r1 += "ample";           // modifies s//  r2 += "!";               // error: cannot modify through reference to conststd::cout << r2 << '\n'; // prints s, which now holds "Example"// 2. They can also be used to implement pass-by-reference semantics in function calls:std::string str = "Test";double_string(str);std::cout << str << '\n';// 3. When a function's return type is lvalue reference, the function call expression becomes an lvalue expressionstd::string str_ = "Test";char_number(str_, 1) = 'a'; // the function call is lvalue, can be assigned tostd::cout << str_ << '\n';return 0;
}/
// reference: http://en.cppreference.com/w/cpp/language/reference
namespace {
void f(int& x)
{std::cout << "lvalue reference overload f(" << x << ")\n";
}void f(const int& x)
{std::cout << "lvalue reference to const overload f(" << x << ")\n";
}void f(int&& x)
{std::cout << "rvalue reference overload f(" << x << ")\n";
}
}int test_lvalue_rvalue_5()
{// 1. Rvalue references can be used to extend the lifetimes of temporary objects// (note, lvalue references to const can extend the lifetimes of temporary objects too, but they are not modifiable through them):std::string s1 = "Test";//  std::string&& r1 = s1;           // error: can't bind to lvalueconst std::string& r2 = s1 + s1; // okay: lvalue reference to const extends lifetime//  r2 += "Test";                    // error: can't modify through reference to conststd::string&& r3 = s1 + s1;      // okay: rvalue reference extends lifetimer3 += "Test";                    // okay: can modify through reference to non-conststd::cout << r3 << '\n';// 2. More importantly, when a function has both rvalue reference and lvalue reference overloads,// the rvalue reference overload binds to rvalues (including both prvalues and xvalues),// while the lvalue reference overload binds to lvalues:int i = 1;const int ci = 2;f(i);  // calls f(int&)f(ci); // calls f(const int&)f(3);  // calls f(int&&)// would call f(const int&) if f(int&&) overload wasn't providedf(std::move(i)); // calls f(int&&)// This allows move constructors, move assignment operators, and other move-aware functions// (e.g. vector::push_back() to be automatically selected when suitable.return 0;
}/
// reference: http://www.bogotobogo.com/cplusplus/C11/5_C11_Move_Semantics_Rvalue_Reference.php
namespace {
void printReference(int& value)
{std::cout << "lvalue: value = " << value << std::endl;
}void printReference(int&& value)
{std::cout << "rvalue: value = " << value << std::endl;
}int getValue()
{int temp_ii = 99;return temp_ii;
}
}int test_lvalue_rvalue_6()
{int ii = 11;printReference(ii);printReference(getValue());  //  printReference(99);return 0;
}// reference: https://msdn.microsoft.com/en-us/library/dd293668.aspx
namespace {
template<typename T> struct S;// The following structures specialize S by
// lvalue reference (T&), const lvalue reference (const T&),
// rvalue reference (T&&), and const rvalue reference (const T&&).
// Each structure provides a print method that prints the type of
// the structure and its parameter.
template<typename T> struct S<T&> {static void print(T& t){std::cout << "print<T&>: " << t << std::endl;}
};template<typename T> struct S<const T&> {static void print(const T& t){std::cout << "print<const T&>: " << t << std::endl;}
};template<typename T> struct S<T&&> {static void print(T&& t){std::cout << "print<T&&>: " << t << std::endl;}
};template<typename T> struct S<const T&&> {static void print(const T&& t){std::cout << "print<const T&&>: " << t << std::endl;}
};// This function forwards its parameter to a specialized
// version of the S type.
template <typename T> void print_type_and_value(T&& t)
{S<T&&>::print(std::forward<T>(t));
}// This function returns the constant string "fourth".
const std::string fourth() { return std::string("fourth"); }
}int test_lvalue_rvalue_7()
{// The following call resolves to:// print_type_and_value<string&>(string& && t)// Which collapses to:// print_type_and_value<string&>(string& t)std::string s1("first");print_type_and_value(s1);// The following call resolves to:// print_type_and_value<const string&>(const string& && t)// Which collapses to:// print_type_and_value<const string&>(const string& t)const std::string s2("second");print_type_and_value(s2);// The following call resolves to:// print_type_and_value<string&&>(string&& t)print_type_and_value(std::string("third"));// The following call resolves to:// print_type_and_value<const string&&>(const string&& t)print_type_and_value(fourth());return 0;
}} // namespace lvalue_rvalue_

GitHub:https://github.com/fengbingchun/Messy_Test

C++/C++11中左值、左值引用、右值、右值引用的使用相关推荐

  1. 2020-09-22C++学习笔记之引用1(1.引用(普通引用)2.引用做函数参数 3.引用的意义 4.引用本质5.引用结论 6.函数返回值是引用(引用当左值)7测试代码)

    2020-09-22C++学习笔记之引用1(1.引用(普通引用)2.引用做函数参数 3.引用的意义 4.引用本质5.引用结论 6.函数返回值是引用(引用当左值)7测试代码) 1.引用(普通引用) 变量 ...

  2. 万能引用,引用折叠,右值变左值的情况

    文章目录 万能引用 引用折叠 右值变左值的情况 万能引用 左值引用只能引用左值,右值引用只能引用右值. 但是对于一个函数我们有时候并不清楚传入的参数是左值还是右值,这时候就需要写两个同名的函数重载,而 ...

  3. C++ 左值与右值 左值引用与右值引用

    1 左值与右值 左值: 是可以取地址有名字的,非临时的都是左值.在内存中必须有实体. 右值: 不能取地址,没有名字,临时值是右值.在内存或者在寄存器中. 通俗点讲: 右值只能放在 = 号的右边 左值可 ...

  4. C++中左值(引用)及右值(引用)详解

    写C++代码编译时,有时会出现左值问题错误或右值错误,那左值和右值究竟是什么呢??? 一.左值与右值 啥是左值和右值呢? 左值:在内存有确定存储地址.有变量名,表达式结束依然存在的值,简单来说左值就是 ...

  5. 左值/右值/左值引用/右值引用/move的用法介绍

    目录 问题 左值和右值 概念总结: 需要用到左值的运算符: 引用分类 左值引用 右值引用 右值引用到底什么用? std::move()函数介绍 问题 什么是左值和右值? 什么是左/右值引用? 左/右值 ...

  6. std::move 左值右值 左值引用右值引用

    参考:https://blog.csdn.net/daaikuaichuan/article/details/88371948 https://zhuanlan.zhihu.com/p/9458820 ...

  7. 左值、右值、将亡值 | 左值引用、右值引用

    左值.右值.将亡值 左值.右值 左值(lvalue)中l指的是location,是有明确地址的,可寻址的值.右值(rvalue)中r指的是read,是可读的值,不一定可以寻址.左值可以当右值使用,但右 ...

  8. 常亮左值引用可以绑定右值的原因

    相关文章: 为什么常量左值引用可以绑定到右值? 根据该问题的几位答主的回答,整理成个人的理解. 从设计初衷上讲 允许引用绑定非左值的初衷在于"让传值还是传引用成为函数本身的细节,调用者不用去 ...

  9. 函数返回值是否使用引用类型的问题:理解引用、返回值

    在<对象更有用的玻璃罩--常引用>一文中,介绍了对象作为函数的参数时,推荐使用引用的形式.并且,如果实际参数的值不允许改变时,声明为常引用更佳. 在<第8周-任务1-方案3-复数类中 ...

最新文章

  1. 搜索页面scroll下拉时候进行刷新,显示更多搜索值
  2. 类中友元(c++小细节篇一)
  3. Mysql ERROR 1067: Invalid default value for ‘auth_time‘
  4. 2020蓝桥杯省赛---java---B---7(单词分析)
  5. 围观:各地大学教师自曝近年工资待遇,真实一手数据!
  6. E: Some index files failed to download. They have been ignored, or old ones used instead.解决方案
  7. CentOS6.5+Python2.7+ GIT +IPython
  8. 启动关闭HadoopSpark历史服务
  9. 主流JS框架中DOMReady事件的实现
  10. 青云oracle,青云oracle工具下载
  11. android 微信分享小程序 图片显示不全
  12. 【GZH逸佳君】简约ppt模板-答辩ppt模板-毕业季ppt模板-说课ppt模板-ppt模板免费下载-ppt模板下载免费版
  13. 当你第一次发送ping请求包,ARP缓存表为空时会发生什么?(详细解析全过程)
  14. 嵌入式MRZ机读码OCR识别电子护照阅读器模块|模组安装与测试注意事项
  15. [转]centos安装oracle
  16. 底层网络知识详解:从二层到三层-第7讲-ICMP与ping:投石问路的侦察兵
  17. 程序员职业资格软考——软考,你不想软就得考 (值得一看的总结)
  18. CLIPS 的简单认识
  19. linux怎么读取光盘文件,linux怎么读取光盘里的文件?
  20. 门神——转转前端代码校验系统

热门文章

  1. 数据结构和算法:(3)3.2.1单链表的整表创建
  2. C指针4:数组指针(指向数组的指针)
  3. 基于YOLO的目标检测界面化部署实现(支持yolov1-yolov5、yolop、yolox)
  4. window和linux和android进程内存CPU等监控软件
  5. Tensorflow C++ API调用Keras模型实现RGB图像语义分割
  6. 在CentOS 6.9 x86_64上从源码安装xz命令的方法
  7. leetcode-440 字典序的第K小数字
  8. Cachegrind--缓存命中检查工具及其可视化
  9. JavaScript 表单与表单验证
  10. leetcode第一刷_Recover Binary Search Tree