Skip to content

第3章 二维及多维数组 ​


🎯 本章学习目标 ​

通过本章的学习,你将能够:

  1. 理解二维数组概念:把二维数组看作"矩阵"或"表格",掌握行和列的含义
  2. 正确定义与初始化:掌握二维数组的定义语法和多种初始化方式
  3. 熟练双重遍历:用双层 for 循环按行/按列遍历二维数组
  4. 掌握行优先存储:理解二维数组在内存中按行优先排列,能计算元素地址
  5. 避免下标越界:清楚行列下标的合法范围,识别越界陷阱
  6. 操作矩阵运算:实现矩阵加法、转置等经典操作
  7. 计算行列统计:统计每行/每列的和、最大值、最小值等
  8. 实现杨辉三角:用二维数组递推生成杨辉三角
  9. 二维数组传参:掌握二维数组作为函数参数的规则(列数不能省略)
  10. 了解多维数组:知道三维及更高维数组的定义和遍历方法

🗺️ 本章知识图谱 ​

二维及多维数组
├── 基础概念
│   ├── 二维数组 = 矩阵/表格
│   ├── 行下标和列下标(从0开始)
│   └── arr[i][j] 的含义
├── 定义与初始化
│   ├── 定义语法:类型 数组名[行数][列数]
│   ├── 分行初始化(推荐)
│   ├── 整体初始化
│   ├── 部分初始化(其余补0)
│   └── 全局 vs 局部数组初始化
├── 内存布局
│   ├── 行优先存储(像读书)
│   ├── 地址计算公式
│   └── 数组越界问题
├── 遍历方式
│   ├── 按行遍历(外层i内层j)
│   ├── 按列遍历(外层j内层i)
│   └── 对角线遍历
├── 典型应用
│   ├── 矩阵加法/减法
│   ├── 矩阵转置
│   ├── 行列统计(和/最大/最小)
│   ├── 棋盘/迷宫表示
│   └── 杨辉三角(递推)
├── 函数参数
│   ├── 二维数组做形参
│   ├── 列数不能省略
│   ├── 行数可以省略
│   └── 原因:地址计算需要列数
└── 多维数组
    ├── 三维数组(层+行+列)
    ├── 三重循环遍历
    └── 更高维数组(了解)

一、为什么需要二维数组? ​

一维数组像一排储物柜,只有一个编号(下标)。二维数组则像一栋楼的储物柜——需要**楼层号(行)和房间号(列)**两个坐标才能定位一个格子。

在数学中,二维数组对应矩阵(Matrix):

        列0   列1   列2   列3
行0  [  1     2     3     4  ]
行1  [  5     6     7     8  ]
行2  [  9    10    11    12  ]

这是一个 3行4列(记作 3×4)的矩阵,共 12 个元素。arr[i][j] 表示第 i 行、第 j 列的元素。

⚠️ 易错点:C++ 中数组下标从 0 开始!一个 arr[3][4] 的数组,行下标范围是 0~2,列下标范围是 0~3。访问 arr[3][4] 属于越界,是考试高频陷阱。


二、二维数组的定义与初始化 ​

2.1 定义二维数组 ​

cpp
// 定义一个3行4列的整型二维数组(局部数组若不显式初始化,
// 其元素的值是 未定义(不确定) 的,通常表现为随机值。)
int arr[3][4];

// 定义时初始化为全0
int arr2[3][4] = {};

// 定义双精度浮点型二维数组:5个学生,每人3门课成绩
double score[5][3];

💡 小知识:全局数组默认初始化为 0,局部数组若不显式初始化则值不确定。编程题中建议写 int arr[N][M] = {}; 确保清零。

2.2 分行初始化(推荐 ✅) ​

用花括号将每一行括起来,最直观、最不易出错:

cpp
int arr[3][4] = {
    {1, 2, 3, 4},      // 第0行
    {5, 6, 7, 8},      // 第1行
    {9, 10, 11, 12}    // 第2行
};

如果某行初始化值不足,该行剩余元素自动补 0:

cpp
int arr[3][4] = {
    {1, 2},     // 第0行: {1, 2, 0, 0}
    {5},        // 第1行: {5, 0, 0, 0}
    {}          // 第2行: {0, 0, 0, 0}
};

