据这里所述:
术语可修改的左值(modifiable lvalue)用于强调左值既可以被更改也可以被检查。下列对象类型是左值但不是可修改的左值:
- 数组类型
- 不完整类型
- 带有一个成员限定为const类型的结构体或联合体类型
由于这些左值不可修改,它们不能出现在赋值语句的左侧。
为什么数组类型对象不可修改?难道写下面这样的语句不正确吗?
int i = 5, a[10] = {0};
a[i] = 1;
什么是不完整类型?
int a[10];
如果以下所有条件都成立:
a
的类型为 "10个元素的int
数组";除非a
是sizeof
或一元&
运算符的操作数,否则该表达式将被转换为类型为 "指向int
的指针" 的表达式,并且其值将是数组中第一个元素的地址;a[i]
的类型为int
;它指的是存储在数组中第i个元素中的整数对象;a
不能作为赋值的目标,因为C语言不像其他变量那样处理数组,所以您不能编写诸如a = b
或a = malloc(n * sizeof *a)
之类的代码。你会注意到我一直强调“表达式”这个词。我们设置了一块内存来保存10个整数,与我们用于引用该内存块的符号(表达式)之间存在差异。我们可以使用表达式a
来引用它。我们也可以创建指向该数组的指针:
int (*ptr)[10] = &a;
*ptr
的类型也是“10个元素的int数组”,它指向与a
相同的内存块。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
,并指示它将是一个数组类型,但此时大小未知。最终,我们将不得不定义它的大小,但以这种方式,该符号可以用于数组大小无意义或不必要的情况下。我头脑中没有一个好的、非人为的例子来说明这一点。
编辑
回应这里的评论,因为评论区没有足够的空间让我写出想要写的内容(今晚我很啰嗦)。你问:
这是否意味着每个变量都是表达式?
这意味着任何变量都可以是表达式或表达式的一部分。以下是语言标准如何定义术语表达式:
6.5 表达式
1 一个表达式是一系列操作符和操作数,它指定计算值、指定对象或函数、生成副作用或执行这些组合的序列。
例如,单独的变量a
被视为一个表达式;它指定了我们定义的用于保存10个整数值的数组对象。它还评估为数组的第一个元素的地址。变量a
也可以是一个更大表达式的一部分,如a[i]
;运算符是下标运算符[]
,操作数是变量a
和i
。该表达式指定数组的一个成员,并评估为当前存储在该成员中的值。该表达式又可以是一个更大表达式的一部分,如a[i] = 0
。
并且让我澄清一下,在声明int a[10]中,a[]是否表示数组类型
是的,确切地说。
在C语言中,声明基于表达式的类型,而不是对象的类型。如果你有一个名为y
的简单变量,它存储一个int
值,并且你想要访问该值,你只需在表达式中使用y
,如:
x = y;
y表达式的类型是int
,因此声明y
的写法如下:
int y;
如果您拥有一个整数值的 数组
,并且想要访问特定元素,您可以使用数组名称和索引以及下标运算符来访问该值,例如:
x = a[i];
a[i]表达式的类型是int
,因此数组的声明如下所示:
int arr[N]; // for some value N.
arr
"的"int
-ness"由类型说明符"int
"给出;"arr
"的"array-ness"则由声明符"arr[N]
"给出。声明符给出被声明对象的名称("arr
")以及类型说明符未提供的一些额外类型信息("是一个N元素数组")。这个声明可读作: a -- a
a[N] -- is an N-element array
int a[N]; -- of int
EDIT2
经过这么多讲解,我还没有告诉你数组表达式为什么是不可修改的左值的真正原因。所以,这里又是一个关于这个问题的章节。
C语言并非Dennis Ritchie一蹴而就的产物;它是由一种早期语言B演变而来(而B又是由BCPL演变而来)。1 B是一种“无类型”语言;它没有针对整数、浮点数、文本、记录等不同类型的定义。相反,所有东西都只是固定长度的单元或“单元格”(基本上是无符号整数)。内存被视为单元格的线性数组。当你在B中分配一个数组时,例如:
auto V[10];
+----+
V: | | -----+
+----+ |
... |
+----+ |
| | <----+
+----+
| |
+----+
| |
+----+
| |
+----+
...
struct
类型时,他意识到这种安排会给他带来一些问题。例如,他想创建一个结构体类型来表示文件或目录表中的条目:struct {
int inumber;
char name[14];
};
这就是为什么你不能像这样做的原因。
int a[N], b[N];
a = b;
arr
的“数组性”是由声明符 arr[N]
给出的。由于声明符是正在被声明的对象的名称,可能被操作符如 *
、[]
或 ()
包围,所以 arr
的数组性难道不是由 []
操作符给出的吗? - haccksarr
是 标识符。arr[N]
是 声明符。 - John Bodearr
是一个int
数组;当代码中出现表达式arr[i]
(例如像x = arr[i]
这样的语句)时,该表达式具有int
类型的值。C语言的设计使得声明的形式与代码中表达式的形式非常相似。因此,当我们声明一个数组时,我们写成T a[N];
,因为声明a[N]
看起来像代码中的表达式a[i]
,如x = a[i]
。 - John Bodeint a [10]; int (* ptr) [10] =&a;
C 在这里宽容;C++则不是。带有正确类型的版本(&a
)在两种语言中都可以正常工作。 - Ben Voigta[i]
does not work with lvalue of array type. a[i]
modifies an int
object.* - haccksint a[10]
是什么意思? - haccks不完全类型是指已声明但未定义的类型,例如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};
int i = 1;
也不是赋值语句吗? - haccksconst int i = 1;
也是有效的,尽管您无法更改(分配)i
的值。 - Johannes Overmanna
还是 a[10]
? - haccksa
是正在初始化的对象。C语法似乎很直观,但仍然令人困惑。a[10]
作为声明或定义与a[10]
作为表达式(数组指示器和大小与数组查找)非常不同。 - Johannes Overmanna
是一个整数数组,a[10]
不是数组,它是一个int
类型的变量。
a = {0}
是非法的。a = 1
呢? - haccks请记住,数组的值实际上是其第一个元素的地址(指针)。这个地址是无法修改的。因此,
int a[10], b[10];
a = b
是非法的。
当然,这与修改数组的内容无关,如 a[1] = 3
。
int a[10]
到double a[10]
。 - Ran Eldana = value
和a[i] = value
是有区别的。 - user142162