陈述here
术语可修改的左值用于强调左值允许指定的对象被改变以及被检查。以下对象类型是左值,但不是可修改的左值:
- 数组类型
- 不完整类型
- const限定类型
- 一个结构或联合类型,其中一个成员被限定为const类型
因为这些左值不可修改,所以它们不能出现在赋值语句的左侧。
为什么数组类型对象不可修改?写
是不正确的int i = 5, a[10] = {0};
a[i] = 1;
?
而且,什么是不完整的类型?
答案 0 :(得分:18)
假设声明
int a[10];
然后以下所有情况都是正确的:
a
是"属于int
"的10元素数组;除非a
是sizeof
或一元&
运算符的操作数,否则表达式将转换为&#34类型的表达式;指向int
&#34的指针;它的值将是数组中第一个元素的地址; a[i]
为int
;它指的是整数对象,存储为数组的i
'元素; a
可能不是作业的目标,因为C不像其他变量那样处理数组,所以你不能写a = b
或{{1或类似的东西。你会注意到我一直在强调单词"表达"。我们预留的用于保存10个整数的内存块与用于引用该内存块的符号(表达式)之间存在差异。我们可以用表达式a = malloc(n * sizeof *a)
来引用它。我们还可以创建一个指向该数组的指针:
a
表达式int (*ptr)[10] = &a;
还具有*ptr
"的类型" 10元素数组,它指的是int
引用的同一块内存。
C不会像处理其他类型的表达式那样处理数组表达式(a
,a
),其中一个区别是 array 类型的表达式可能不是作业的目标。您不能重新指定*ptr
来引用不同的数组对象(表达式a
也是如此)。您可能为*ptr
或a[i]
分配新值(更改每个数组的值元素),您可以指定{{1}指向不同的数组:
(*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
成员都包含{{ 1}}和left
个自己的成员,每个成员都包含他们的拥有的right
和left
成员,并且依次开启。
这对结构和工会来说非常棒,但是
呢right
???
我们已经声明了符号left
,并表示它将是一个数组类型,但此时此大小未知。 最终我们必须用大小来定义它,但这样符号可以用在数组大小没有意义或必要的上下文中。尽管如此,不要在我的头顶上有一个好的,非人为的例子来说明这一点。
修改强>
回应这里的评论,因为在评论部分没有空间来表达我想写的内容(今晚我的心情很冗长)。你问:
这是否意味着每个变量都是表达式?
这意味着任何变量都可以是表达式,也可以是表达式的一部分。以下是language standard定义术语表达式的方式:
6.5表达
1 表达式是一系列运算符和操作数,用于指定a的计算 值,或指定对象或功能,或产生副作用,或 执行它们的组合。
例如,变量right
本身都算作表达式;它指定我们定义的数组对象,以保存10个整数值。它还会计算数组第一个元素的地址。变量int bar[];
也可以是更大表达式的一部分,如bar
;运算符是下标运算符a
,操作数是变量a
和a[i]
。此表达式指定数组的单个成员,并计算当前存储在该成员中的值。该表达式反过来可以成为像[]
这样的更大表达式的一部分。
并且让我明确一点,在声明int a [10]中,a []代表数组类型
是的,确实。
在C语言中,声明基于表达式的类型,而不是对象的类型。如果您有一个名为a
的简单变量存储i
值,并且您想要访问该值,则只需在表达式中使用a[i] = 0
,例如
y
表达式 int
的类型为y
,因此x = y;
的声明已写入
y
另一方面,如果您有一个数组的int
值,并且您想要访问特定元素,那么您将使用数组名称和索引以及用于访问该值的下标运算符,如
y
表达式 int y;
的类型是int
,因此数组的声明写为
x = a[i];
" a[i]
- ness" int
的{{1}}由类型说明符int arr[N]; // for some value N.
给出; "阵列" int
的{{1}}由声明者arr
给出。声明器为我们提供了声明对象的名称(int
)以及类型说明符未给出的一些其他类型信息("是一个N元素数组")。声明"读取"如
arr
修改 2 的
毕竟,我仍然没有告诉你真正的理由为什么数组表达式是不可修改的左值。所以,这是本书答案的又一章。
从丹尼斯·里奇的心中完全没有形成完全形成的C;它来源于早期的B语言(源自BCPL)。 1 B是一种无类型的"语言;对于整数,浮点数,文本,记录等,它没有不同的类型。相反,一切都只是一个固定长度的单词或" cell" (基本上是无符号整数)。记忆被视为线性细胞阵列。在B中分配数组时,例如arr[N]
编译器分配了11个单元;阵列本身的10个连续单元格,以及绑定到包含第一个单元格位置的V的单元格:
arr
当Ritchie向C添加 a -- a
a[N] -- is an N-element array
int a[N]; -- of int
类型时,他意识到这种安排给他带来了一些问题。例如,他想创建一个结构类型来表示文件或目录表中的条目:
auto V[10];
他希望结构不仅以抽象方式描述条目,而且还要表示实际文件表条目中的位,这些位没有额外的单元格或单词来存储第一个位置。数组中的元素。所以他摆脱了它 - 而不是留出一个单独的位置来存储第一个元素的地址,他写了C,这样在计算数组表达式时将计算第一个元素的地址。
此是您无法执行
之类的操作的原因 +----+
V: | | -----+
+----+ |
... |
+----+ |
| | <----+
+----+
| |
+----+
| |
+----+
| |
+----+
...
因为struct
和struct {
int inumber;
char name[14];
};
在该上下文中评估指针值;它等同于写int a[N], b[N];
a = b;
。内存中没有任何东西实际上存储数组中第一个元素的地址;编译器只是在翻译阶段计算它。
<小时/> <子> 1。这全部来自论文The Development of the C Language
答案 1 :(得分:6)
术语“数组类型的左值”字面上将数组对象称为数组类型的左值,即数组对象作为整体。这个左值不是一个整体可修改的,因为没有合法的操作可以整体修改它。实际上,您可以对数组类型的左值执行的唯一操作是:一元&
(地址),sizeof
以及隐式转换为指针类型。这些操作都不会修改数组,这就是数组对象无法修改的原因。
a[i]
不适用于数组类型的左值。 a[i]
指定int
对象:数组a
的第i个元素。此表达式的语义(如果明确说明)是:*((int *) a + i)
。第一步 - (int *) a
- 已经将数组类型的左值转换为类型int *
的右值。此时,数组类型的左值不合适。
不完整类型是一种其大小尚未知晓的类型。例如:已声明但未定义的结构类型,具有未指定大小的数组类型,void
类型。
答案 2 :(得分:4)
不完整类型是声明但未定义的类型,例如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};
答案 3 :(得分:2)
假设a
是一个整数数组,a[10]
不是一个数组。这是int
。
a = {0}
将是非法的。
答案 4 :(得分:1)
请记住,数组的值实际上是其第一个元素的地址(指针)。此地址无法修改。所以
int a[10], b[10];
a = b
是非法的。
当然,与a[1] = 3