2.3 顺序初始化 ​

将所有值写在一个花括号内,按行优先顺序依次填充:

cpp
int arr[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};

效果与分行初始化完全相同,但可读性较差,元素较多时不推荐。

2.4 省略行数 ​

定义二维数组时,行数可以省略,但列数必须指定:

cpp
// 正确:省略行数,编译器根据初始值推算为2行(每行4列,8个值 → 2行)
int arr[][4] = {1, 2, 3, 4, 5, 6, 7, 8};

// 正确:分行初始化时也可省略行数
int arr2[][3] = { {1, 2, 3}, {4, 5, 6} };  // 自动推算为2行3列

🔥 高频考点:以下写法都是错误的,编译不通过:

  • int arr[3][]; // ❌ 列数不能省略
  • int arr[][]; // ❌ 两维都不能省略

记忆口诀:"左边可以省,右边不能省"——编译器需要知道每行有多少个元素,才能正确计算内存地址。

2.5 初始化方式对比 ​

方式示例可读性适用场景
分行初始化{ {1,2},{3,4} }⭐⭐⭐⭐⭐日常首选,清晰直观
顺序初始化{1,2,3,4}⭐⭐元素较少时
省略行数int a[][4]={...}⭐⭐⭐行数不确定时
部分初始化{ {1},{3,4} }⭐⭐⭐只需赋部分值,其余自动补0

三、内存布局——按行连续存储 ​

3.1 行优先存储 ​

C++ 二维数组在内存中是按行连续存放的。int arr[3][4] 的 12 个元素在内存中一字排开:

地址偏移:  0    1    2    3    4    5    6    7    8    9   10   11
值:        1    2    3    4    5    6    7    8    9   10   11   12
         |----- 第0行 -----|----- 第1行 -----|----- 第2行 -----|

假设 int 占 4 字节,首元素地址为 base,则:

arr[i][j] 的地址 = base + (i × 列数 + j) × sizeof(元素类型)

cpp
// &arr[i][j] = &arr[0][0] + (i * 4 + j) * sizeof(int)

📌提醒:公式中的 sizeof(元素类型) 在GESP考试(Dev-C++环境)中,int 固定为 4 字节,不需要过度深究,重点掌握偏移量 (i × 列数 + j) 的计算即可。

🔥 高频考点:地址偏移量公式中乘的是列数而非行数!例如 arr[3][4] 中 arr[2][1] 的偏移量 = 2×4 + 1 = 9(个 int 的位置)。

3.2 验证连续性 ​

cpp
#include <iostream>
using namespace std;

int main() {
    int arr[2][3] = { {1, 2, 3}, {4, 5, 6} };

    // 输出每个元素的地址,观察相邻元素地址差
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            cout << "arr[" << i << "][" << j << "] 地址: "
                 << &arr[i][j] << "  值: " << arr[i][j] << endl;
        }
    }
    // 相邻元素地址相差4字节(一个int的大小),整体连续

    return 0;
}

⚠️ 注意:行与行之间没有空隙!arr[0][2] 的下一个内存位置紧挨着 arr[1][0]。


四、访问与遍历 ​

4.1 访问元素 ​

cpp
int arr[2][3] = { {10, 20, 30}, {40, 50, 60} };

cout << arr[0][0] << endl;  // 输出 10(第0行第0列)
cout << arr[1][2] << endl;  // 输出 60(第1行第2列)

arr[0][1] = 99;             // 修改第0行第1列的值为99

4.2 行优先遍历(外层行、内层列) ​

这是最常用的遍历方式,与内存布局一致,缓存友好、效率更高:

cpp
#include <iostream>
using namespace std;

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

    // 行优先遍历:外层控制行,内层控制列
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            cout << arr[i][j] << "\t";
        }
        cout << endl;  // 每行结束换行
    }

    return 0;
}

输出:

1       2       3       4
5       6       7       8
9       10      11      12

4.3 列优先遍历(外层列、内层行) ​

交换内外层循环即可按列遍历:

