为什么数组types对象是不可修改的?

这里说明了这一点

术语可修改的左值用于强调左值允许指定的对象被改变以及被检查。 以下对象types是左值,但不能修改左值:

  • 一个数组types
  • 一个不完整的types
  • 一个const限定的types
  • 一个结构或联合types的一个成员资格为consttypes

因为这些左值是不可修改的,所以它们不能出现在赋值语句的左边。

为什么数组types对象是不可修改的? 写这个不正确吗?

int i = 5, a[10] = {0}; a[i] = 1; 


而且,什么是不完整的types?

假设声明

 int a[10]; 

那么以下所有内容都是正确的:

  • expression式 a的types是“ int 10元素数组”; 除非asizeof或unary &运算符的操作数,则expression式将转换为“指向int指针”types的expression式,其值将是数组中第一个元素的地址;
  • expression式 a[i]的types是int ; 它指的是存储为数组的第i个元素的整数对象;
  • expression式 a可能不是一个赋值的目标,因为C不像其他variables那样处理数组,所以你不能写a = ba = malloc(n * sizeof *a)东西。

你会注意到我一直强调“expression”这个词。 我们用来存储10个整数的内存块和我们用来引用这块内存块的符号(expression式)之间有区别。 我们可以用expression式a来引用它。 我们也可以创build一个指向该数组的指针:

 int (*ptr)[10] = &a; 

expression式*ptr也具有types“ int 10元素数组”,并且它引用与a相同的内存块。

