为什么数组类型对象不可修改?

hac*_*cks 11 c arrays lvalue

据说这里

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

  • 数组类型
  • 不完整的类型
  • const限定类型
  • 结构或联合类型,其成员之一被限定为const类型

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

为什么数组类型对象不可修改?写不正确

int i = 5, a[10] = {0};    
a[i] = 1;
Run Code Online (Sandbox Code Playgroud)


而且,什么是不完整的类型?

Joh*_*ode 20

假设声明

int a[10];
Run Code Online (Sandbox Code Playgroud)

那么以下所有都是真的:

  • 表达式 的类型a是"10元素数组int"; 除非asizeof或一元运算&符的操作数,表达式将转换为"指向int" 的类型的表达式,其值将是数组中第一个元素的地址;
  • 表达式 的类型a[i]int; 它指的是存储为i数组的第th个元素的整数对象;
  • 表达 a可能不是一个赋值的目标,因为C不会像对待其他变量数组,所以你不能写类似a = ba = malloc(n * sizeof *a)之类的东西.

你会注意到我一直在强调"表达"这个词.我们用来保存10个整数的内存块和我们用来引用那块内存的符号(表达式)之间有区别.我们可以用表达式来引用它a.我们还可以创建一个指向该数组的指针:

int (*ptr)[10] = &a;
Run Code Online (Sandbox Code Playgroud)

该表达式*ptr还具有类型"10-element array of int",它指的是引用的相同内存块a.

C不会像处理其他类型的表达式那样处理数组表达式(a,*ptr),其中一个区别是数组类型的表达式可能不是赋值的目标.您无法重新分配a以引用其他数组对象(表达式相同*ptr).您可以a[i]或分配一个新值或(*ptr)[i](更改每个数组元素的值),您可以指定ptr指向另一个数组:

int b[10], c[10];
.....
ptr = &b;
.....
ptr = &c;
Run Code Online (Sandbox Code Playgroud)

至于第二个问题......

一个不完整的型缺乏尺寸信息; 声明如

struct foo;
int bar[];
union bletch;
Run Code Online (Sandbox Code Playgroud)

所有都创建不完整的类型,因为编译器没有足够的信息来确定为该类型的对象预留多少存储空间.你不能创建不完整类型的对象; 例如,你不能声明

struct foo myFoo;
Run Code Online (Sandbox Code Playgroud)

除非你完成了定义struct foo.但是,您可以创建指向不完整类型的指针 ; 例如,你可以声明

struct foo *myFooPtr;
Run Code Online (Sandbox Code Playgroud)

没有完成定义,struct foo因为指针只存储对象的地址,你不需要知道类型的大小.这使得定义自引用类型成为可能

struct node {
  T key;  // for any type T
  Q val;  // for any type Q
  struct node *left; 
  struct node *right;
};
Run Code Online (Sandbox Code Playgroud)

在我们达到结束之前,类型定义struct node尚未完成}.既然我们可以声明指向不完整类型的指针,那我们没问题.但是,我们无法将结构定义为

struct node {
  ... // same as above
  struct node left;
  struct node right;
};
Run Code Online (Sandbox Code Playgroud)

因为当我们声明的类型是不完整的leftright会员,并且还因为每个leftright成员将各自包含leftright自己的成员,每个成员都将包含leftright成员他们自己,以及和和.

这对结构和工会来说很好,但是怎么样呢

int bar[];
Run Code Online (Sandbox Code Playgroud)

???

我们已经声明了符号bar并指出它将是一个数组类型,但此时此大小尚不清楚. 最终我们必须使用大小来定义它,但是这样,符号可以在数组大小没有意义或不必要的上下文中使用.但是,没有一个好的,非人为的例子来说明这一点.

编辑

回应这里的评论,因为评论部分没有足够的空间来处理我想写的内容(今晚我的心情很啰嗦).您询问:

这是否意味着每个变量都是表达式?

这意味着任何变量都可以是表达式,也可以是表达式的一部分.以下是语言标准定义术语表达式的方式:

6.5表达式
1 表达式是一系列运算符和操作数,用于指定值的计算,或指定对象或函数,或生成副作用或执行其组合的操作和操作数.