cpp
// 列优先遍历:外层控制列,内层控制行
for (int j = 0; j < 4; j++) {
    for (int i = 0; i < 3; i++) {
        cout << arr[i][j] << " ";
    }
    cout << endl;
}
// 输出:
// 1 5 9
// 2 6 10
// 3 7 11
// 4 8 12

⚠️ 易错点:循环条件中,行的上界是行数,列的上界是列数。arr[3][4] 的遍历范围是 i < 3,j < 4,写成 i <= 3 就会越界!

4.4 两种遍历方式对比 ​

对比项行优先遍历列优先遍历
外层循环行 i列 j
内层循环列 j行 i
访问顺序与内存存储顺序一致跨行跳跃访问
缓存效率⭐⭐⭐⭐⭐ 高⭐⭐ 较低
适用场景绝大多数情况需按列统计时(如列求和)

C++ 二维数组按行连续存储,行优先遍历时内存访问地址是递增的(arr[0][0] → arr[0][1] → arr[0][2] → ...),CPU 缓存可以预取相邻内存块,命中率极高。而列优先遍历时,访问 arr[0][0] 后跳到 arr[1][0],中间跨越了一整行的内存,缓存命中率大幅下降。在数据量较大时,两种遍历方式的性能差距可能达到数倍。

💡 编程建议:除非题目明确要求按列处理,否则一律使用行优先遍历。


五、二维数组作为函数参数 ​

5.1 基本规则 ​

二维数组作为函数参数时,必须指定第二维(列数),行数可以省略并通过参数传递:

cpp
// ✅ 正确:列数4明确指定
void printArray(int arr[][4], int rows) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < 4; j++) {
            cout << arr[i][j] << " ";
        }
        cout << endl;
    }
}

5.2 完整示例 ​

cpp
#include <iostream>
using namespace std;

// 计算矩阵所有元素之和
// 注意:此函数仅适用于列数为 4 的矩阵。
// 因为 C++ 原生二维数组做参数时,第二维(列数)必须写成编译期常量。
int matrixSum(int arr[][4], int rows) {
    int sum = 0;
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < 4; j++) {
            sum += arr[i][j];
        }
    }
    return sum;
}

// 方阵转置(原地修改)
void transpose(int arr[][3], int n) {
    for (int i = 0; i < n; i++) {
        for (int j = i + 1; j < n; j++) {
            // 交换 arr[i][j] 和 arr[j][i]
            int temp = arr[i][j];
            arr[i][j] = arr[j][i];
            arr[j][i] = temp;
        }
    }
}

int main() {
    int a[2][4] = { {1, 2, 3, 4}, {5, 6, 7, 8} };
    cout << "矩阵之和: " << matrixSum(a, 2) << endl;  // 36

    int b[3][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} };
   
    //此函数仅适用于3×3方阵。
    //对于一般 m×n 矩阵的转置,需要定义一个独立的 n×m 结果矩阵,而非原地修改。
    transpose(b, 3);
    // 转置后:
    // 1 4 7
    // 2 5 8
    // 3 6 9
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            cout << b[i][j] << " ";
        }
        cout << endl;
    }

    return 0;
}

🔥 高频考点:以下函数声明都是错误的:

  • void func(int arr[][], int n) // ❌ 必须指定列数
  • void func(int arr[3][]) // ❌ 列数不能省略

原因:编译器需要通过列数来计算每个元素的地址偏移量 (i * cols + j)。 二维数组作为函数参数时,编译器必须知道列数,因为它决定了内存寻址公式 arr[i][j] = 基地址 + (i × 列数 + j) × 元素大小。行数只是用来控制循环边界,不会参与内存寻址,所以可以省略。

5.3 指针与二维数组的关系 ​

二维数组的数组名 arr 本质上是一个指向第一行的指针(类型为 int (*)[4]),因此 arr[i] 等价于 *(arr+i),arr[i][j] 等价于 *(*(arr+i)+j)。这一性质解释了为什么函数参数必须指定列数——编译器需要知道每行的宽度才能正确计算地址偏移。


六、典型应用 ​

6.1 矩阵加法 ​

两个同型矩阵对应位置元素相加:

cpp
#include <iostream>
using namespace std;

