为什么数组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元素数组”; 除非a
是sizeof
或unary&
运算符的操作数,则expression式将转换为“指向int
指针”types的expression式,其值将是数组中第一个元素的地址; - expression式
a[i]
的types是int
; 它指的是存储为数组的第i
个元素的整数对象; - expression式
a
可能不是一个赋值的目标,因为C不像其他variables那样处理数组,所以你不能写a = b
或a = 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; };
因为当我们声明left
和right
成员时,types是不完整的,因为每个left
和right
成员都会包含它们自己的left
和right
成员,每个成员都包含自己的left
和right
成员,并继续。
这对工会和工会来说很好,但那又如何?
int bar[];
???
我们已经声明了符号bar
并指出它将是一个数组types,但是在这一点上的大小是未知的。 最后,我们必须用大小来定义它,但是这样符号可以用在数组大小没有意义或不必要的上下文中。 尽pipe如此,我的头顶上还没有一个好的,没有人为的例子来说明这一点。
编辑
回应这里的评论,因为我想写的东西在评论部分没有空间(今天晚上我心情很冗长)。 你问:
这是否意味着每个variables都是expression式?
这意味着任何variables都可以是expression式,也可以是expression式的一部分。 以下是语言标准如何定义术语expression式 :
6.5expression式
1 expression式是一系列运算符和操作数,它们指定计算值,指定对象或函数,或者生成副作用,或者执行其组合。
例如,variablesa
本身就是一个expression式; 它指定我们定义的数组对象来保存10个整数值。 它也评估到数组的第一个元素的地址。 variablesa
也可以是像a[i]
这样a[i]
较大expression式的一部分; 操作符是下标操作符[]
,操作数是variablesa
和i
。 该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添加struct
types时,他意识到这种安排给他带来了一些问题。 例如,他想创build一个结构types来表示文件或目录表中的一个条目:
struct { int inumber; char name[14]; };
他希望结构不仅仅以抽象的方式描述条目,而且要表示实际文件表项中没有额外的单元或单词来存储arrays中第一个元素位置的位。 所以他摆脱了它 – 而不是放置一个单独的位置来存储第一个元素的地址,他写了C,以便在计算数组expression式时计算第一个元素的地址。
这就是为什么你不能做这样的事情
int a[N], b[N]; a = b;
因为a
和b
都在这个上下文中评估指针值 ; 这相当于写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, void
types。
不完整的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