C++ 二维数组

如果要保存像 “学生成绩表”、“棋盘网格” 这样带有行和列结构的表格数据,只靠一维数组就不太够用了,这时我们可以用到 “二维数组”。

前面学习的一维数组只有一个下标,而二维数组有两个下标:第 1 个下标表示 “行”,第 2 个下标表示 “列”。你可以把二维数组看成是由一维数组嵌套而成的,它的每一行本身就是一个一维数组。

C++ 二维数组的声明与初始化

在 C++ 中,声明二维数组时我们需要在数组名后面连续写两对中括号 [][]。

语法:

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

说明:

第 1 个中括号里的数字表示二维数组有几行,第 2 个中括号里的数字表示每一行有几列。比如 int arr[2][3]; 就声明了一个 2 行 3 列的整型二维数组,总共可以存放 2 × 3 = 6 个整数。

和一维数组的初始化方式类似,二维数组也支持在声明后面直接使用花括号 {} 赋初始值。下面以定义一个 2 行 3 列的二维数组为例,常见的有以下 4 种写法:

// 方式 1(推荐)
int arr1[2][3] = {
    {10, 20, 30},
    {40, 50, 60}
};

// 方式 2
int arr2[2][3] = {10, 20, 30, 40, 50, 60};

// 方式 3
int arr3[2][3] = {};

// 方式 4
int arr4[][3] = {{10, 20, 30}, {40, 50, 60}};

方式 1 使用两层花括号按行分组,第 1 组 {10, 20, 30} 对应第 1 行,第 2 组 {40, 50, 60} 对应第 2 行。像上面那样分行对齐书写,看起来就像一张直观的表格。

对于方式 2,编译器会按照从第 1 行到第 2 行的顺序,依次把花括号里的 6 个整数填进二维数组中。不过在实际开发中,我们更推荐使用方式 1,因为它按行隔开,一眼就能分清哪几个元素属于同一行。

方式 3 使用一对空花括号 {},将全部 6 个元素统一初始化为 0。至于方式 4,小伙伴们要特别注意一条规则:初始化二维数组时,只能省略第一维的 “行数”,不能省略第二维的 “列数”:

// 正确方式
int a[][3] = {{10, 20, 30}, {40, 50, 60}};

// 错误方式
int b[2][] = {{10, 20, 30}, {40, 50, 60}};

为什么不能省略列数呢?道理很简单:编译器需要先知道 “每一行固定放几个元素”,才能根据初始值的总个数算出能排几行。

比如花括号里一共给了 6 个数,指定每行放 3 列,编译器算一下 6 ÷ 3 = 2 就知道有 2 行。如果把列数省略掉,编译器不知道每一行应该分几个元素,就会直接报错。

C++ 二维数组的访问与修改

在 C++ 中,如果我们想要读取或修改二维数组中的某一个元素,需要同时指定它的 “行下标” 和 “列下标”。

语法:

数组名[行下标][列下标]

说明:

和一维数组一样,二维数组的行下标与列下标都从 0 开始计算。以 int arr[2][3] = {{10, 20, 30}, {40, 50, 60}} 为例,行下标范围是 0 ~ 1,列下标范围是 0 ~ 2,对应关系如下表所示。

二维数组行列下标
行 / 列 第 1 列(列下标 0) 第 2 列(列下标 1) 第 3 列(列下标 2)
第 1 行(行下标 0) arr[0][0] = 10 arr[0][1] = 20 arr[0][2] = 30
第 2 行(行下标 1) arr[1][0] = 40 arr[1][1] = 50 arr[1][2] = 60

示例 1:访问与修改元素

#include <iostream>

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

    std::cout << "第 1 行第 2 列:" << arr[0][1] << '\n';
    std::cout << "第 2 行第 3 列:" << arr[1][2] << '\n';

    // 修改第 2 行第 1 列的元素
    arr[1][0] = 100;
    std::cout << "修改后第 2 行第 1 列:" << arr[1][0];
    return 0;
}

运行结果如下。

第 1 行第 2 列:20
第 2 行第 3 列:60
修改后第 2 行第 1 列:100

分析:

arr[0][1] 先通过行下标 0 找到第 1 行,再通过列下标 1 找到该行的第 2 个元素,因此输出 20。同理,arr[1][2] 表示访问第 2 行第 3 列的元素,输出结果为 60。

接着执行 arr[1][0] = 100,就会将第 2 行第 1 列的元素从原来的 40 修改为 100。在使用下标操作二维数组时,行下标和列下标都不能超出合法范围,比如访问 arr[2][0] 或 arr[0][3] 都会导致下标越界。

