5 二维数组与多维数组
正在加载视频播放器…
GESP C++ 四级:二维数组与多维数组
核心目标:深入掌握二维数组的定义、初始化、内存布局与遍历,理解二维数组作为函数参数时“列数不可省略”的规则,了解多维数组的基本概念与使用方式,能熟练解决矩阵操作、二维网格遍历等综合问题。
二维数组是“一维数组的数组”,内存按行连续存储;遍历用嵌套循环,外层行内层列;作函数参数时列数必须编译期可知;多维数组依此类推,维度越多,内存消耗越大。
一、考纲定位
根据CCF官方考纲,四级的知识目标明确要求:“掌握C++二维及多维数组的基本概念及使用”。考核目标进一步指出:“掌握C++指针类型、二维及多维数组的基本使用”。
考纲同时明确了一个重要限制:对C++一维、二维及多维数组增加“不包括变长数组”的限制。这意味着数组的每一维长度都必须是编译期常量,不能用变量定义。
四级数组模块的考查重点包括:
| 考点 | 重要程度 | 考查形式 |
|---|---|---|
| 二维数组定义与初始化 | ★★★★★ | 选择题、编程题 |
| 二维数组遍历与统计 | 编程题高频 | |
| 二维数组作函数参数 | ★★★★☆ | 选择题高频 |
| 二维数组内存布局与地址计算 | 选择题、程序阅读 | |
| 多维数组基本概念 | ★★★☆☆ | 选择题 |
| 矩阵操作(转置、乘法、对角线) | ★★★★☆ | 编程题 |
四级不考:动态内存分配(new/delete)、变长数组。
二、二维数组核心语法
2.1 定义与初始化
数据类型 数组名[行数][列数];
int matrix[3][4]; // 3行4列
double scores[5][3]; // 5行3列
char grid[10][10]; // 10行10列
初始化的五种方式:
// 方式一:分行初始化(推荐,清晰直观)
int a[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// 方式二:连续初始化(按行填充)
int b[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
// 方式三:部分初始化,其余补0
int c[3][4] = {{1}, {2}, {3}}; // 每行只初始化第一个元素
// 方式四:全部清零(最常用)
int d[3][4] = {0};
// 方式五:省略行数,列数必须写
int e[][4] = {{1,2,3,4}, {5,6,7,8}}; // 自动推导为2行
关键规则:定义二维数组时,列数必须写,行数可以省略(由初始化列表推导)。
2.2 内存布局
二维数组在内存中是按行连续存储的。matrix[3][4] 相当于 3 个长度为 4 的一维数组依次排列。
内存布局(按行连续):
matrix[0][0], matrix[0][1], matrix[0][2], matrix[0][3],
matrix[1][0], matrix[1][1], matrix[1][2], matrix[1][3],
matrix[2][0], matrix[2][1], matrix[2][2], matrix[2][3]
地址计算公式:a[i][j] 的地址 = 基地址 + (i * 列数 + j) * sizeof(类型)
真题示例(2025年9月) :给定
int arr[2][3] = {{1,2,3}, {4,5,6}};,则*(*(arr + 1) + 2)的值是( )。答案:6。arr + 1指向第二行,*(arr + 1)得到第二行首地址,+ 2偏移到arr[1][2],解引用得到 6。
2.3 遍历
标准双重循环:
int n = 3, m = 4;
for (int i = 0; i < n; i++) { // 外层控制行
for (int j = 0; j < m; j++) { // 内层控制列
cout << a[i][j] << " ";
}
cout << endl; // 每行结束换行
}
按列遍历:
for (int j = 0; j < m; j++) {
for (int i = 0; i < n; i++) {
cout << a[i][j] << " ";
}
cout << endl;
}
2.4 定义规则辨析(高频选择题)
真题示例(四级样题第2题) :以下哪个选项能正确定义一个二维数组( )?
A.int a[][];B.char b[][4];C.double c[3][];D.bool d[3][4];
答案:D
解析:A 列数缺失;B 行数可省略但列数必须写,b[][4]是合法的,但题目问的是“定义”,初始化时才能省略行数;C 列数缺失。
三、二维数组作函数参数
3.1 核心规则:列数不可省略
二维数组作为函数参数时,列数必须在编译期可知,不能省略。因为编译器需要知道每行有多少个元素,才能正确计算 a[i][j] 的地址。
// 方式一:省略首维长度(最常见)
void printMatrix(int a[][4], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 4; j++) {
cout << a[i][j] << " ";
}
cout << endl;
}
}
// 方式二:固定两维
void printMatrix(int a[3][4]) { ... }
// 方式三:指针+手算下标
void printMatrix(int *a, int rows, int cols) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
cout << a[i * cols + j] << " ";
}
cout << endl;
}
}
3.2 真题示例
四级样题第11题:以下哪个函数声明在调用时可以传递二维数组的名字作为参数( )?
A.void BubbleSort(int a[3][4]);B.void BubbleSort(int a[1[]]);
C.void BubbleSort(int * a[]);D.void BubbleSort(int ** a);
答案:A
解析:二维数组作参数时,列数必须指定,A 正确。B 语法错误,C 和 D 都是指针数组或二级指针,不是二维数组的正确参数形式。
3.3 三种传参方式对比
| 方式 | 声明 | 特点 |
|---|---|---|
| 省略首维 | void f(int a[][4], int rows) |
最常用,列数固定 |
| 固定两维 | void f(int a[3][4]) |
行列都固定的小矩阵 |
| 指针+手算 | void f(int *a, int r, int c) |
灵活,但需手动计算下标 |
四、常见矩阵操作
4.1 矩阵转置
将矩阵的行列互换:b[j][i] = a[i][j]。
int a[3][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};
int b[4][3];
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
b[j][i] = a[i][j];
}
}
4.2 矩阵加法
int c[3][4];
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
c[i][j] = a[i][j] + b[i][j];
}
}
4.3 矩阵乘法
// a: n×m, b: m×p, c: n×p
int c[3][4] = {0};
for (int i = 0; i < n; i++) {
for (int j = 0; j < p; j++) {
for (int k = 0; k < m; k++) {
c[i][j] += a[i][k] * b[k][j];
}
}
}
4.4 对角线元素
// 主对角线(左上到右下)
for (int i = 0; i < n; i++) {
cout << a[i][i] << " ";
}
// 副对角线(右上到左下)
for (int i = 0; i < n; i++) {
cout << a[i][n - 1 - i] << " ";
}
4.5 行列统计
// 每行之和
for (int i = 0; i < n; i++) {
int rowSum = 0;
for (int j = 0; j < m; j++) {
rowSum += a[i][j];
}
cout << rowSum << " ";
}
// 每列之和
for (int j = 0; j < m; j++) {
int colSum = 0;
for (int i = 0; i < n; i++) {
colSum += a[i][j];
}
cout << colSum << " ";
}
4.6 二维网格遍历
二维数组是“画布裁剪”“排兵布阵”“黑白方块”等四级经典编程题的底层结构。核心遍历模式包括:
遍历所有元素:
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
// 处理 a[i][j]
}
}
枚举所有子矩形(前两重确定左上角,后两重确定右下角):
for (int x1 = 0; x1 < n; x1++) {
for (int y1 = 0; y1 < m; y1++) {
for (int x2 = x1; x2 < n; x2++) {
for (int y2 = y1; y2 < m; y2++) {
// 处理以(x1,y1)为左上角、(x2,y2)为右下角的矩形
}
}
}
}
五、多维数组
5.1 三维数组的定义与初始化
三维数组是“二维数组的数组”,声明时遵循带括号的常量表达式列表的形式。
int cube[2][3][4]; // 2层,每层3行4列
// 初始化
int test[2][3][4] = {
{
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
},
{
{13, 14, 15, 16},
{17, 18, 19, 20},
{21, 22, 23, 24}
}
};
5.2 三维数组的遍历
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
for (int k = 0; k < 4; k++) {
cout << cube[i][j][k] << " ";
}
cout << endl;
}
cout << "---" << endl;
}
5.3 多维数组的内存与限制
- 内存消耗:维度越多,内存占用越大。
int a[100][100][100]占用 100×100×100×4 = 4,000,000 字节 ≈ 4MB。 - 初始化:嵌套花括号可以省略,但建议写清楚以增强可读性。
- 变长数组:四级明确不考变长数组,每一维长度必须是编译期常量。
- 应用场景:三维数组常用于表示空间中的物体形状,如立方体网格、多层迷宫等。
六、GESP四级数组相关真题示例
真题1:二维数组参数声明(四级样题第11题)
以下哪个函数声明在调用时可以传递二维数组的名字作为参数?
A.void BubbleSort(int a[3][4]);B.void BubbleSort(int a[1[]]);
C.void BubbleSort(int * a[]);D.void BubbleSort(int ** a);
答案:A
真题2:二维数组地址计算(2025年9月第3题)
给定
int arr[2][3] = {{1,2,3}, {4,5,6}};,则*(*(arr + 1) + 2)的值是( )。
A. 2 B. 5 C. 4 D. 6
答案:D
真题3:二维数组定义(四级样题第2题)
以下哪个选项能正确定义一个二维数组?
A.int a[][];B.char b[][4];C.double c[3][];D.bool d[3][4];
答案:D
真题4:数组描述判断(2025年9月第2题)
以下关于数组的描述中,( )是错误的。
A. 数组名是一个指针常量 B. 随机访问数组的元素方便快捷
C. 数组可以像指针一样进行自增操作 D.sizeof(arr)返回的是整个数组arr占用的字节数
答案:C
解析:数组名是常量指针,不能进行自增操作。
七、易错点汇总
7.1 定义与初始化
- 列数必须写:
int a[][]错误,int a[][4]正确。 - 行列混淆:
a[i][j]中i是行,j是列,遍历时外层行内层列。 - 部分初始化补零:
int a[3][4] = {{1}, {2}, {3}}中未初始化的元素自动为0。 - 全部清零:
int a[3][4] = {0}可将所有元素置为0。
7.2 函数参数
- 列数不可省略:
void f(int a[][], int m)错误,必须写列数。 - 数组退化为指针:函数内
sizeof(a)得到指针大小,不是数组总大小。 - 必须额外传行数:
void f(int a[][4])不知道有多少行,需要传rows。
7.3 地址计算
- 二维数组按行连续存储:
a[i][j]的地址是基地址 + (i * 列数 + j) * sizeof(类型)。 - 指针运算单位错误:
arr + 1跳过一整行(列数 × sizeof(类型)字节)。 *(arr + i)与*arr + i混淆:前者是第i行的首地址,后者是首元素值加i。
7.4 多维数组
- 维度越多内存越大:
int a[100][100][100]约 4MB,注意内存限制。 - 每一维长度必须是常量:四级不考变长数组,不能用变量定义数组长度。
7.5 常见编程陷阱
- 矩阵乘法维度不匹配:
n×m矩阵乘以m×p矩阵,结果是n×p。 - 对角线条件错误:主对角线
i == j,副对角线i + j == n - 1(n为阶数)。 - 子矩形枚举边界遗漏:枚举子矩形时,右下角坐标从左上角坐标开始,确保不遗漏。
八、速查表
| 需求 | 写法 | 说明 |
|---|---|---|
| 定义二维数组 | int a[3][4]; |
3行4列,列数必须写 |
| 全部清零 | int a[3][4] = {0}; |
所有元素为0 |
| 分行初始化 | {{1,2},{3,4}} |
推荐写法 |
| 二维遍历 | 外层行,内层列 | for(i) for(j) |
| 二维数组作参数 | void f(int a[][4], int rows) |
列数必须写 |
| 矩阵转置 | b[j][i] = a[i][j]; |
行列互换 |
| 主对角线 | a[i][i] |
左上到右下 |
| 副对角线 | a[i][n-1-i] |
右上到左下 |
| 行和 | 固定i,累加j |
每行求和 |
| 列和 | 固定j,累加i |
每列求和 |
| 矩阵乘法 | 三重循环 | c[i][j] += a[i][k]*b[k][j] |
| 三维数组 | int a[2][3][4]; |
2层3行4列 |
| 地址计算 | 基地址 + (i*列数+j)*sizeof |
按行连续 |
九、总结
二维数组与多维数组是GESP四级从“数据”迈向“空间”的核心知识模块。
- 二维数组是“一维数组的数组”,内存按行连续存储。
- 定义时列数必须写,行数可省略(由初始化推导)。
- 遍历用双重循环,外层控制行,内层控制列。
- 二维数组作函数参数时,列数必须在编译期可知。
- 数组名是首地址,作参数时退化为指针,必须额外传行数。
- 地址计算:
a[i][j]= 基地址 +(i × 列数 + j) × sizeof(类型)。 - 矩阵转置:
b[j][i] = a[i][j]。 - 主对角线
i == j,副对角线i + j == n - 1(n为阶数)。 - 多维数组依此类推,每一维长度必须是编译期常量。
- 多练“矩阵转置”“行列统计”“地址计算”“函数参数声明”四类题。
复习建议:先把二维数组的定义、初始化、遍历、矩阵操作四个模板写熟,再重点突破“二维数组作函数参数”和“地址计算”两类选择题,最后练习“画布裁剪”“排兵布阵”等二维网格遍历编程题。考试中先确定数组维度,再设计循环结构,最后检查边界和输出格式。