2-1 二维数组
⏱ 本节预计用时:30分钟
本节意义:一维数组是一条线,能存一排数据。但现实中更多数据是"表格"------成绩表有行有列,棋盘是3x3,班级花名册一行一个学生。二维数组让你用一张表管理所有行和列,循环套循环,几行代码把全班的成绩算完。
本节目标:掌握二维数组的初始化和嵌套遍历;理解二维数组在内存中仍然是连续存储的。
1 开场------一维不够用了
上一节你用 char board[9] 存了井字棋的9个格子,一维数组一条线就搞定了。
但现在你是班主任,要管理5个学生、每个学生3门课的成绩。一维数组怎么存?
你可以这样:
c
int scores[15] = {85, 90, 78, 92, 88, 95, 78, 82, 80, 80, 60, 72, 95, 91, 88};
15个数字排成一条线。但你得自己记住:前3个是学生0的,接下来3个是学生1的......一旦数据多了,这种"人脑分组"的方式极其容易出错。你想算"学生2的第2门课成绩"------得心算 2×3 + 1 = 7,然后去 scores[7] 里找。
这就是二维数组要解决的问题。
2 二维数组------一张有行有列的表
把刚才那15个成绩,用二维数组来存:
c
int scores[5][3] = {
{85, 90, 78}, // 学生0:语文 数学 英语
{92, 88, 95}, // 学生1
{78, 82, 80}, // 学生2
{80, 60, 72}, // 学生3
{95, 91, 88} // 学生4
};
[5][3] 的意思是:5行、3列。第一维是行(学生),第二维是列(科目)。
现在你想查"学生2的第2门课成绩",直接写 scores[2][1],一目了然------不需要心算。
把二维数组想象成教学楼的储物柜墙 :5层楼(行),每层3个柜子(列)。scores[2][1] 就是第3层第2个柜子。
c
列0 列1 列2
+------+------+------+
行0 | 85 | 90 | 78 | 学生0
+------+------+------+
行1 | 92 | 88 | 95 | 学生1
+------+------+------+
行2 | 78 | 82 | 80 | 学生2
+------+------+------+
行3 | 80 | 60 | 72 | 学生3
+------+------+------+
行4 | 95 | 91 | 88 | 学生4
+------+------+------+
scores[2][1] → 行索引2、列索引1 → 82分。
3 嵌套遍历------两层循环管行管列
一维数组用一层循环,二维数组自然用两层:
c
int scores[5][3] = { /* 刚才那堆数据 */ };
for (int i = 0; i < 5; i++) { // i 管第几个学生(行)
int sum = 0;
for (int j = 0; j < 3; j++) { // j 管第几门课(列)
sum += scores[i][j];
}
printf("学生%d的平均分:%.1f\n", i + 1, (float)sum / 3);
}
外层循环 i 每走一次(换一个学生),内层循环 j 把3门课全跑一遍------这就叫"嵌套循环"。
运行情况:

如果你想反过来遍历------先按科目、再按学生:
c
for (int j = 0; j < 3; j++) { // j 管科目(列)
int sum = 0;
for (int i = 0; i < 5; i++) { // i 管学生(行)
sum += scores[i][j];
}
printf("科目%d的全班平均分:%.1f\n", j + 1, (float)sum / 5);
}
运行情况:

我们认真去想这个过程,就会发现,数组的元素都被遍历了,只是遍历的顺序不同而已。
| 遍历方式 | 外层循环 | 内层循环 | 结果 |
|---|---|---|---|
| 按学生 | i(行) |
j(列) |
每个学生的总分/平均分 |
| 按科目 | j(列) |
i(行) |
每门课的全班平均分 |
为了更好的理解整个过程是什么样的,我们可以让ai做一个可交互式执行代码的网页:

回到文件管理器,双击打开。

效果如下:



让ai把循环的流程图也加进去,可以再弄漂亮点,比如我给他说要蓝色系,大气简约。

得到了这样的东西(可以发给朋友装b了)。



是不是还挺有意思的?大家有想法都可以给ai说,have a try,看他能做出来什么样的东西!
4 内存里的二维数组
你可能以为二维数组在内存里也是一张二维的表。不是的。
内存是一维的------一条长长的地址线。二维数组只不过是"把第二行接在第一行后面"存在内存里。这一节叫"连续存储",二维也不例外。
以 int scores[2][3] = {{1,2,3}, {4,5,6}}; 为例:
c
逻辑视图(你脑子里的表): 内存实际存储(一条线):
列0 列1 列2 地址: 0 4 8 12 16 20
+----+----+----+ +----+----+----+----+----+----+
| 1 | 2 | 3 | 行0 | 1 | 2 | 3 | 4 | 5 | 6 |
+----+----+----+ +----+----+----+----+----+----+
| 4 | 5 | 6 | 行0 行0的3个 行1的3个
+----+----+----+
scores[0][2](第1行第3列,值为3)和 scores[1][0](第2行第1列,值为4)在内存里是紧挨着的。
编译器计算 scores[i][j] 的地址:
c
实际地址 = 首地址 + (i × 列数 + j) × 每个元素的大小
scores[1][2] → 首地址 + (1 × 3 + 2) × 4字节 = 首地址 + 20字节
5 总结
-
二维数组是"数组的数组"------
int scores[5][3]是5行3列的表 ------ 第一维是行(学生),第二维是列(科目)。scores[2][1]就是第3个学生的第2门课。 -
遍历用两层循环:外层管行,内层管列 ------ 按学生遍历:
i在外、j在内。按科目遍历:j在外、i在内。scores[i][j]本身不用改,只换循环顺序。 -
二维数组在内存里仍是连续存储------一行接一行 ------
scores[i][j]的地址 = 首地址 + (i × 列数 + j) × 元素大小。所以声明时列数不能省略。
6 练习题
题目:4 行 2 列数组,输入数据,找出最大值,输出最大值、所在行、列下标。
c
#include <stdio.h>
int main() {
int arr[4][2];
int max, x=0, y=0;
for(int i=0;i<4;i++) {
for(int j=0;j<2;j++) {
scanf("%d", &arr[i][j]);
}
}
max = arr[0][0];
for(int i=0;i<4;i++) {
for(int j=0;j<2;j++) {
if(arr[i][j] > max) {
max = arr[i][j];
x = i; y = j;
}
}
}
printf("最大值=%d,行:%d,列:%d", max, x, y);
return 0;
}
运行情况:
