汉川市城乡建设局网站,论坛推广工具,编程软件自学网,网站备案资料文章首发公众号#xff1a;iDoitnow 1. 左右值和左右值引用
什么是左值、右值呢#xff1f;一种极不严谨的理解为#xff1a;在赋值的时候#xff0c;能够被放到等号左边的值为左值#xff0c;放在右边的值为右值。例如#xff1a;
int sum(int x, int y){return x y;… 文章首发公众号iDoitnow 1. 左右值和左右值引用
什么是左值、右值呢一种极不严谨的理解为在赋值的时候能够被放到等号左边的值为左值放在右边的值为右值。例如
int sum(int x, int y){return x y;}int a 1; //a为左值常数1为右值
int b a a; //b为左值表达式aa为右值
int c sum(a, a);//c为左值但函数sum(a, a)返回值为右值通过上面的例子常数a、表达式(aa)和函数sum(aa)返回值他们都是临时值这些值都保存在寄存器中无法取到他们的地址而对于a、b和c为具体的变量名存储在内存中可以取到其地址。因此一般情况下可以根据能否取到地址来区分左值和右值。
在了解左值和右值之前我们首先要知道表达式的概念由运算符和运算对象构成的计算式类似数学中的算术表达式。表达式是可以求值的因此根据表达式值的类别可以对其进行分类准确的来说是表达式的结果的值类别但我们一般不刻意区分表达式和表达式的求值结果所以这里称“表达式的值类别”。C11之后将表达式定义了五种类型 lvalueLeft-hand-side value左值 prvaluePure rvalue纯右值 xvalueeXpiring value将亡值 rvalueRight-hand-side value右值 glvalueGeneralized lvalue泛左值
它们之间的关系如下图所示
#mermaid-svg-Y2P7Cm83aehKF0Li {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-Y2P7Cm83aehKF0Li .error-icon{fill:#552222;}#mermaid-svg-Y2P7Cm83aehKF0Li .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Y2P7Cm83aehKF0Li .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-Y2P7Cm83aehKF0Li .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Y2P7Cm83aehKF0Li .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Y2P7Cm83aehKF0Li .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Y2P7Cm83aehKF0Li .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Y2P7Cm83aehKF0Li .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Y2P7Cm83aehKF0Li .marker.cross{stroke:#333333;}#mermaid-svg-Y2P7Cm83aehKF0Li svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Y2P7Cm83aehKF0Li .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Y2P7Cm83aehKF0Li .cluster-label text{fill:#333;}#mermaid-svg-Y2P7Cm83aehKF0Li .cluster-label span{color:#333;}#mermaid-svg-Y2P7Cm83aehKF0Li .label text,#mermaid-svg-Y2P7Cm83aehKF0Li span{fill:#333;color:#333;}#mermaid-svg-Y2P7Cm83aehKF0Li .node rect,#mermaid-svg-Y2P7Cm83aehKF0Li .node circle,#mermaid-svg-Y2P7Cm83aehKF0Li .node ellipse,#mermaid-svg-Y2P7Cm83aehKF0Li .node polygon,#mermaid-svg-Y2P7Cm83aehKF0Li .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Y2P7Cm83aehKF0Li .node .label{text-align:center;}#mermaid-svg-Y2P7Cm83aehKF0Li .node.clickable{cursor:pointer;}#mermaid-svg-Y2P7Cm83aehKF0Li .arrowheadPath{fill:#333333;}#mermaid-svg-Y2P7Cm83aehKF0Li .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Y2P7Cm83aehKF0Li .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Y2P7Cm83aehKF0Li .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-Y2P7Cm83aehKF0Li .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-Y2P7Cm83aehKF0Li .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Y2P7Cm83aehKF0Li .cluster text{fill:#333;}#mermaid-svg-Y2P7Cm83aehKF0Li .cluster span{color:#333;}#mermaid-svg-Y2P7Cm83aehKF0Li div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Y2P7Cm83aehKF0Li :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}valueglvaluervaluelvaluexvalueprvalueC11中将表达式按值类别可以分为左值、将亡值和纯右值。其中左值和将亡值合称为泛左值纯右值和将亡值合称为右值。
随着移动语义后面我们会详细介绍引入到 C11 之中值类别被重新进行了定义C之父Bjarne Stroustrup在《“New” Value Terminology》中给出以区别表达式的两种独立的性质 拥有身份 (identity)可以确定表达式是否与另一表达式指代同一实体例如通过比较它们所标识的对象或函数的直接或间接获得的地址可被移动移动构造函数、移动赋值运算符或实现了移动语义的其他函数重载能够绑定于这个表达式。 C11 中
拥有身份且不可被移动的表达式被称作左值(lvalue)表达式拥有身份且可被移动的表达式被称作将亡值 (xvalue)表达式不拥有身份且可被移动的表达式被称作纯右值 (prvalue)表达式
1.1 左值
一般情况下左值我们可以简单地理解理解为能够使用取地址的表达式。
常见的左值有
变量名函数名返回左值引用的函数调用前置自增/减的运算符链接的表达式如i/--i内置的赋值表达式如ab,a1字符串等。
【注字符串是可以取地址的因此字符串常量也属于左值】
1.2 纯右值
纯右值表达式本身就是纯粹的字面值如1ture1.0或者该表达式求值结果相当于一个字面值或一个不具名的临时对象。
常见的纯右值有
除字符串字面值以外的字面值返回非引用类型的函数调用后置自增/减的运算符链接的表达式如i/i--算术/逻辑/比较表达式如ababab取地址表达式如a
1.3 将亡值
将亡值是在C11中引进来的顾名思义就即将销毁的东西。将亡值的产生与右值引用的产生而引起的对于将亡值我们常用到的有
返回类型是右值引用的函数调用或重载运算符的表达式如std::move(x)转换为右值引用的转换函数的调用表达式如staticint(a)
1.4 左右值引用
左值引用就是对左值的引用。它的形式如T根据const属性可以分为两种
const左值引用非const左值引用
例如
int a 1;
int la a;//la为a的左值引用非const左值引用
la 2;//la为非const左值引用可以修改它的值const int c_la a;//c_la为a的左值引用const左值引用
c_la 2;//该语法错误c_la为const左值引用不可以修改它的值右值引用就是对右值的引用通过T来表示。右值的引用只能绑定到右值上。
2. 移动语义
在未出现右值引用之前我们在函数调用传参的时候在某些时候可以使用按引用传递参数减少参数多的拷贝对资源的消耗提高程序的运行效率。当我们在处理包含大量数据的对象时移动语义显的尤为重要。
2.1 std::move
如何将一个左值转换为一个右值呢C11在头文件utility中声明了std::move()函数该函数的作用就是类型转换通过它我们可以 把一个左值将其标记为右值。move()不做任何资源转移的操作只是产生一个将亡值表达式来标识参数x其完全等同于static_castT(x)。例如
int a 1;
int r_a a; //错误,右值引用只能绑定到右值上而a是一个左值
int r_a std::move(b); //正确, std::move(a) 是一个右值可以用右值引用绑定2.2 移动构造函数
一个类 T 的首个形参是 T、const T、volatile T 或 const volatile T且没有其他形参或剩余形参都有默认值。
具体的形式如下
T (T ) //移动构造函数的典型声明形式
T (T ) default; //强制编译器生成移动构造函数。
T (T ) delete; //避免隐式生成移动构造函数。示例
#include string
#include iostream
#include utilityclass A
{private:std::string s;public:A(std::string str A()) : s(str) {std::couts的构造函数\n;}A(A o) : s(std::move(o.s)) {std::couts的移动构造函数\n;}~A(){std::couts的析构函数\n;}
};A f(A a) { return a; }int main()
{A a1(f(A(a)));// 按值返回时从函数形参移动构造它的目标A a2(std::move(a1));// 从亡值移动构造
}2.3 移动赋值运算符
一个类 T 的移动赋值运算符是名为 operator的非模板非静态成员函数它接受恰好一个 T、const T、volatile T 或 const volatile T 类型的形参。
具体的形式如下
T T ::operator (T ) //移动赋值运算符的典型声明
T T ::operator (T ) default; //强制编译器生成移动赋值运算符
T T ::operator (T ) delete; //避免隐式移动赋值示例
#include string
#include iostream
#include utilityclass A
{private:std::string s;public:A(std::string str A()) : s(str) {std::couts的构造函数\n;}~A(){std::couts的析构函数\n;}A operator(const A other){s other.s;std::cout 复制赋值\n;return *this;}A operator(A other){s std::move(other.s);std::cout 移动赋值\n;return *this;}
};A f(A a) { return a; }int main()
{A a1(a1), a2(a2);std::cout 尝试从右值临时量移动赋值 A\n;a1 f(A(a)); // 从右值临时量移动赋值std::cout 尝试从亡值移动赋值 A\n;a2 std::move(a1); // 从将亡值移动赋值
}参考文献 C Primer Plus(第六版) - 第18章 探讨C新标准 C 参考手册