int main() {
    int a[2][3] = { {1, 2, 3}, {4, 5, 6} };
    int b[2][3] = { {6, 5, 4}, {3, 2, 1} };
    int c[2][3];  // 存储结果

    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            c[i][j] = a[i][j] + b[i][j];
        }
    }

    // 输出结果矩阵
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            cout << c[i][j] << " ";
        }
        cout << endl;
    }
    // 输出:
    // 7 7 7
    // 7 7 7

    return 0;
}

6.2 矩阵转置 ​

将 m×n 矩阵的行和列互换,得到 n×m 矩阵:

cpp
#include <iostream>
using namespace std;

int main() {
    int a[2][3] = { {1, 2, 3}, {4, 5, 6} };
    int b[3][2];  // 转置后为3行2列

    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            b[j][i] = a[i][j];  // 行列互换赋值
        }
    }

    // 输出转置结果
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 2; j++) {
            cout << b[i][j] << " ";
        }
        cout << endl;
    }
    // 输出:
    // 1 4
    // 2 5
    // 3 6

    return 0;
}

⚠️ 易错点:转置时注意原矩阵和目标矩阵的维度交换!a[m][n] 转置到 b[n][m],赋值方向是 b[j][i] = a[i][j],不要写反。

6.3 统计每行/每列的和 ​

cpp
#include <iostream>
using namespace std;

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

    // 统计每行的和
    for (int i = 0; i < 3; i++) {
        int rowSum = 0;
        for (int j = 0; j < 4; j++) {
            rowSum += arr[i][j];
        }
        cout << "第" << i << "行的和: " << rowSum << endl;
    }
    // 输出:
    // 第0行的和: 10
    // 第1行的和: 26
    // 第2行的和: 42

    cout << "----------" << endl;

    // 统计每列的和
    for (int j = 0; j < 4; j++) {
        int colSum = 0;
        for (int i = 0; i < 3; i++) {
            colSum += arr[i][j];
        }
        cout << "第" << j << "列的和: " << colSum << endl;
    }
    // 输出:
    // 第0列的和: 15
    // 第1列的和: 18
    // 第2列的和: 21
    // 第3列的和: 24

    return 0;
}

💡 区分技巧:求"每行的和"——外层行、内层列累加;求"每列的和"——外层列、内层行累加。

6.4 棋盘/迷宫问题 ​

用二维数组表示棋盘或迷宫,0 和 1 分别表示不同状态(如 0=通路,1=墙壁),是经典应用场景:

cpp
#include <iostream>
using namespace std;

int main() {
    // 5x5的迷宫: 0=通路, 1=墙壁
    int maze[5][5] = {
        {0, 1, 0, 0, 0},
        {0, 1, 0, 1, 0},
        {0, 0, 0, 1, 0},
        {0, 1, 1, 1, 0},
        {0, 0, 0, 0, 0}
    };

    // 输出迷宫地图
    for (int i = 0; i < 5; i++) {
        for (int j = 0; j < 5; j++) {
            if (maze[i][j] == 1)
                cout << "█ ";  // 墙壁
            else
                cout << "· ";  // 通路
        }
        cout << endl;
    }

    return 0;
}

6.5 杨辉三角 ​

杨辉三角是 GESP 四级的经典编程题,第 i 行第 j 列的值 = 上一行同列 + 上一行前一列:

        1
       1 1
      1 2 1
     1 3 3 1
    1 4 6 4 1
cpp
#include <iostream>
using namespace std;

int main() {
    int n;
    cout << "请输入杨辉三角的行数: ";
    cin >> n;

    int yh[20][20] = {};  // 初始化为0

    for (int i = 0; i < n; i++) {
        yh[i][0] = 1;        // 每行第一个数为1
        yh[i][i] = 1;        // 每行最后一个数为1
        // 中间元素 = 上一行同列 + 上一行前一列
        for (int j = 1; j < i; j++) {
            yh[i][j] = yh[i - 1][j - 1] + yh[i - 1][j];
        }
    }

    // 输出杨辉三角
    for (int i = 0; i < n; i++) {
        for (int j = 0; j <= i; j++) {
            cout << yh[i][j] << " ";
        }
        cout << endl;
    }

    return 0;
}