C++ 二维数组的遍历

一维数组只需要一层循环就能遍历完,而二维数组既有行又有列,因此需要嵌套两层循环才能把所有元素访问一遍。常用的遍历方式有 2 种:一种是 “双层下标 for 循环”,另一种是 “双层范围 for 循环”。

1. 使用双层下标 for 循环

使用双层下标 for 循环时,我们用外层循环来控制 “行下标” 的变化,用内层循环来控制 “列下标” 的变化。

我们可以调用 std::size() 函数来分别获取二维数组的行数和列数:

  • std::size(arr):获取外层数组的元素个数,也就是二维数组的 “行数”。
  • std::size(arr[0]):arr[0] 代表第 1 行,它本身是一个一维数组,因此算出来的就是每一行的 “列数”。

示例 2:双层 for 循环遍历

#include <iostream>
#include <iterator>

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

    int rows = std::size(arr);       // 获取行数:2
    int cols = std::size(arr[0]);    // 获取列数:3

    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            std::cout << arr[i][j] << ' ';
        }
        std::cout << '\n';
    }
    return 0;
}

运行结果如下。

10 20 30
40 50 60

分析:

在这个例子中,外层循环变量 i 控制行下标,依次取 0、1。内层循环变量 j 控制列下标,依次取 0、1、2:

  • 当 i = 0 时:内层循环让 j 依次取 0、1、2,在同一行连续输出第 1 行的 arr[0][0]、arr[0][1]、arr[0][2]。内层循环结束后,执行 std::cout << '\n' 换到下一行。
  • 当 i = 1 时:内层循环再次从 j = 0 开始,依次输出第 2 行的 arr[1][0]、arr[1][1]、arr[1][2],随后换行。

这里大家要留意换行语句 std::cout << '\n' 的位置:它必须放在内层循环的外面、外层循环的里面,表示 “每打印完一整行才换一次行”。如果误把它写进内层循环里,每个数字都会独占一行,就看不出表格的行列结构了。

掌握了双层循环遍历之后,我们再来通过一个例子计算二维数组中所有元素的总和。

示例 3:计算所有元素之和

#include <iostream>

int main() {
    int arr[3][4] = {
        {2, 4, 6, 8},
        {10, 12, 14, 16},
        {18, 20, 22, 24}
    };
    int sum = 0;

    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            sum += arr[i][j];
        }
    }

    std::cout << "所有元素之和:" << sum;
    return 0;
}

运行结果如下。

所有元素之和:156

分析:

在这个例子中,arr 是一个 3 行 4 列的二维数组,共有 3 × 4 = 12 个元素。外层循环执行 3 轮,每一轮带动内层循环执行 4 次,最里面的 sum += arr[i][j] 一共执行 12 次,最终算出所有元素的总和 156。

2. 使用双层范围 for 循环

如果遍历时不需要用到行下标和列下标,我们也可以使用范围 for 循环来遍历二维数组。这里有一个关键细节:外层循环的变量类型后面必须加上 & 符号,写成 auto& row。

示例 4:双层范围 for 遍历

#include <iostream>

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

    for (auto& row : arr) {
        for (auto num : row) {
            std::cout << num << ' ';
        }
        std::cout << '\n';
    }
    return 0;
}

运行结果如下。

10 20 30
40 50 60

分析:

为什么外层循环一定要加上 & 写成 auto& row 呢?我们知道,二维数组的每一行本身就是一个一维数组。

如果不加 & 直接写成 auto row,变量 row 只会拿到这一行的起始内存地址,丢掉了 “这一行有几个元素” 的长度信息,导致内层循环不知道怎么遍历而报错。

加上 & 写成 auto& row 之后,相当于直接给原数组里的每一行起了一个别名,完整保留了整行数组的信息,内层循环就能继续把这一行里的数字逐个取出来了。另外,如果希望在遍历的同时直接修改二维数组里的元素,只需要把内层循环也加上 &,写成 for (auto& num : row) 即可。

二维数组的内存存储顺序

虽然我们在逻辑上把二维数组看成有 “行” 和 “列” 的表格,但在计算机内存中,所有元素其实是按照 “行优先” 顺序连续存放的:先依次放完第 1 行的元素,紧接着再放第 2 行的元素,中间没有任何间隔,如下图所示。

二维数组按行优先顺序在内存中连续存放

正因如此,整个二维数组占用的总字节数就等于 “单个元素字节数 × 总元素个数”。比如 int arr[2][3] 共有 2 × 3 = 6 个 int 元素,每个占 4 个字节,sizeof(arr) 算出的总字节数就是 24。

上一篇:

下一篇:

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号