数组(array)是一种特殊的变量,同一变量名下,通过索引存放一组同类型数据。它的定义语法写法如下:
int numbers[10]; // 定义一个有 10 个 int 的数组下标从 0 开始
访问数组元素用的和定义是同一套语法:方括号里写下标。注意C 数组下标从 0 开始,长度 10 的数组,合法下标是 0 到 9;numbers[10] 并不是真实存在的元素,属于越界。
int numbers[10];
numbers[0] = 10; // 第 1 个
numbers[1] = 20; // 第 2 个
numbers[2] = 30;
numbers[3] = 40;
numbers[4] = 50;
numbers[5] = 60;
numbers[6] = 70; // 第 7 个
/* 打印第 7 个数,它的下标是 6 */
printf("数组第 7 个数是 %d\n", numbers[6]);数组只能装一种类型
数组在内存中占用一段连续空间,存放相同类型的数据。这也就决定了数组只能保存单一类型元素,要么全部是 int,要么全部是 double,不能混合多种类型。也正因如此,按下标访问任何一个元素非常快,地址可以"首地址 + 下标 × 单个大小"直接算出来,这就是语言设计者愿意用这个限制换来的性能。
声明与初始化
声明数组时编译器就按尺寸划好内存,里面的值一开始是垃圾值随机残留,必须初始化。初始化有几种写法:
int a[3] = {10, 20, 30}; // 依次填满
int b[] = {1, 2, 3, 4, 5}; // 省略数量,编译器按右边自动数
int c[5] = {1, 2}; // 部分初始化,其余自动补 0
int d[5] = {0}; // 全部清零的偷懒写法
/* 声明之后再整段赋 {} 不行,只能逐个元素赋值 */
a[0] = 99;初始化之后想改某个元素,跟访问一样写下标赋值即可:a[2] = 77;
用循环遍历数组
既然有下标,拿 for 一路扫过去是最基本操作。正着、反着都能扫:
#include <stdio.h>
int main(void) {
int arr[5] = {2, 4, 8, 12, 16};
printf("正序:");
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
printf("\n倒序:");
for (int i = 4; i >= 0; i--) {
printf("%d ", arr[i]);
}
printf("\n");
int size = sizeof(arr) / sizeof(arr[0]); // 5
printf("数组里有 %d 个元素\n", size);
return 0;
}求元素个数有个固定套路:sizeof(arr) / sizeof(arr[0]),整个数组占的字节数,除以一个元素占的字节数。注意这个写法只在数组所在的那个作用域里有效,数组传进函数后就不灵了(指针篇解释)。
越界:C 不设防的地方
int a[3] = {1, 2, 3};
a[3] = 100; // 越界,合法下标只到 2这句能编译、通常也不报错,但它往里写进了数组后面的内存,可能是别的变量、别的重要数据。Java 越界会直接抛异常,C 语言把责任全交给你,代价就是现在没出事,出事了才致命。写循环时记住 i < 长度 而不是 i <= 长度,是最实在的预防。
二维数组:行和列
数组里再装数组就是二维,用 [行][列] 两个下标访问:
int grid[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
for (int r = 0; r < 3; r++) {
for (int c = 0; c < 4; c++) {
printf("%2d ", grid[r][c]);
}
printf("\n");
}内存里其实还是连续排布(按行优先),两层方括号只是让你在行列的直觉下操作。地图、棋盘、矩阵都用得上。
下一篇的主角是 C 语言里最常用的特殊数组,字符串。