C语言小课堂:二维数组

  1. 什么是 二维数组

在C语言中,二维数组可以被理解为数组的数组,或者说是一个具有行和列结构的表格。它是一种用于存储同类型数据的集合,这些数据通过行索引和列索引来访问。

一维数组:像一条线,数据按顺序排列,通过一个下标(索引)访问,例如 arr[0] , arr[1]

二维数组:像一个平面(表格),数据按行和列排列,通过两个下标(索引)访问,例如 arr[行索引][列索引]

声明语法:

数据类型 数组名[行数][列数];

数据类型:数组中所有元素的数据类型(例如 int , char , double 等)。

数组名 :你给数组起的名字。

行数 :数组的行数,必须是正整数常量。

列数 :数组的列数,必须是正整数常量。

内存布局: 虽然我们把它想象成一个表格,但计算机内存是线性的。C语言中的二维数组在内存中是**按行优先(row-major)**存储的。这意味着第一行的所有元素存储在内存中是连续的,接着是第二行的所有元素,依此类推。

例如,一个 int arr[2][3]; 的二维数组,其内存布局大致如下:

arr[0][0], arr[0][1], arr[0][2], arr[1][0], arr[1][1], arr[1][2]

  1. 何时用 二维数组

当你需要处理的数据具有表格结构或矩阵特性时,二维数组是你的不二之选。它能让你的代码更直观、更符合逻辑地表示这些数据。

典型应用场景:

矩阵运算:在线性代数中,矩阵是二维的,二维数组天生适合表示和操作矩阵(加法、乘法、转置等)。

图像处理:简单的灰度图像可以用一个二维数组表示,每个元素代表一个像素的灰度值。

游戏地图:棋盘、迷宫、扫雷等游戏的地图可以用二维数组来表示,每个元素代表地图上的一个格子(是墙、是路、是地雷等)。

学生成绩表:如果需要存储多个学生的多个科目的成绩,可以想象成一个表格:行代表学生,列代表科目。

座位安排:电影院、教室的座位布局。

为什么要用?因为二维数组能够模拟现实世界中常见的二维结构,使得数据的存储、访问和操作变得更加逻辑化和高效。如果没有二维数组,你需要用复杂的一维数组索引计算来模拟,这将大大增加代码的复杂度和出错率。

与关联知识点的联系与区别:

与一维数组:二维数组是更高维度的一维数组的扩展。一维数组是线性结构,二维数组是平面结构。理解了一维数组的索引和内存连续性,就能更好地理解二维数组。

与结构体数组:如果表格的每一行(或每一列)的数据类型不同,或者每一行代表一个“实体”(比如学生信息:姓名、学号、成绩),那么结构体数组可能是更好的选择。二维数组要求所有元素都是同类型。

与指针:二维数组名在很多情况下可以看作是指向其第一个元素(即第一行)的指针。深入理解指针有助于理解二维数组的底层机制和函数传参。

  1. 怎么用 二维数组

3.1 声明和初始化

声明:

int matrix[3][4]; // 声明一个3行4列的整数二维数组
char board[8][8]; // 声明一个8行8列的字符二维数组,可以用于棋盘

初始化: 你可以在声明时进行初始化。

完全初始化:按行顺序提供所有元素。

int matrix[2][3] = {
{1, 2, 3}, // 第0行
{4, 5, 6} // 第1行
};

等价于:

int matrix[2][3] = {1, 2, 3, 4, 5, 6}; // 内存按行优先排列

部分初始化:未初始化的元素会被自动设置为0(如果是全局或静态数组)或垃圾值(如果是局部自动数组)。

int matrix[2][3] = {
{1, 2}, // 第0行:matrix[0][2]为0
{4} // 第1行:matrix[1][1]和matrix[1][2]为0
};

省略行数:如果初始化时提供了所有元素,并且指定了列数,编译器可以自动推断行数。

int matrix[][3] = { // 编译器会根据元素数量和列数3推断出行数是2
{1, 2, 3},
{4, 5, 6}
};

注意:列数不能省略,因为编译器需要知道每行有多少元素才能正确计算内存地址。

3.2 访问元素

使用行索引和列索引来访问特定元素。索引从0开始。

int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};

// 访问第一行第一列的元素(值为1)
int element_0_0 = matrix[0][0];

// 访问第二行第三列的元素(值为6)
int element_1_2 = matrix[1][2];

// 修改元素
matrix[0][1] = 10; // 将第一行第二列的元素(原为2)修改为10

3.3 遍历数组

通常使用嵌套的 for 循环来遍历二维数组的所有元素。外层循环控制行,内层循环控制列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>

int main() {
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};

printf("遍历二维数组:\n");
for (int i = 0; i < 3; i++) { // 外层循环控制行索引 i (0到2)
for (int j = 0; j < 4; j++) { // 内层循环控制列索引 j (0到3)
printf("%4d", matrix[i][j]); // 打印当前元素,%4d保证对齐
}
printf("\n"); // 每打印完一行就换行
}

return 0;
}

输出:

遍历二维数组:
1 2 3 4
5 6 7 8
9 10 11 12

通过以上讲解,你应该对C语言二维数组有了全面的认识。它是学习更复杂数据结构和算法的基础,掌握它将为你的编程之路打下坚实的基础!

💻 配套习题

第一题 (选择题)

以下关于C语言二维数组的说法,哪项是错误的?

A. 二维数组可以看作是数组的数组。

B. 声明二维数组时,可以省略行数,但不能省略列数。

C. 二维数组在内存中是按列优先(column-major)存储的。

D. 二维数组的元素可以通过 arr[行索引][列索引] 的方式访问。