⚠️ 递推公式:yh[i][j] = yh[i-1][j-1] + yh[i-1][j],边界条件是每行首尾为 1。

注意:当 n = 0 时程序不输出任何内容(符合数学定义);当 n = 1 时输出 1(当前代码已正确处理)。实际编程题中,建议先判断 n <= 0 的情况并给出提示。


七、三维及多维数组 ​

7.1 三维数组的定义 ​

三维数组可以理解为"二维数组的数组",或一栋楼的储物柜——层、行、列三个坐标:

cpp
// 定义一个2层3行4列的三维数组
int arr[2][3][4];

// 分行初始化
int arr[2][3][4] = {
    {                           // 第0层
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    },
    {                           // 第1层
        {13, 14, 15, 16},
        {17, 18, 19, 20},
        {21, 22, 23, 24}
    }
};

7.2 三维数组的遍历 ​

三重 for 循环,从最外层到最内层依次遍历:

cpp
#include <iostream>
using namespace std;

int main() {
    int arr[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} }
    };

    int sum = 0;
    for (int d = 0; d < 2; d++) {         // 层
        for (int i = 0; i < 3; i++) {      // 行
            for (int j = 0; j < 4; j++) {  // 列
                sum += arr[d][i][j];
            }
        }
    }
    cout << "所有元素之和 = " << sum << endl;  // 300

    return 0;
}

💡 规律总结:n 维数组需要 n 重循环遍历。三维数组在 GESP 四级中偶尔出现在选择题中考察遍历和初始化,掌握遍历方法即可。

7.3 更高维数组 ​

C++ 理论上支持任意维度的数组,但实际使用中超过三维就非常罕见了:

cpp
int a4[2][3][4][5];  // 四维数组——语法合法,但可读性和实用性很差

⚠️ 考试提示:四级考试以二维数组为主,三维数组多出现在选择题中。更高维数组仅做了解。


八、📝 实战练习 ​

练习题 1(程序阅读题) ​

题目: 阅读以下程序,输出结果是( )

cpp
#include <iostream>
using namespace std;

int main() {
    int a[3][3] = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    };
    int sum = 0;
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            if (i == j || i + j == 2)
                sum += a[i][j];
        }
    }
    cout << sum << endl;
    return 0;
}

A. 25
B. 30
C. 35
D. 45

🔑 点击查看答案与解析

答案: A

解析:

程序累加满足 i == j(主对角线)或 i + j == 2(副对角线)的元素:

位置i, j条件值
a[0][0](0,0)i==j ✓1
a[0][2](0,2)i+j==2 ✓3
a[1][1](1,1)两个条件都满足(只算一次)5
a[2][0](2,0)i+j==2 ✓7
a[2][2](2,2)i==j ✓9

⚠️ 注意 a[1][1] 同时满足两个条件,但因为是 ||(逻辑或),只累加一次!a[1][1] 同时满足 i==j 和 i+j==2,但 if 语句无论几个条件为真都只执行一次 sum += a[i][j],因此 5 只被累加一次。

总和 = 1 + 3 + 5 + 7 + 9 = 25,选 A。

📌 提醒:实战编程中,建议明确写成两个独立的 if 判断,或者先累加主对角线,再累加次对角线并单独处理中心点(避免重复累加)。把逻辑写清楚,远比追求代码简洁更重要——这是GESP考试评分的重要标准。


练习题 2(单选题) ​

题目: 已知 int a[4][6];,且 &a[0][0] = 3000(十进制),假设 int 占 4 字节,则 &a[3][2] 的值是多少?( ) A. 3072
B. 3080
C. 3088
D. 3096

🔑 点击查看答案与解析

答案: B

解析:

使用地址公式:

&a[i][j] = &a[0][0] + (i × 列数 + j) × sizeof(int)

本题列数 = 6,i = 3,j = 2:

偏移量 = 3 × 6 + 2 = 20(个int的位置)
&a[3][2] = 3000 + 20 × 4 = 3000 + 80 = 3080

选 B。

