C: 数组
想象一排储物柜,每个柜子编号从0开始,里面存着同类的物品——这就是数组。它让你用一个名字管理一组相同类型的数据。
1. 一维数组的定义与初始化
数组是相同类型元素的有序集合,在内存中连续存放。定义时需要指明元素类型和数量。
int scores[5];
上面定义了一个包含5个int元素的数组,但内容未初始化,值是不确定的。建议定义时立即初始化:
int scores[5] = {90, 85, 78, 92, 88};
如果初始化列表比数组短,剩余元素自动填0:
int data[5] = {10, 20};
此时data[2]、data[3]、data[4]都是0。利用这个特性,可以将整个数组清零:
int zeros[100] = {0};
如果给出了完整初始化列表,可以省略数组长度,编译器会自动计算:
int days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
2. 下标访问
数组元素通过下标(索引)访问,下标从0开始。对于长度为N的数组,合法下标范围是0到N-1。
int scores[5] = {90, 85, 78, 92, 88};
int first = scores[0];
int last = scores[4];
scores[2] = 100;
下标可以是整型表达式,这为循环遍历提供了便利:
int i = 3;
int val = scores[i];
3. 数组越界风险
C语言不检查数组下标是否越界。访问越界元素属于未定义行为,可能读取到垃圾数据,可能修改其他变量的值,甚至导致程序崩溃。
int arr[3] = {10, 20, 30};
printf("%d\n", arr[3]);
printf("%d\n", arr[-1]);
上面两行都访问了非法位置。编译器不会警告,但结果不可预测。
<而非<=来对比长度。
4. 数组遍历
用for循环逐个处理数组元素是最常见的操作:
int scores[5] = {90, 85, 78, 92, 88};
int i;
for (i = 0; i < 5; i++) {
printf("scores[%d] = %d\n", i, scores[i]);
}
求所有元素之和:
int sum = 0;
for (i = 0; i < 5; i++) {
sum += scores[i];
}
printf("总分: %d\n", sum);
找出最大值:
int max = scores[0];
for (i = 1; i < 5; i++) {
if (scores[i] > max) {
max = scores[i];
}
}
printf("最高分: %d\n", max);
▶ 示例
计算一组成绩的平均分和不及格人数:
#include <stdio.h>
int main(void) {
double scores[] = {78.5, 92.0, 55.5, 88.0, 43.0, 67.5, 90.0};
int len = sizeof(scores) / sizeof(scores[0]);
double sum = 0;
int fail = 0;
int i;
for (i = 0; i < len; i++) {
sum += scores[i];
if (scores[i] < 60.0) {
fail++;
}
}
printf("平均分: %.1f\n", sum / len);
printf("不及格人数: %d\n", fail);
return 0;
}
平均分: 73.5
不及格人数: 2
这里用sizeof(scores) / sizeof(scores[0])动态计算数组元素个数,这样修改数组内容时不用同步修改循环条件。
5. 数组作为函数参数
数组传递给函数时,不会复制整个数组,而是只传递首元素地址。这意味着函数内无法通过sizeof获知数组长度,必须额外传入长度参数。
void print_array(int arr[], int len) {
int i;
for (i = 0; i < len; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
参数int arr[]等价于int *arr,这是"退化"为指针的体现。函数内修改数组元素会影响原始数组,因为操作的是同一块内存。
void double_values(int arr[], int len) {
int i;
for (i = 0; i < len; i++) {
arr[i] *= 2;
}
}
int main(void) {
int data[] = {1, 2, 3, 4, 5};
double_values(data, 5);
return 0;
}
调用后data的内容变为{2, 4, 6, 8, 10}。
6. 二维数组
二维数组可以理解为"数组的数组",常用于表示矩阵或表格数据。
int matrix[3][4];
定义了一个3行4列的二维数组。初始化可以按行分组:
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
也可以平铺写,效果相同:
int matrix[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
访问元素使用两个下标:
int val = matrix[1][2];
matrix[0][0] = 100;
遍历二维数组需要双重循环:
int i, j;
for (i = 0; i < 3; i++) {
for (j = 0; j < 4; j++) {
printf("%4d", matrix[i][j]);
}
printf("\n");
}
初始化时可以省略行数,但不能省略列数:
int m[][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8}
};
编译器根据初始化数据推断出2行。
▶ 示例
计算3名学生的4门课平均成绩:
#include <stdio.h>
void student_avg(double grades[][4], int rows) {
int i, j;
for (i = 0; i < rows; i++) {
double sum = 0;
for (j = 0; j < 4; j++) {
sum += grades[i][j];
}
printf("学生%d 平均分: %.1f\n", i + 1, sum / 4);
}
}
int main(void) {
double grades[3][4] = {
{85.0, 90.0, 78.0, 92.0},
{70.0, 65.0, 80.0, 75.0},
{95.0, 88.0, 92.0, 97.0}
};
student_avg(grades, 3);
return 0;
}
学生1 平均分: 86.2
学生2 平均分: 72.5
学生3 平均分: 93.0
❓ 常见问题
sizeof(arr)在函数内部为什么不能用来求元素个数?sizeof得到的是指针大小(4或8字节),而非数组总大小。📖 小节
- 数组是同类型元素的连续存储,下标从0开始
- 初始化时可省略长度,部分初始化时剩余元素为0
- C语言不做越界检查,程序员必须自行保证下标合法
- 数组传给函数时退化为指针,需额外传递长度参数
- 二维数组按行存储,作函数参数时必须指定列数
📝 作业
- 编写程序,输入10个整数到数组中,然后逆序输出所有元素。
- 编写函数,接收一个整型数组和长度,返回数组中最大值的下标。
- 定义一个4×4的整型矩阵并初始化,计算并输出两条对角线元素之和。