第二题 (填空题)

声明一个名为 grades 的二维数组,用于存储5个学生(行)的3门课程(列)的整数成绩,且所有学生的所有成绩都初始化为0。请写出其声明和初始化代码:int grades[][] = {______};

第三题 (代码输出题)

阅读下面的C代码,并写出其输出结果:

#include <stdio.h>
int main() {
int arr[2][2] = {{10, 20}, {30, 40}};
printf("%d %d\n", arr[0][1], arr[1][0]);
return 0;
}

第四题 (概念辨析题)

判断题:一个 int matrix[2][3]; 的二维数组,其元素 matrix[1][0] 在内存中紧邻着 matrix[0][2]

A. 正确

B. 错误

第五题 (编程题)

编写一个C程序:

声明一个 3x3 的整型二维数组 magic_square

通过嵌套循环,将数组的每个元素初始化为 (行索引 + 1) * (列索引 + 1)

遍历并打印这个二维数组,每行元素打印在一行,并用空格分隔。

例如: 当 i=0, j=0 时,magic_square[0][0](0+1)(0+1) = 1i=0, j=1 时,magic_square[0][1](0+1)(1+1) = 2 … 当 i=2, j=2 时,magic_square[2][2](2+1)*(2+1) = 9

预期输出格式:

1 2 3
2 4 6
3 6 9

答案: 见下方代码及讲解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>

int main() {
int magic_square[3][3];
int rows = 3;
int cols = 3;

// 初始化二维数组
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
magic_square[i][j] = (i + 1) * (j + 1);
}
}

// 打印二维数组
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%d ", magic_square[i][j]);
}
printf("\n"); // 每打印完一行就换行
}

return 0;
}

💡 习题讲解

第一题 (选择题)

答案: C

讲解:

A. 二维数组可以看作是数组的数组。 这是正确的。在C语言中,int arr[3][4] 可以被理解为包含3个元素的一维数组,每个元素又是一个包含4个 int 类型元素的一维数组。

B. 声明二维数组时,可以省略行数,但不能省略列数。 这是正确的。例如 int matrix[][3] = {{1,2,3},{4,5,6}};。编译器需要列数来确定每行的大小,从而计算内存地址。

C. 二维数组在内存中是按列优先(column-major)存储的。这是错误的! C语言中的二维数组是**按行优先(row-major)**存储的。这意味着同一行的元素在内存中是连续存放的。例如 arr[0][0], arr[0][1], arr[0][2], arr[1][0], ...。Fortran等语言是按列优先存储的。

D. 二维数组的元素可以通过 arr[行索引][列索引] 的方式访问。 这是正确的,这是二维数组元素访问的标准语法。

第二题 (填空题)

答案: int grades[5][3] = {0};

讲解:

int grades[5][3]:根据题目要求,5个学生对应行数,3门课程对应列数。所以声明为 [5][3]

= {0}:这是C语言中初始化数组的一个技巧。当初始化列表只提供一个值(例如 0)时,如果数组的元素数量多于初始化列表中提供的值,那么所有未显式初始化的元素都会被自动设置为0。对于局部变量,这是一种非常方便的将整个数组清零的方法。

第三题 (代码输出题)

答案: 20 30

讲解:

int arr[2][2] = {{10, 20}, {30, 40}};:声明并初始化一个 2x2 的二维数组。

arr[0][0]10

arr[0][1]20

arr[1][0]30

arr[1][1]40

printf("%d %d\n", arr[0][1], arr[1][0]);

arr[0][1] 对应的值是 20

arr[1][0] 对应的值是 30

%d %d\n 会按顺序打印这两个整数,中间用空格分隔,末尾换行。 因此,输出结果是 20 30

第四题 (概念辨析题)

答案: A

讲解:A 选项正确 (即原命题正确)。 这是一个关于C语言二维数组内存布局的关键概念。C语言中的二维数组是**按行优先(row-major)**存储的。 对于 int matrix[2][3];

内存中先是 matrix[0][0] , matrix[0][1], matrix[0][2] (第一行的所有元素)。

紧接着是 matrix[1][0] , matrix[1][1], matrix[1][2] (第二行的所有元素)。 所以,matrix[0][2] 是第一行的最后一个元素,matrix[1][0] 是第二行的第一个元素。在内存中,它们是紧挨着存储的。

易混淆点:理解行优先存储是避免数组越界和进行指针操作时非常重要的基础。

第五题 (编程题)

答案: 见上方代码及讲解

讲解:

#include <stdio.h>:引入标准输入输出库,以便使用 printf 函数。

int magic_square[3][3];:声明一个 3x3 的整型二维数组。

int rows = 3; int cols = 3;:定义行数和列数常量,这样可以使代码更具可读性和可维护性,如果需要修改数组大小,只需修改这两个变量即可。

初始化循环:

for (int i = 0; i < rows; i++) { // 外层循环控制行 i
for (int j = 0; j < cols; j++) { // 内层循环控制列 j
magic_square[i][j] = (i + 1) * (j + 1); // 根据题目要求进行初始化
}
}

这里 (i + 1)(j + 1) 是因为索引 ij 从0开始,而题目要求从1开始计算乘积。

打印循环:

for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%d ", magic_square[i][j]); // 打印当前元素,后跟一个空格
}
printf("\n"); // 每打印完一行后换行,使得输出呈现矩阵形式
}

这个循环结构和初始化循环类似,确保所有元素都被正确访问和打印。printf("\n"); 是关键,它确保了每行数据在输出时都独占一行。

这个题目综合考察了二维数组的声明、初始化、通过索引访问以及使用嵌套循环进行遍历打印的能力。