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 定义与初始化

  1. 列数必须写:int a[][] 错误,int a[][4] 正确。
  2. 行列混淆:a[i][j] 中 i 是行,j 是列,遍历时外层行内层列。
  3. 部分初始化补零:int a[3][4] = {{1}, {2}, {3}} 中未初始化的元素自动为0。
  4. 全部清零:int a[3][4] = {0} 可将所有元素置为0。

7.2 函数参数

  1. 列数不可省略:void f(int a[][], int m) 错误,必须写列数。
  2. 数组退化为指针:函数内 sizeof(a) 得到指针大小,不是数组总大小。
  3. 必须额外传行数:void f(int a[][4]) 不知道有多少行,需要传 rows。

7.3 地址计算

  1. 二维数组按行连续存储:a[i][j] 的地址是 基地址 + (i * 列数 + j) * sizeof(类型)。
  2. 指针运算单位错误:arr + 1 跳过一整行(列数 × sizeof(类型) 字节)。
  3. *(arr + i) 与 *arr + i 混淆:前者是第 i 行的首地址,后者是首元素值加 i。

7.4 多维数组

  1. 维度越多内存越大:int a[100][100][100] 约 4MB,注意内存限制。
  2. 每一维长度必须是常量:四级不考变长数组,不能用变量定义数组长度。

7.5 常见编程陷阱

  1. 矩阵乘法维度不匹配:n×m 矩阵乘以 m×p 矩阵,结果是 n×p。
  2. 对角线条件错误:主对角线 i == j,副对角线 i + j == n - 1(n为阶数)。
  3. 子矩形枚举边界遗漏:枚举子矩形时,右下角坐标从左上角坐标开始,确保不遗漏。

八、速查表

需求 写法 说明
定义二维数组 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四级从“数据”迈向“空间”的核心知识模块。

  1. 二维数组是“一维数组的数组”,内存按行连续存储。
  2. 定义时列数必须写,行数可省略(由初始化推导)。
  3. 遍历用双重循环,外层控制行,内层控制列。
  4. 二维数组作函数参数时,列数必须在编译期可知。
  5. 数组名是首地址,作参数时退化为指针,必须额外传行数。
  6. 地址计算:a[i][j] = 基地址 + (i × 列数 + j) × sizeof(类型)。
  7. 矩阵转置:b[j][i] = a[i][j]。
  8. 主对角线 i == j,副对角线 i + j == n - 1(n为阶数)。
  9. 多维数组依此类推,每一维长度必须是编译期常量。
  10. 多练“矩阵转置”“行列统计”“地址计算”“函数参数声明”四类题。

复习建议:先把二维数组的定义、初始化、遍历、矩阵操作四个模板写熟,再重点突破“二维数组作函数参数”和“地址计算”两类选择题,最后练习“画布裁剪”“排兵布阵”等二维网格遍历编程题。考试中先确定数组维度,再设计循环结构,最后检查边界和输出格式。