据说here
术语可修改的左值用于强调左值允许指定的对象被改变以及被检查。以下对象类型是左值,但不是可修改的左值:
- 数组类型
- 不完整的类型
- const限定类型
- 结构或联合类型,其成员之一被限定为const类型
因为这些左值不可修改,所以它们不能出现在赋值语句的左侧。
为什么数组类型对象不可修改?写不正确
int i = 5, a[10] = {0};
a[i] = 1;
? 而且,什么是不完整的类型?
假设声明
int a[10];
那么以下所有都是真的:
a
的类型是“int
的10元素数组”;除非当a
是sizeof
或一元&
运算符的操作数时,表达式将转换为“指向int
的指针”类型的表达式,其值将是数组中第一个元素的地址;a[i]
的类型是int
;它指的是存储为数组的i
'元素的整数对象;a
可能不是赋值的目标,因为C不像其他变量那样处理数组,所以你不能写像a = b
或a = malloc(n * sizeof *a)
或类似的东西。你会注意到我一直在强调“表达”这个词。我们用来保存10个整数的内存块和我们用来引用那块内存的符号(表达式)之间有区别。我们可以用表达式a
来引用它。我们还可以创建一个指向该数组的指针:
int (*ptr)[10] = &a;
表达式*ptr
也有类型“int
的10元素数组”,它指的是a
所指的同一块记忆。
C不像其他类型的表达式那样处理数组表达式(a
,*ptr
),其中一个区别是数组类型的表达式可能不是赋值的目标。你不能重新分配a
来引用一个不同的数组对象(表达式*ptr
也是如此)。您可以为a[i]
或(*ptr)[i]
指定一个新值(更改每个数组元素的值),并且可以指定ptr
指向不同的数组:
int b[10], c[10];
.....
ptr = &b;
.....
ptr = &c;
至于第二个问题......
不完整类型缺少大小信息;声明如
struct foo;
int bar[];
union bletch;
所有都创建不完整的类型,因为编译器没有足够的信息来确定为该类型的对象预留多少存储空间。你不能创建不完整类型的对象;例如,你不能声明
struct foo myFoo;
除非您完成struct foo
的定义。但是,您可以创建指向不完整类型的指针;例如,你可以声明
struct foo *myFooPtr;
没有完成struct foo
的定义,因为指针只存储对象的地址,你不需要知道类型的大小。这使得定义自引用类型成为可能
struct node {
T key; // for any type T
Q val; // for any type Q
struct node *left;
struct node *right;
};
struct node
的类型定义在我们达到收盘价}
之前是不完整的。既然我们可以声明指向不完整类型的指针,那我们没问题。但是,我们无法将结构定义为
struct node {
... // same as above
struct node left;
struct node right;
};
因为当我们宣布left
和right
成员时类型不完整,并且因为每个left
和right
成员都将包含他们自己的left
和right
成员,每个成员都包含他们自己的left
和right
成员,并且等等。
这对结构和工会来说很好,但是怎么样呢
int bar[];
???
我们已经声明了符号bar
并指出它将是一个数组类型,但此时的大小是未知的。最终我们必须使用大小来定义它,但是这样,符号可以在数组大小没有意义或不必要的上下文中使用。但是,没有一个好的,非人为的例子来说明这一点。
编辑
回应这里的评论,因为评论部分没有足够的空间来处理我想写的内容(今晚我的心情很啰嗦)。您询问:
这是否意味着每个变量都是表达式?
这意味着任何变量都可以是表达式,也可以是表达式的一部分。以下是language standard定义术语表达式的方式:
6.5 Expressions
1 An expression is a sequence of operators and operands that specifies computation of a value, or that designates an object or a function, or that generates side effects, or that performs a combination thereof.
例如,变量a
本身就算作表达式;它指定我们定义的数组对象,以保存10个整数值。它还会计算数组第一个元素的地址。变量a
也可以是像a[i]
这样的更大表达式的一部分;运算符是下标运算符[]
,操作数是变量a
和i
。此表达式指定数组的单个成员,并计算当前存储在该成员中的值。反过来,这个表达可以成为像a[i] = 0
这样更大的表达的一部分。
并且让我明确一点,在声明int [10]中,a []代表数组类型
对,就是这样。
在C中,声明基于表达式的类型,而不是对象的类型。如果你有一个名为y
的简单变量存储int
值,并且你想要访问该值,你只需在表达式中使用y
,就像
x = y;
表达式y
的类型是int
,所以y
的声明是写的
int y;
另一方面,如果你有一个int
值的数组,并且你想访问一个特定的元素,你将使用数组名称和索引以及下标运算符来访问该值,如
x = a[i];
表达式a[i]
的类型是int
,因此数组的声明被写为
int arr[N]; // for some value N.
int
的“arr
-ness”由类型说明符int
给出; arr
的“阵列性”由宣战者arr[N]
给出。声明器为我们提供了声明对象的名称(arr
)以及类型说明符未给出的一些其他类型信息(“是一个N元素数组”)。宣言“读”为
a -- a
a[N] -- is an N-element array
int a[N]; -- of int
Aaditi
毕竟,我还没有告诉你数组表达式是不可修改的左值的真正原因。所以这是本书答案的又一章。
C并没有完全由Dennis Ritchie的思想形成;它源于早期的B语言(源自BCPL).1 B是一种“无类型”语言;它没有用于整数,浮点数,文本,记录等的不同类型。相反,一切都只是一个固定长度的单词或“单元格”(本质上是一个无符号整数)。记忆被视为线性细胞阵列。在B中分配数组时,例如
auto V[10];
编译器分配了11个单元;阵列本身的10个连续单元格,以及绑定到包含第一个单元格位置的V的单元格:
+----+
V: | | -----+
+----+ |
... |
+----+ |
| | <----+
+----+
| |
+----+
| |
+----+
| |
+----+
...
当Ritchie在C中添加struct
类型时,他意识到这种安排给他带来了一些问题。例如,他想创建一个结构类型来表示文件或目录表中的条目:
struct {
int inumber;
char name[14];
};
他希望结构不仅以抽象方式描述条目,而且还表示实际文件表条目中的位,这些位没有额外的单元格或单词来存储数组中第一个元素的位置。所以他摆脱了它 - 而不是留出一个单独的位置来存储第一个元素的地址,他写了C,这样在计算数组表达式时将计算第一个元素的地址。
这就是为什么你不能做的事情
int a[N], b[N];
a = b;
因为a
和b
都会在该上下文中评估指针值;它相当于写3 = 4
。内存中没有任何内容实际存储数组中第一个元素的地址;编译器只是在翻译阶段计算它。
术语“数组类型的左值”字面上将数组对象称为数组类型的左值,即整个数组对象。这个左值不是一个整体可修改的,因为没有合法的操作可以整体修改它。实际上,您可以对数值类型的左值执行的唯一操作是:一元&
(地址),sizeof
和隐式转换为指针类型。这些操作都不会修改数组,这就是数组对象不可修改的原因。
a[i]
不适用于数组类型的左值。 a[i]
指定一个int
对象:数组a
的第i个元素。此表达式的语义(如果明确说明)是:*((int *) a + i)
。第一步 - (int *) a
- 已经将数组类型的左值转换为int *
类型的右值。此时,数组类型的左值不再合适。
不完整类型是一种其大小尚未知晓的类型。例如:已声明但未定义的结构类型,具有未指定大小的数组类型,void
类型。
不完整类型是声明但未定义的类型,例如struct Foo;
。
您始终可以分配给各个数组元素(假设它们不是const
)。但是你不能为整个数组分配东西。
C和C ++非常令人困惑,因为像int a[10] = {0, 1, 2, 3};
这样的东西不是赋值,而是初始化,即使它看起来非常类似于赋值。
这没关系(初始化):
int a[10] = {0, 1, 2, 3};
这在C / C ++中不起作用:
int a[10];
a = {0, 1, 2, 3};
假设a
是一个整数数组,a[10]
不是一个数组。这是一个int
。
a = {0}
将是非法的。
请记住,数组的值实际上是其第一个元素的地址(指针)。此地址无法修改。所以
int a[10], b[10];
a = b
是非法的。
它当然与修改a[1] = 3
中的数组内容无关