例如,变量aall本身就算作表达式; 它指定我们定义的数组对象,以保存10个整数值.它还会计算数组第一个元素的地址.变量a也可以是更大表达式的一部分a[i]; 运算符是下标运算符[],操作数是变量ai.此表达式指定数组的单个成员,并计算当前存储在该成员中的值.反过来,这个表达式可以成为更大表达式的一部分a[i] = 0.

并且让我明确一点,在声明int [10]中,a []代表数组类型

对,就是这样.

在C中,声明基于表达式的类型,而不是对象的类型.如果您有一个名为y存储int值的简单变量,并且您想要访问该值,则只需y在表达式中使用,例如

x = y;
Run Code Online (Sandbox Code Playgroud)

表达式 的类型yint,所以声明y是写的

int y;
Run Code Online (Sandbox Code Playgroud)

如果,另一方面,你有一个数组int值,你要访问一个特定的元素,你会沿着使用数组名和索引下标操作符来访问该值,如

x = a[i];
Run Code Online (Sandbox Code Playgroud)

表达式 的类型a[i]int,所以数组的声明写成

int arr[N]; // for some value N.  
Run Code Online (Sandbox Code Playgroud)

" int-ness" arr由类型说明符给出int; arr声明者给出了"array-ness" arr[N].声明器为我们提供了声明的对象的名称(arr)以及类型说明符未给出的一些其他类型信息("是一个N元素数组").宣言"读"为

    a       -- a
    a[N]    -- is an N-element array
int a[N];   -- of int
Run Code Online (Sandbox Code Playgroud)

编辑2

毕竟,我还没有告诉你数组表达式是不可修改的左值的真正原因.所以这是本书答案的又一章.

C并没有完全由Dennis Ritchie的思想形成; 它源于早期的B语言(源自BCPL).1 B是一种"无类型"语言; 它没有用于整数,浮点数,文本,记录等的不同类型.相反,一切都只是一个固定长度的单词或"单元格"(本质上是一个无符号整数).记忆被视为线性细胞阵列.在B中分配数组时,例如

auto V[10];
Run Code Online (Sandbox Code Playgroud)

编译器分配了11个单元; 阵列本身的10个连续单元格,以及绑定到包含第一个单元格位置的V的单元格:

    +----+
V:  |    | -----+
    +----+      |
     ...        |
    +----+      |
    |    | <----+
    +----+
    |    |
    +----+
    |    |      
    +----+
    |    |
    +----+
     ...
Run Code Online (Sandbox Code Playgroud)

当Ritchie struct向C 添加类型时,他意识到这种安排给他带来了一些问题.例如,他想创建一个结构类型来表示文件或目录表中的条目:

struct {
  int inumber;
  char name[14];
};
Run Code Online (Sandbox Code Playgroud)

他希望结构不仅以抽象方式描述条目,而且还表示实际文件表条目中的位,这些位没有额外的单元格或单词来存储数组中第一个元素的位置.所以他摆脱了它 - 而不是留出一个单独的位置来存储第一个元素的地址,他写了C,这样在计算数组表达式时将计算第一个元素的地址.

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

int a[N], b[N];
a = b;
Run Code Online (Sandbox Code Playgroud)

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


这一切都取自"C语言的发展"一文


AnT*_*AnT 6

术语"数组类型的左值"字面上将数组对象称为数组类型的左值,即整个数组对象.这个左值不是一个整体可修改的,因为没有合法的操作可以整体修改它.实际上,您可以对数组类型的左值执行的唯一操作是:一元&(地址)sizeof和隐式转换为指针类型.这些操作都不会修改数组,这就是数组对象不可修改的原因.

a[i]不适用于数组类型的左值.a[i]指定一个int对象:数组的第i个元素a.此表达式的语义(如果明确拼写)是:*((int *) a + i).第一步 - (int *) a已经将数组类型的左值转换为类型的右值int *.此时,数组类型的左值不再合适.

不完整类型是一种其大小尚未知晓的类型.例如:已声明但未定义的结构类型,具有未指定大小的数组类型,void类型.