🔥 再次提醒:公式中乘的是列数(6),不是行数(4)!这是最容易出错的地方。如果误用行数:3×4+2=14,3000+56=3056,就会算错。


练习题 3(判断题) ​

题目: 有定义 int a[5][10];,则 a[4][9] 是最后一个元素,a[5][10] 是第一个越界元素。( )

🔑 点击查看答案与解析

答案: 正确 ✅

解析:

数组下标从 0 开始:

  • 行下标范围:0 ~ 4(共 5 行)
  • 列下标范围:0 ~ 9(共 10 列)
  • a[4][9] 是合法的最后一个元素
  • a[5][10] 行和列都越界了

💡 记忆技巧:n 个元素,下标范围是 0 ~ n-1。就像一排 5 个座位,编号 0 到 4,坐第 5 号就坐地上了。


练习题 4(单选题) ​

题目: 将 3 行 4 列的矩阵 a 转置到矩阵 b,以下赋值正确的是( ) A. b[i][j] = a[i][j]
B. b[j][i] = a[i][j]
C. b[i][j] = a[j][i]
D. b[j][i] = a[j][i]

🔑 点击查看答案与解析

答案: B

解析:

矩阵转置的核心是行列互换:原矩阵 a 的第 i 行第 j 列元素,放到目标矩阵 b 的第 j 行第 i 列。

  • a 是 3×4,b 应该是 4×3
  • a[i][j] → b[j][i]
  • 所以赋值语句是 b[j][i] = a[i][j]

选 B。

🍎 生活比喻:就像把一张照片横过来放,原来的第一行变成了第一列,原来的第一列变成了第一行。


练习题 5(填空题) ​

题目: 杨辉三角递推公式:yh[i][j] = yh[i-1][j-1] + yh[i-1][j],每行首尾元素为 1。

若要求输出前 5 行杨辉三角,请补全代码中第 ____ 行的正确写法。

cpp
int yh[10][10] = {};
for (int i = 0; i < 5; i++) {
    yh[i][0] = 1;
    _______________;   // 填空
    for (int j = 1; j < i; j++) {
        yh[i][j] = yh[i-1][j-1] + yh[i-1][j];
    }
}
🔑 点击查看答案与解析

答案: yh[i][i] = 1

解析:

杨辉三角每行的第一个元素(第0列)和最后一个元素(第i列)都是 1。

  • yh[i][0] = 1:设置每行第一个元素为 1
  • yh[i][i] = 1:设置每行最后一个元素为 1(第 i 行有 i+1 个元素,最后一个下标是 i)

中间元素 yh[i][j](1 ≤ j < i)由递推公式计算。

🔥 高频考点:杨辉三角的边界条件(首尾为1)和递推公式是编程题常考内容。


练习题 6(单选题) ​

题目: 有定义 int a[3][4] = {{1, 2}, {3}};,以下说法正确的是( ) A. a[0][2] 的值是 0
B. a[1][1] 的值是 3
C. a[2][0] 的值不确定
D. 编译错误,初始化列表元素不够

🔑 点击查看答案与解析

答案: A

解析:

当二维数组部分初始化时,未显式给出的元素自动初始化为 0。

逐行分析:

  • 第 0 行:{1, 2} → [1, 2, 0, 0](后两个补 0)
  • 第 1 行:{3} → [3, 0, 0, 0](后三个补 0)
  • 第 2 行:没有给出 → [0, 0, 0, 0](整行补 0)

所以:

  • A 正确 ✅:a[0][2] = 0
  • B 错误:a[1][1] = 0,不是 3
  • C 错误:a[2][0] = 0,不是不确定
  • D 错误:部分初始化是合法的,其余自动补 0

练习题 7(单选题) ​

题目: 以下二维数组作为函数参数的声明中,正确的是( ) A. void func(int a[][], int n, int m)
B. void func(int a[3][], int n)
C. void func(int a[][4], int n)
D. void func(int a[3][4]),调用时只能传 3 行 4 列的数组

🔑 点击查看答案与解析

答案: C

解析:

二维数组作为函数参数时:

  • 列数必须指定(编译器需要列数计算地址)
  • 行数可以省略(由调用者决定实际行数)

