数组

为什么我们需要数组?

数组提供了一种将对象组织成具有其自身重要性的聚合的方法。例如,C 字符串是字符数组(chars),字符串如 Hello World!。具有作为一个单独的字符不固有的聚合的含义。类似地,数组通常用于表示数学向量和矩阵,以及多种类型的列表。此外,如果没有某种方法对元素进行分组,则需要单独处理每个元素,例如通过单独的变量。它不仅不实用,而且不容易适应不同长度的集合。

在大多数上下文中,数组被隐式转换为指针

除了作为 sizeof 运算符,_Alignof 运算符(C2011)或一元 &(取址)运算符的操作数出现,或者作为用于初始化(其他)数组的字符串文字时,数组被隐式转换为指向其第一个元素的指针。这种隐式转换与数组下标运算符([])的定义紧密耦合:表达式 arr[idx] 被定义为等同于*(arr + idx)。此外,由于指针算术是可交换的,*(arr + idx) 也相当于*(idx + arr),而*(idx + arr) 又相当于 toidx[arr]。只要 idxarr 是一个指针(或一个衰减到指针的数组),另一个是整数,所有这些表达式都是有效的并且计算得到相同的值。

作为一个特例,观察 &(arr[0]) 相当于 &*(arr + 0),这简化为 arr。所有这些表达式在最后衰落到指针的任何地方都是可以互换的。这再次简单地表达了一个数组衰减到指向其第一个元素的指针。

相反,如果取址运算符应用于 T[N] 类型的数组( &arr),那么结果的类型为 T (*)[N] 并指向整个数组。这与至少针对指针算术的指向第一数组元素的指针不同,指针算术是根据指向类型的大小来定义的。

函数参数不是数组

void foo(int a[], int n);
void foo(int *a, int n);

虽然 foo 的第一个声明对参数 a 使用类似数组的语法,但这种语法用于声明一个函数参数,声明该参数作为指向数组元素类型的指针。因此,foo() 的第二个签名在语义上与第一个签名相同。这对应于数组值到指针的衰减,它们作为函数调用的参数出现,这样如果变量和函数参数声明为具有相同的数组类型,那么该变量的值适合在函数调用中使用,因为与参数关联的参数。