什么是lvalue?
什么是lvalue?
左值(lvalue)是一个可以被赋值的值:
lvalue = rvalue;
它的缩写是“左值”或“左手值”,基本上就是等号=
符号左边的值,也就是你要赋值的值。
以下是一个不是左值(即仅为右值)的示例:
printf("Hello, world!\n") = 100; // WTF?
该代码无法工作是因为printf()
(返回一个int
的函数)不能是左值,只能是右值。
出现在赋值语句左侧的东西,即可被赋值的东西。
请注意,在C++中,如果满足以下条件,函数调用可以是lvalue:
int & func() {
static int a = 0;
return a;
}
然后:
func() = 42; // is legal (and not uncommon)
传统上来说,“=”操作符左边的是lvalue(可被赋值的值)。然而随着时间的推移,“lvalue”和“rvalue”的含义也有所改变。C++中增加了一个术语叫做“不可修改的lvalue”,它是指任何不能被赋值的lvalue:数组和带有“const”限定符的变量就是其中的两个例子。在C中,你不能对任何rvalue进行赋值(请参见下文)。同样地,在C++中,你不能对不属于某些用户定义的类类型的rvalues进行赋值。
你可以说“lvalue”是一个表达式,它对应着一个随时间持续存在的对象,并占据一些存储位置。你是否能将赋值操作应用于该表达式并不重要,因为这与其分类无关。特别地,一个引用也是一个lvalue,因为它拥有一个随时间持续存在的名字。以下所有内容都是lvalues,因为它们都指向命名的对象。同时请注意,“const”并不会对lvalue性质产生任何影响。
int a; lvalue: a;
lvalue: ++a;
int a[2]; lvalue: a;
int &ra = a; lvalue: ra;
int *pa = &a; lvalue: *pa;
术语“rvalue”用于诸如文字常量和枚举值以及那些不享受长寿命的临时对象,它们在完整表达式结束时立即被销毁。对于rvalue而言,持久性方面并不重要,而是重视其值方面。C++中的函数是lvalue,因为它们是持久的并且具有地址,尽管它们不是对象。我在上述lvalues概述中将它们省略了,因为首先只考虑对象更容易理解lvalues。以下所有内容都是rvalues:
enum { FOO, BAR }; rvalue: BAR;
int a[2]; rvalue: (a+1);
rvalue: 42;
int a; rvalue: a++; // refering to a temporary
struct mystruct { }; mystruct f() { return mystruct(); } rvalue: f();
顺便提一下,通常情况下您有一个左值,但运算符需要一个右值。例如,二元内置 "+" 运算符添加两个值。左值表达式首先为所有内容指定了一个位置,其中必须首先读取一个值。因此,当您添加两个变量时,会发生“左值到右值”的转换。标准规定,左值表达式中包含的值是其右值结果:
int a = 0, b = 1;
int c = a + b; // read values out of the lvalues of a and b.
其他运算符不接受右值,只接受左值。它们不读取值。一个例子是取地址运算符 &
。你不能对右值表达式取地址。有些右值甚至不是对象:它们不占用任何存储空间。例如字面量(10、3.3等)和枚举值。
嗯,区分左值和右值有几个优点:
...... 还有更多,我感觉......
我所知道的最好的解释可以在这篇关于RValue引用的文章中找到。
另一种确定表达式是否为左值的方法是询问“我能取它的地址吗?”。如果可以,那么它就是左值。如果不能,那么它就是右值。例如,&obj,& * ptr,&ptr [index]和&++x都是有效的(即使其中一些表达式很傻),而&1729,&(x + y),&std :: string(“meow”)和&x ++都是无效的。为什么会这样?取地址运算符需要其“操作数应为左值”(C ++ 03 5.3.1 / 2)。为什么它需要呢?取持久对象的地址是可以的,但取临时对象的地址会非常危险,因为临时对象很快就会消失。
lvalue中的“L”通常被描述为代表“位置”。lvalue指定了某物的位置;正如另一个回答者所指出的,lvalue通常可以取其地址。这就是为什么数字文字和非引用函数返回值不是lvalue的原因。
在const被引入C++之前,“L”曾经代表“left”。const lvalue不能出现在赋值语句的左边。
对于C编程语言,C11 draft n1570 6.3.2.1p1 简洁地表述:
一个lvalue是一个表达式(具有非void对象类型),它可能指定一个对象[...]
就是这么简单。
维基百科definition在回答写作时几乎同样好。
[...] 指向存储位置的值,可以潜在地允许分配新值或提供可访问的内存地址,其中数据可以被写入变量的位置值。
左值表达式的例子? 给定
int a, b[1], *c;
a
、b
和c
,每个都明确地指代一个对象。b[0]
也指代一个对象,因此它是一个左值表达式。那么潜在性呢?*c
始终是一个左值表达式,但如果c
不持有指向有效对象的指针,则不指代对象。b[1]
在语义上可能指代一个对象,但由于访问了数组越界,所以实际上并没有。它们两个都是左值表达式。((flag ? foo() : bar())->pointed_to_by_struct_member[42])[0]
如果它编译通过,那么它就是一个左值表达式。
基本上,在C中您可以应用 &
(取地址运算符)的任何东西都是一个左值,除了函数,因为在C中函数不是对象。
那么L代表什么呢?
64) 名称lvalue最初来自赋值表达式
E1 = E2
,其中左操作数E1
必须是(可修改的)左值。 它可能更好地被认为代表一个对象的定位器值。[...]
E1 = E2
不是必需的编译条件,以使E1
成为lvalue。6.3.2.1p1继续说:
可修改的lvalue是指不具有数组类型、不具有不完整类型、不具有
const
限定类型,并且如果它是结构体或联合体,则不具有任何成员(包括递归地所有包含的聚合体或联合体的成员或元素)具有const
限定类型。
在C之前(例如B语言),所有定位器值都可以出现在赋值的左侧,因此得名。这在C中不再适用,因为数组从未被分配,并且ISO C添加了const
。
P.S. 同样的注释解释了相关术语 rvalue,如下所示:
[...] 有时被称为rvalue的内容在国际标准中被描述为表达式的值。 [...]
一个绝对不是左值的简单示例:
3 = 42;
运算符 要求 & (一元) 操作数必须是一个左值。 ++ -- 操作数必须是一个左值。 这适用于前缀和后缀形式。 = += -= *= %= >= &= ^= |= 左操作数必须是一个左值。
例如,所有赋值运算符都会评估其右操作数,并将该值分配给其左操作数。左操作数必须是可修改的左值或可修改对象的引用。
地址运算符(&)要求一个左值作为操作数,而增量(++)和减量(--)运算符要求一个可修改的左值作为操作数。