数组
学习如何定义和访问数组,以及了解C语言中数组是如何实现的为何至关重要。
在 C 语言中,数组是一组存储在连续内存位置中的数据项。到目前为止,我们已经学习了如何使用变量来存储单个值。而数组的用途在于,它允许我们通过一个变量来处理一组值。
数组中每个元素的物理位置由一个整数指定,称为索引。在 C 语言中,第一个索引始终从 0 开始(与某些其他语言如 MATLAB 不同,MATLAB 的索引从 1 开始)。
定义和索引数组
举个例子,假设我们要存储 5 个成绩。我们可以这样定义一个数组:
1int grades[5];
这条声明表示我们在内存中预留了 5 个整数大小的空间,并可以通过变量名 grades 来引用这块内存。需要注意的是,这里我们只是分配了内存空间,并未对数组元素进行初始化。当我们声明 grades 变量时,这些内存位置原本可能包含任意随机值,它们仍然保留在那里。我们可以通过索引访问这些值,例如:
1#include <stdio.h> 2 3int main(void) 4{ 5 int grades[5]; 6 int i; 7 for (i = 0; i < 5; i++) { 8 printf("grades[%d] = %d\n", i, grades[i]); 9 } 10 return 0; 11}
在第 8 行,grades[i] 访问的是数组中索引为 i 的元素当前存储的值。当你运行代码时,可能会看到不同的数值。
数组的底层机制
我们已经知道,数组的索引总是从 0 开始。此外还需要了解的是:一旦声明了一个固定大小的数组并分配了内存,其大小就固定不变了。也就是说,你无法动态扩展数组使其更大(或更小)。稍后我们会讨论使用 malloc 函数动态分配内存的情况,但目前请假设数组一旦声明,大小就是固定的。
1#include <stdio.h> 2 3int main(void) 4{ 5 int grades[5]; 6 printf("grades[5] = %d\n", grades[5]); 7 printf("grades[500] = %d\n", grades[500]); 8 return 0; 9}
实际上,编译器甚至不会给出警告。这凸显了 C 语言与其他高级语言(尤其是 Python 和 MATLAB 这类解释型语言)之间的根本区别——C 会严格按照你的指令执行,无论是否安全。
-
一段连续的内存被“预留”出来供使用。预留的内存大小等于数组元素个数乘以所声明元素类型的大小。例如,当我们声明
int grades[5];时,系统会预留 20 字节(5 × 4 字节)的连续内存空间。 -
变量名
grades指向这段分配的内存块。实际上,grades这个变量被赋予了数组第一个元素的内存地址(即预留的连续内存块的起始位置)。
注意:从技术上讲,可以说
grades是指向数组第一个元素内存地址的一个 指针(关于指针的内容将在后续讲解)。
当你对 grades 数组进行索引操作时,比如 grades[0] 或 grades[3],C 会根据数组起始地址(即 grades 指针)加上由索引值和基本类型大小决定的偏移量,定位到对应的内存位置。例如:
如你所见,这种缺乏“保护”的特性使得程序容易出现难以调试的错误。尤其危险的是,这一特性同样适用于向数组赋值。
现在我们已经能够定义数组,接下来就可以给它赋值并进行各种操作。