逐一分析:

  • A 错误:列数也省略了,编译器不知道每行有多少元素
  • B 错误:列数省略了,行数指定反而没意义
  • C 正确 ✅:列数指定为 4,行数省略,调用时可以传任意行(只要列数是 4)
  • D 错误:行数指定的是"至少能容纳多少行",不是"只能传这么多行"。实际上调用时传 5 行 4 列也能通过编译(虽然可能有问题)

💡 记忆口诀:列不能省,行可以省。就像寄快递,必须告诉快递员每层几个(列数),几层可以现场数。


练习题 8(编程题) ​

题目: 给定一个 3 行 4 列的整数矩阵,求:

  1. 所有元素中的最大值
  2. 最大值所在的行号和列号

输入:12 个整数
输出:最大值 行号 列号

🔑 点击查看答案与解析

参考答案:

cpp
#include <iostream>
using namespace std;

int main() {
    int a[3][4];
    // 输入矩阵
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            cin >> a[i][j];
        }
    }

    int maxVal = a[0][0];  // 假设第一个元素最大
    int maxRow = 0, maxCol = 0;

    // 遍历找最大值
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            if (a[i][j] > maxVal) {
                maxVal = a[i][j];
                maxRow = i;
                maxCol = j;
            }
        }
    }

    cout << maxVal << " " << maxRow << " " << maxCol << endl;
    return 0;
}

解析:

这是二维数组的经典题型——查找最大值及其位置。核心思路:

  1. 假设第一个元素是最大值,记录其值和位置
  2. 遍历所有元素,如果发现更大的,就更新最大值和位置
  3. 遍历结束后就得到了全局最大值

🍎 生活比喻:就像选班长,先让第一个人当临时班长,然后每个人都跟现任班长比一比,比班长厉害就替换掉。


九、📊 考点总结表 ​

考点重要程度出题形式易错点
二维数组定义与初始化⭐⭐⭐⭐选择/判断部分初始化时其余元素补0,不是随机值
列数不能省略(定义及传参)⭐⭐⭐⭐⭐选择(判断正误)行数可省略,列数必须显式指定
双重循环遍历⭐⭐⭐⭐⭐编程题/程序阅读行优先 vs 列优先搞混,内外层写反
内存布局与地址计算⭐⭐⭐⭐选择/计算地址公式乘列数不是行数,i×cols+j
矩阵加法/转置⭐⭐⭐⭐编程题转置时行列维度交换,b[j][i]=a[i][j]
杨辉三角⭐⭐⭐⭐编程题边界条件:每行首尾为1,中间递推
行列统计(和/最值)⭐⭐⭐⭐⭐编程题每行重置累加器,别忘初始化为0
数组越界⭐⭐⭐⭐⭐选择(陷阱题)下标从0开始,n个元素最大下标是n-1
三维数组遍历⭐⭐⭐选择/程序阅读三重循环顺序:外层到内层

⭐ 必背速记卡 ​

┌─────────────────────────────────────────────┐
│       二维及多维数组 · 必背速记卡           │
├─────────────────────────────────────────────┤
│  1. 二维数组 = 行 × 列,像表格/矩阵         │
│  2. 下标从0开始,arr[n][m] 最大下标 n-1, m-1│
│  3. 行优先存储:先放完一行,再放下一行       │
│  4. 地址公式:&a[i][j] = 基址 + (i×列数+j)×大小│
│  5. 二维数组做参数:列不能省,行可以省       │
│  6. 部分初始化,未给出的元素自动补0          │
│  7. 按行遍历:外层i内层j                    │
│  8. 按列遍历:外层j内层i                    │
│  9. 矩阵转置:b[j][i] = a[i][j]             │
│ 10. 杨辉三角:首尾为1,中间 = 左上+正上     │
│ 11. 每行求和:内层循环前重置sum=0           │
│ 12. 找最大值:假设第一个最大,遍历更新       │
│ 13. 三维数组:层+行+列,三重循环            │
│ 14. 越界是UB,一定要检查边界条件             │
│ 15. 全局数组默认0,局部数组要手动初始化      │
└─────────────────────────────────────────────┘

百炼成钢,融会贯通