数组包含给定类型的一些对象,并将这些对象依次存储在连续的内存空间中。每个独立的对象被称为 数组的元素(element) 。 元素的类型可以是任何对象类型,但函数类型或不完整类型
数组本身也是一个对象,其类型由它的元素类型延伸而来。更具体地说,数组的类型由元素的类型和数量所决定。
如果一个数组的元素是 T 类型,那么该数组就称为“T 数组”。例如,如果元素类型为 int,那么该数组的类型就是“int 数组”。然而,int 数组类型是不完整的类型,除非指定了数组元素的数量。如果一个 int 数组有 16 个元素,那么它就是一个完整的对象类型,即“16 个 int 元素数组”。
数组的定义决定了数组名称、元素类型以及元素个数。没有显式初始化操作的数组定义,其语法如下:
类型 名称[元素数量];
元素数量在方括号([])之间,它必须是大于 0 的整数表达式。示例:
char buffer[4*512];
这一行代码定义了一个名为 buffer 的数组,它包含 2048 个 char 类型元素。
可以利用 sizeof 运算符获取对象所占内存空间的大小。数组在内存中的空间大小总是等于一个元素的空间大小乘以数组中元素的个数。因此,上述例子中的 buffer 数组,表达式 sizeof(buffer)会产生 2048*sizeof(char)的值。换句话说,buffer 数组占用 2048 个内存字节,因为 sizeof(char)等于 1。
在数组定义中,可以将元素数量指定为一个常量表达式,或者在特定情况下,指定为涉及变量的表达式。采用这两种方式定义的数组分别被称为固定长度数组(fixed-length)和长度可变(variable-length)数组。
固定长度数组
固定长度数组可以具有任意存储类别:可以将它们定义在所有函数的外面或语句块的里面,并且可以使用或不使用存储类修饰符 static。唯一的限制是数组不能作为函数参数。一个传入函数的数组参数需要被转换为指向数组第一个元素的指针。下面 4 种数组定义方式都是合法的:
int a[10]; // a有外部链接 static int b[10]; // b有静态存储周期和文件作用域 void func() { static int c[10]; // c有静态存储周期和块作用域 int d[10]; // d有动态存储周期 /* ... */ }
长度可变数组
如果一个数组具有动态存储周期(也就是说,如果在语句块内定义数组,并且没有 static 修饰符),那么 C99 也允许把非常量表达式作为元素数量来定义该数组。这样的数组被称为长度可变数组(variable-length array)。而且,长度可变数组的名称必须是普通的标识符。长度可变数组不能作为结构或联合的成员。在下面的示例中,只有 vla 数组的定义是合法的:
void func( int n ) { int vla[2*n]; // 合法:存储周期为动态的 static int e[n]; // 非法:长度可变数组不可有静态存储周期 struct S { int f[n]; }; // 非法:f不是一个普通标识符 /* ... */ }
与其他动态变量一样,每次程序流进入包含长度可变数组定义的语句块时,都会重新创建这个长度可变数组。因此,在每次实例化时,数组都可以有不同的长度。然而,一旦被创建,即便是长度可变数组,在它的当前存储周期内也不能改变数组长度。
动态对象被存储在栈中,当程序流离开对象所在的语句块时,动态对象的空间就会被释放。因此,只有对小的、临时的数组,定义长度可变数组才比较合理。如想动态地创建大型数组,通常应该使用标准函数 malloc()和 calloc()来显式地分配内存空间。
这种数组的存储周期会持续到程序结束,也可以调用函数 free()来主动地释放被占用的内存空间。