大家好,我是网域小星球。
前面我们学习了一维数组,能够批量存储一组同类型数据;但在实际编程和工程场景中,经常需要处理二维或多维结构的数据,比如班级成绩表(行=学生、列=科目)、矩阵运算、地图网格、图像像素等。
C语言通过二维数组实现这种"行列结构"的数据存储,写法规范、访问高效,是入门阶段必须掌握的重要知识点。本篇从二维数组的本质、定义、初始化、遍历,到经典矩阵实战案例全面展开,全程适配VS2022,代码可直接编译运行,零基础也能轻松吃透。
目录
[1. 什么是二维数组?](#1. 什么是二维数组?)
[2. 二维数组的核心特点](#2. 二维数组的核心特点)
[1. 定义格式(标准规范)](#1. 定义格式(标准规范))
[2. 初始化方式(3种常用,重点掌握)](#2. 初始化方式(3种常用,重点掌握))
[3. 初始化注意事项(新手必记)](#3. 初始化注意事项(新手必记))
[1. 元素访问方式(核心语法)](#1. 元素访问方式(核心语法))
[2. 二维数组的遍历(核心操作,必掌握)](#2. 二维数组的遍历(核心操作,必掌握))
[3. 遍历实战案例(VS2022可直接运行)](#3. 遍历实战案例(VS2022可直接运行))
一、本章学习目标
学完本篇你将彻底掌握:
-
二维数组的本质:"行+列"的表格结构,本质是"一维数组的数组"。
-
二维数组的定义、初始化、赋值的完整规范。
-
使用双重for循环遍历二维数组的方法(外层控行、内层控列)。
-
二维数组在内存中的存储方式(行优先存储)。
-
经典实战案例:矩阵打印、矩阵求和、矩阵转置、二维数组查找。
-
新手避坑指南:下标越界、维度混乱、遍历边界错误等问题排查。
二、二维数组的基本概念
1. 什么是二维数组?
二维数组的本质是**"一维数组的数组"**,可以形象理解为一张"表格"------由若干行、若干列组成,每一行都是一个独立的一维数组,所有行的列数相同。
示例:定义一个3行4列的二维整数数组
cpp
int arr[3][4]; // 3行4列,共3×4=12个元素,可存放12个整数
形象表格示意:
| 列0 | 列1 | 列2 | 列3 |
|---|---|---|---|
| arr[0][0] | arr[0][1] | arr[0][2] | arr[0][3] |
| arr[1][0] | arr[1][1] | arr[1][2] | arr[1][3] |
| arr[2][0] | arr[2][1] | arr[2][2] | arr[2][3] |
2. 二维数组的核心特点
-
所有元素数据类型统一:只能存放同一种类型的数据(如全部是int、全部是char)。
-
行数和列数固定:定义时必须指定列数,行数可省略(由初始化列表决定)。
-
内存连续存储:二维数组在内存中是"行优先"存储(先存第0行,再存第1行,依次类推),本质是一块连续的内存空间。
-
下标从0开始:行下标和列下标都从0开始,最大行下标=行数-1,最大列下标=列数-1。
三、二维数组的定义与初始化
1. 定义格式(标准规范)
cpp
数据类型 数组名[行数][列数];
说明:
-
数据类型:数组中所有元素的类型(int、char、double等)。
-
数组名:遵循变量命名规则(字母、数字、下划线组成,不数字开头,不使用关键字),建议见名知意(如成绩表用score、矩阵用mat)。
-
行数:二维数组的行数(可选,可省略,由初始化列表决定)。
-
列数:二维数组的列数(必填,不能省略,否则编译器报错)。
示例:
cpp
int score[5][3]; // 5行3列,存放5个学生、3门科目的成绩
char map[10][10]; // 10行10列,存放地图网格数据
double mat[4][4]; // 4行4列,存放浮点型矩阵
2. 初始化方式(3种常用,重点掌握)
二维数组的初始化,核心是"按行赋值",可分为完全初始化、部分初始化、省略行数初始化三种,未赋值的元素会自动补0(整数型)。
(1)完全初始化(最规范,推荐使用)
用大括号包裹,每一行的元素单独用大括号包裹,清晰直观,不易出错。
cpp
// 3行4列,完全初始化,每一行单独赋值
int arr[3][4] = {
{10, 20, 30, 40}, // 第0行
{50, 60, 70, 80}, // 第1行
{90, 0, 10, 20} // 第2行
};
(2)部分初始化(未赋值元素自动补0)
只给部分行、部分元素赋值,未赋值的元素(包括未赋值的行)会自动初始化为0,适合大部分元素为0的场景(如矩阵)。
cpp
// 3行4列,部分初始化
int arr[3][4] = {
{1, 2}, // 第0行:前2个元素为1、2,后2个补0
{}, // 第1行:所有元素补0
{9, 8, 7} // 第2行:前3个元素为9、8、7,最后1个补0
};
// 等价于:{{1,2,0,0}, {0,0,0,0}, {9,8,7,0}}
(3)省略行数初始化(推荐,灵活高效)
定义时可省略行数,编译器会根据初始化列表的"行数",自动计算数组的行数,但列数不能省略(核心易错点)。
cpp
// 省略行数,编译器自动识别为3行4列(有3个大括号,代表3行)
int arr[][4] = {
{1,2,3,4},
{5,6},
{7}
};
// 等价于:int arr[3][4] = {{1,2,3,4}, {5,6,0,0}, {7,0,0,0}}
3. 初始化注意事项(新手必记)
-
列数不能省略:二维数组定义时,无论是否初始化,列数都必须指定(如int arr[][4]合法,int arr[3][]非法)。
-
初始化列表的行数不能超过定义的行数:如int arr[3][4],初始化列表最多只能有3个大括号(3行)。
-
未赋值元素自动补0:仅针对全局二维数组和初始化时未赋值的元素,局部二维数组未初始化时,元素为随机垃圾值。
-
不能整体赋值:二维数组不能直接用"arr2 = arr1"赋值,必须通过循环逐个元素赋值。
四、二维数组的访问与遍历
1. 元素访问方式(核心语法)
二维数组的元素通过"行下标+列下标"访问,语法格式:
cpp
数组名[行下标][列下标];
注意:行下标和列下标都从0开始,最大行下标=行数-1,最大列下标=列数-1,超出范围会导致下标越界(程序崩溃或乱码)。
示例:
cpp
int arr[2][2] = {{1,2}, {3,4}};
printf("%d\n", arr[0][0]); // 访问第0行第0列,输出1
printf("%d\n", arr[1][0]); // 访问第1行第0列,输出3
arr[1][1] = 10; // 修改第1行第1列的元素,变为10
printf("%d\n", arr[1][1]); // 输出10
2. 二维数组的遍历(核心操作,必掌握)
二维数组的遍历,必须使用双重for循环:外层循环控制"行",内层循环控制"列",逐个访问每一个元素。
遍历模板(通用,直接套用):
cpp
// 假设二维数组为int arr[行数][列数];
for (int i = 0; i < 行数; i++) // 外层循环:遍历每一行(i为行下标)
{
for (int j = 0; j < 列数; j++) // 内层循环:遍历当前行的每一列(j为列下标)
{
// 访问当前元素arr[i][j],可进行打印、赋值等操作
printf("%d ", arr[i][j]);
}
printf("\n"); // 每遍历完一行,换行(避免所有元素打印在一行)
}
3. 遍历实战案例(VS2022可直接运行)
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
// 定义并初始化一个3行4列的二维数组
int arr[3][4] = {
{1,2,3,4},
{5,6,7,8},
{9,0,1,2}
};
// 双重for循环遍历二维数组
printf("二维数组遍历结果:\n");
for (int i = 0; i < 3; i++) // 外层控行(3行)
{
for (int j = 0; j < 4; j++) // 内层控列(4列)
{
printf("%d ", arr[i][j]); // 打印当前元素,加空格分隔
}
printf("\n"); // 换行
}
return 0;
}
运行结果:
cpp
二维数组遍历结果:
1 2 3 4
5 6 7 8
9 0 1 2
五、二维数组经典实战案例(全部适配VS2022)
二维数组最常用的场景是"矩阵操作",以下案例覆盖作业、笔试高频考点,代码可直接复制运行,重点掌握思路。
案例1:打印3×3矩阵(基础入门)
需求:定义一个3×3的整型矩阵,初始化后,按表格形式打印输出。
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
// 定义并初始化3×3矩阵
int mat[3][3] = {
{1,2,3},
{4,5,6},
{7,8,9}
};
printf("3×3矩阵:\n");
// 遍历打印
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
printf("%d\t", mat[i][j]); // \t 制表符,让输出更整齐
}
printf("\n");
}
return 0;
}
案例2:求二维数组(矩阵)所有元素的总和
思路:遍历所有元素,用一个变量累加求和。
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int arr[3][4] = {{1,2,3,4}, {5,6,7,8}, {9,0,1,2}};
int sum = 0; // 用于存放总和
// 遍历所有元素,累加求和
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
sum += arr[i][j];
}
}
printf("二维数组所有元素的总和为:%d\n", sum);
return 0;
}
运行结果:48(1+2+3+4+5+6+7+8+9+0+1+2=48)
案例3:查找矩阵中最大值及其位置
思路:假设第一个元素为最大值,遍历所有元素,逐个比较,更新最大值及其行、列下标。
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int mat[3][4] = {{10,20,30,40}, {50,60,70,80}, {90,0,10,20}};
int max = mat[0][0]; // 假设第一个元素为最大值
int row = 0, col = 0; // 存放最大值的行、列下标
// 遍历所有元素,查找最大值
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
if (mat[i][j] > max)
{
max = mat[i][j]; // 更新最大值
row = i; // 更新行下标
col = j; // 更新列下标
}
}
}
printf("矩阵中的最大值为:%d\n", max);
printf("最大值所在位置:第%d行,第%d列(下标从0开始)\n", row, col);
return 0;
}
运行结果:
cpp
矩阵中的最大值为:90
最大值所在位置:第2行,第0列(下标从0开始)
案例4:矩阵转置(高频考点)
需求:将3×3矩阵转置(行变列、列变行),原矩阵的第i行第j列元素,转置后变为第j行第i列元素。
原矩阵: 转置后矩阵:
cpp
1 2 3 1 4 7
4 5 6 → 2 5 8
7 8 9 3 6 9
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
// 原矩阵(3×3)
int src[3][3] = {{1,2,3}, {4,5,6}, {7,8,9}};
// 目标矩阵(用于存放转置后的结果)
int dest[3][3];
// 矩阵转置:src[i][j] → dest[j][i]
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
dest[j][i] = src[i][j];
}
}
// 打印原矩阵
printf("原矩阵:\n");
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
printf("%d\t", src[i][j]);
}
printf("\n");
}
// 打印转置后矩阵
printf("\n转置后矩阵:\n");
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
printf("%d\t", dest[i][j]);
}
printf("\n");
}
return 0;
}
案例5:打印二维字符数组图形(趣味实战)
需求:用二维字符数组定义一个5×5的爱心图形,打印输出。
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
// 定义5×5二维字符数组,存放爱心图形
char heart[5][5] = {
{' ', '*', '*', ' ', ' '},
{'*', '*', '*', '*', ' '},
{'*', '*', '*', '*', '*'},
{' ', '*', '*', '*', ' '},
{' ', ' ', '*', ' ', ' '}
};
// 遍历打印爱心
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 5; j++)
{
printf("%c", heart[i][j]);
}
printf("\n");
}
return 0;
}
运行结果(爱心图形):
cpp
**
****
*****
***
*
六、二维数组常见错误(避坑指南,新手必看)
-
下标越界:访问的行下标≥行数,或列下标≥列数(如int arr[3][4],访问arr[3][4]),会导致程序崩溃、乱码,或运行结果异常。
-
省略列数定义:定义二维数组时,省略列数(如int arr[3][]),编译器直接报错,列数必须指定。
-
遍历边界错误:外层循环条件写成i ≤ 行数(如3行写成i ≤ 3),导致下标越界,正确应为i < 行数。
-
混淆行和列:把行下标和列下标写反(如arr[j][i]写成arr[i][j]),导致访问错误元素,结果异常。
-
局部二维数组未初始化:局部二维数组未赋值时,元素为随机垃圾值,直接使用会导致结果异常,需手动初始化。
-
整体赋值错误:用"arr2 = arr1"给二维数组赋值,编译器报错,必须通过双重循环逐个元素赋值。
七、本章核心总结
-
二维数组的本质是"一维数组的数组",呈"行+列"的表格结构,用于存储二维数据。
-
定义格式:数据类型 数组名[行数][列数],列数不能省略,行数可省略(由初始化列表决定)。
-
初始化有3种方式:完全初始化、部分初始化、省略行数初始化,未赋值元素自动补0。
-
访问方式:数组名[行下标][列下标],下标从0开始,避免越界。
-
遍历必须用双重for循环:外层控行、内层控列,是二维数组操作的核心。
-
经典应用:矩阵打印、求和、查找、转置,是作业和笔试的高频考点。
下期预告
下一篇我们将进入C语言最难但最重要的核心知识点------指针,从指针的本质、定义、解引用,到指针与变量的关系,逐步拆解,避开新手误区,搭配实战案例,全程VS2022精讲,为后续指针进阶打下基础。
✅ 本篇配套练习:打印5×5二维数组随机矩阵、求二维数组每列的和、完成4×4矩阵转置