C不会像其他types的expression式那样处理数组expression式( a*ptr ),其中一个区别是数组types的expression式可能不是赋值的目标。 您不能重新分配a引用不同的数组对象(对于expression式*ptr相同)。 您可以 a[i](*ptr)[i] (更改每个数组元素的值(*ptr)[i]分配一个新值,并且可以将ptr指派给另一个数组:

 int b[10], c[10]; ..... ptr = &b; ..... ptr = &c; 

至于第二个问题

不完整的types缺乏尺寸信息; 声明像

 struct foo; int bar[]; union bletch; 

都会创build不完整的types,因为编译器没有足够的信息来确定要为该types的对象留出多less存储空间。 您不能创build不完整types的对象; 例如,你不能申报

 struct foo myFoo; 

除非你完成struct foo的定义。 但是,您可以创build指向不完整types的指针 ; 例如,你可以声明

 struct foo *myFooPtr; 

没有完成struct foo的定义,因为指针只是存储对象的地址,而不需要知道types的大小。 这使得可以定义自我指涉types

 struct node { T key; // for any type T Q val; // for any type Q struct node *left; struct node *right; }; 

struct node的types定义在我们打到closures之前是不完整的 } 。 由于我们可以声明一个指向不完整types的指针,所以我们没问题。 但是,我们无法将结构定义为

 struct node { ... // same as above struct node left; struct node right; }; 

因为当我们声明leftright成员时,types是不完整的,因为每个leftright成员都会包含它们自己的leftright成员,每个成员都包含自己的leftright成员,并继续。

这对工会和工会来说很好,但那又如何?

 int bar[]; 

???

我们已经声明了符号bar并指出它将是一个数组types,但是在这一点上的大小是未知的。 最后,我们必须用大小来定义它,但是这样符号可以用在数组大小没有意义或不必要的上下文中。 尽pipe如此,我的头顶上还没有一个好的,没有人为的例子来说明这一点。

编辑

回应这里的评论,因为我想写的东西在评论部分没有空间(今天晚上我心情很冗长)。 你问:

这是否意味着每个variables都是expression式?

这意味着任何variables都可以是expression式,也可以是expression式的一部分。 以下是语言标准如何定义术语expression式

6.5expression式
1 expression式是一系列运算符和操作数,它们指定计算值,指定对象或函数,或者生成副作用,或者执行其组合。

例如,variablesa本身就是一个expression式; 它指定我们定义的数组对象来保存10个整数值。 它也评估到数组的第一个元素的地址。 variablesa也可以是像a[i]这样a[i]较大expression式的一部分; 操作符是下标操作符[] ,操作数是variablesai 。 该expression式指定数组的单个成员,并且计算出该成员中正确存储的值。 这个expression又可以是一个更大的expression式的一部分,比如a[i] = 0

而且让我明白,在声明int a [10]中,a []代表数组types

对,就是这样。

在C中,声明基于expression式的types,而不是对象的types。 如果你有一个名为y的简单variables存储一个int值,并且你想访问这个值,那么你只需要在expression式中使用y

 x = y; 

expression式 y的types是int ,因此y的声明被写入

 int y; 

另一方面,如果你有一个int值的数组 ,并且你想访问一个特定的元素,你可以使用数组名和一个索引以及下标运算符来访问这个值,就像

 x = a[i]; 

expression式 a[i]的types是int ,所以数组的声明被写为

 int arr[N]; // for some value N. 

arr的“ int -ness”由types说明符int ; arr的“数组”由声明者arr[N] 。 声明符给出了我们声明的对象的名字( arr )以及types说明符(“是一个N元素数组”)给出的一些额外的types信息。 声明“读取”为

  a -- a a[N] -- is an N-element array int a[N]; -- of int 

编辑2

毕竟,我还没有告诉过你为什么数组expression式是不可修改的左值。 所以这本书的答案还有另外一章。

C并没有从丹尼斯·里奇的思想中完全形成, 它来源于一个早期的B语言(来源于BCPL)。 1B是一种“无types”的语言; 它没有整数,浮点数,文本,logging等不同的types。相反,一切都只是一个固定长度的单词或“单元格”(本质上是一个无符号整数)。 记忆被视为一个线性的细胞arrays。 当你在B中分配一个数组时,比如

 auto V[10]; 

编译器分配了11个单元; 数组10个连续的单元格,加上一个绑定到V的单元格,包含第一个单元格的位置:

  +----+ V: | | -----+ +----+ | ... | +----+ | | | <----+ +----+ | | +----+ | | +----+ | | +----+ ... 

当里奇给C添加structtypes时,他意识到这种安排给他带来了一些问题。 例如,他想创build一个结构types来表示文件或目录表中的一个条目:

 struct { int inumber; char name[14]; }; 

他希望结构不仅仅以抽象的方式描述条目,而且要表示实际文件表项中没有额外的单元或单词来存储arrays中第一个元素位置的位。 所以他摆脱了它 – 而不是放置一个单独的位置来存储第一个元素的地址,他写了C,以便在计算数组expression式时计算第一个元素的地址。

就是为什么你不能做这样的事情

 int a[N], b[N]; a = b; 

因为ab都在这个上下文中评估指针 ; 这相当于写3 = 4 。 内存中没有任何内容实际存储数组中第一个元素的地址; 编译器只是在翻译阶段计算它。


这一切都来自C语言的发展

术语“数组types的左值”实际上指数组对象作为数组types的左值,即数组对象作为整体 。 这个左值不能作为一个整体来修改,因为没有合法的操作可以修改它作为一个整体。 实际上,你可以在数组types的左值上执行的唯一操作是:一元& (地址), sizeof和隐式转换为指针types。 这些操作都不会修改数组,这就是数组对象不可修改的原因。

a[i]不适用于数组types的左值。 a[i]指定一个int对象:数组a的第i个元素。 这个expression式的语义(如果明确阐述的话)是: *((int *) a + i) 。 第一步 – (int *) a – 已经将数组types的左值转换为int *types的右值。 在这一点上,数组types的左值超出了图片的范围。

不完整的types是一个大小尚未知道的types。 例如:已声明但未定义的结构types,具有未指定大小的数组types, voidtypes。

不完整的types是一个声明但没有定义的types,例如struct Foo;

你总是可以分配给各个数组元素 (假设它们不是const )。 但是你不能把一些东西分配给整个数组。

C和C ++在int a[10] = {0, 1, 2, 3}; 不是一个赋值,而是一个初始化,即使它看起来非常像一个赋值。

这是OK(初始化):

 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