左值是不同于void
的对象类型的表达式,其潜在地指定对象(可能存储值的内存块),使得该对象可以被读取或修改.LValue可以包括像x
这样的变量名、像a[i]
这样的数组下标表达式、像foo.bar
这样的成员 Select 表达式、像*p
这样的指针取消引用等等.一个很好的经验法则是,如果它可以成为=
运算符的目标,那么它就是一个左值.
数组很奇怪.数组表达式是一个左值,但它是non-modifiable个左值;它指定一个对象,但它不能是赋值的目标.当您在C中声明一个数组时,如
int a[N];
您在内存中得到的内容如下所示:
+---+
a: | | a[0]
+---+
| | a[1]
+---+
| | a[2]
+---+
...
没有独立于单个数组元素的object,a
;没有什么可以赋值的,名为a
的to.a
表示整个数组,但C没有定义=
运算符来处理整个array.
简要历史教训-C派生自一种名为B的早期语言,当您在B中声明一个数组时:
auto a[N];
你得到的是这样的东西:
+---+
a: | | -------------+
+---+ |
... |
+---+ |
| | a[0] <-------+
+---+
| | a[1]
+---+
| | a[2]
+---+
...
在B,a
,was中,存储到数组第一个元素的偏移量的单独对象.数组下标操作a[i]
是definedAS *(a + i)
-给定存储在a
中的起始地址,从该地址偏移i
个字1并且解除对结果的引用.
在设计C时,Ritchie希望保留B的数组行为(a[i] == *(a + i)
),但他不想保留行为所需的显式指针.相反,他创建了一条规则,即只要数组表达式不是sizeof
、_Alignof
或一元&
运算符的操作数,它就会从"T
的N元素数组"类型转换为"指向T
的指针",并且表达式的值是第一个元素的地址.
表达式a[i] = *(a + i)
的工作方式与它在B中的工作方式相同,但a
中第一个元素的地址不是storing,而是我们需要的地址(这是在转换期间完成的,而不是运行时).但这意味着您也可以将[]
下标运算符与指针一起使用,因此ptr[i]
也可以做同样的事情:
+---+ +---+
a: | | a[0] (ptr[0]) <------ ptr: | |
+---+ +---+
| | a[1] (ptr[1])
+---+
| | a[2] (ptr[2])
+---+
...
这就是为什么a
不能成为赋值的目标--在大多数情况下,它会"衰减"到一个等于&a[0]
的指针值,而values不能成为赋值的目标.
您不能更改某物的地址-您只能更改存储在给定地址的值.
- B是一种无类型的语言--一切都以单词的形式存储.