首页 > 编程笔记

C++二维数组的定义和使用(入门必读)

平时大家提到的数组,通常指的是一维数组,它只有一个下标,常用来存储线性的数据,比如 {1,2,3,4,5}。

而在实际的应用中,有的数据用线性结构不好表示,例如下面的矩阵:
1 0 0
0 1 0
0 0 1
这是一个 3 行 3 列的矩阵。当然可以用一个长度为 9 的数组来存储这些数据,但是却不能表现出数据的逻辑结构。因此 C++ 语言允许构造多维数组,多维数组元素有多个下标,以标识在数组中的位置。

二维数组是常见的多维数组,常用于矩阵运算、图像处理和游戏开发等多个领域。虽然可以定义三维、四维甚至更高维数的数组,但实际用得也少。

二维数组的定义

二维数组的定义与一维数组相似,也需要指定数组元素的类型。不同的是,二维数组需要两个大小参数,分别表示数组的行数和列数。

定义二维数组的语法格式如下:
类型 数组名[行数][列数];
其中,行数和列数必须是程序编译时可以计算出的值,要么是常量,要么是常量表达式。

例如,定义一个 3 行 4 列的整数二维数组:
int num[3][4];

二维数组的初始化

二维数组在定义的时候也可以直接初始化。

二维数组可以看做是将一维数组作为数组元素的一维数组,其初始化可以看做多个一维数组的初始化。

初始化二维数组的格式如下:
类型 数组名[行数][列数]=
{
    {值, 值, ......},
    {值, 值, ......},
    ......
}
里面的花括号中的值分别用来初始化二维数组的每一行,即第一个花括号中的值初始化第一行,第二个花括号中的值初始化第二行,不足的部分用 0 来补齐。

1) 完全初始化

手动指定二维数组中全部元素的值,例如:
int num[3][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
};
完全初始化二维数组时,可以省略每一行元素的大括号:
int num[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};

2) 部分初始化

如果没有为所有元素提供初始值,则剩余元素会被初始化为零。例如:
int num[3][4] = {
    {1, 2, 3},
    {5, 6}
};
num 数组中存储的元素分别是:
1 2 3 0
5 6 0 0
0 0 0 0

3) 行大小自动推断

可以让编译器自动推断二维数组的行数。例如:
int num[][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
}; // 编译器自动推断行数为 3

注意,只能省略二维数组的行数,列数不能省略。

二维数组的使用

使用二维数组主要涉及访问、修改和遍历数组中的元素。

1) 访问数组元素

同一维数组一样,使用下标访问二维数组中的每个元素,不过要使用两个下标,分别指明元素所在的行号和列号。

访问二维数组元素的格式如下:
数组名[行号][列号];
同一维数组的访问一样,这两个下标都需要是整型常量或者变量。例如:
#include <iostream>

int main() {
    int num[3][4] = {
     {1, 2, 3, 4},
     {5, 6, 7, 8},
     {9, 10, 11, 12}
    };
    int value = num[1][2]; // 访问第二行第三列的元素
    std::cout << value << std::endl;
    return 0;
}
输出结果为:

7

2) 修改数组元素

要想修改二维数组中某个元素的值,只能通过下标的方法先找到要操作的元素,然后再进行处理。

例如:
#include <iostream>

int main() {
    int num[3][4] = {
     {1, 2, 3, 4},
     {5, 6, 7, 8},
     {9, 10, 11, 12}
    };
   
    std::cout << "修改前:" << num[1][2] << std::endl;

    num[1][2] = 100; // 修改数组中第二行第三列的元素
    std::cout << "修改后:" << num[1][2] << std::endl;
    return 0;
}
输出结果为:

修改前:7
修改后:100

3) 遍历数组元素

遍历二维数组,通常借助嵌套的循环结构实现。

例如:
#include <iostream>

int main() {
    int num[3][4] = {
     {1, 2, 3, 4},
     {5, 6, 7, 8},
     {9, 10, 11, 12}
    };
    //嵌套的循环结构遍历 num 数组
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            std::cout << num[i][j] << " ";
        }
        std::cout << std::endl;
    }
    return 0;
}
输出结果为:

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

二维数组的底层存储

虽然可以用二维数组来表示一个矩阵,但系统并不允许程序按照这种方式存储数据。二维数组的行、列表现形式只是便于对二维数组的理解,事实上二维数组的存储和一维数组一样,都是线性的结构。

在内存空间中,二维数组是一行一行连续存储的,也就是说先存放 a[0] 行,再存放 a[1] 行,再存放 a[2] 行,以此类推。

下面的程序中,分别定义一个长度为 9 的一维数组和一个 3×3 的二维数组,然后输出其各元素的地址:
#include <iostream>

int main() {
    // 定义一个长度为 9 的一维数组
    int array1D[9]={1,2,3,4,5,6,7,8,9};
   
    // 定义一个 3×3 的二维数组
    int array2D[3][3]={1,2,3,4,5,6,7,8,9};
   
    // 输出一维数组的各个元素的地址
    std::cout << "一维数组的元素地址和值:\n";
    for(int i = 0; i < 9; i++) {
        std::cout << "array1D[" << i << "]: " << &array1D[i] <<" " << array1D[i] << std::endl;
    }
   
    // 输出二维数组的各个元素的地址
    std::cout << "\n二维数组的元素地址和值:\n";
    for(int i = 0; i < 3; i++) {
        for(int j = 0; j < 3; j++) {
            std::cout << "array2D[" << i << "][" << j << "]: " << &array2D[i][j] <<" " << array2D[i][j] << std::endl;
        }
    }
   
    return 0;
}
运行结果为:
array1D[0]: 012FFB90 1
array1D[1]: 012FFB94 2
array1D[2]: 012FFB98 3
array1D[3]: 012FFB9C 4
array1D[4]: 012FFBA0 5
array1D[5]: 012FFBA4 6
array1D[6]: 012FFBA8 7
array1D[7]: 012FFBAC 8
array1D[8]: 012FFBB0 9

二维数组的元素地址和值:
array2D[0][0]: 012FFB64 1
array2D[0][1]: 012FFB68 2
array2D[0][2]: 012FFB6C 3
array2D[1][0]: 012FFB70 4
array2D[1][1]: 012FFB74 5
array2D[1][2]: 012FFB78 6
array2D[2][0]: 012FFB7C 7
array2D[2][1]: 012FFB80 8
array2D[2][2]: 012FFB84 9
从结果中可以看到,在内存的存储方式上,一维数组和二维数组是相同的。对于程序里定义的二维数组来说,元素 array2D[0][2] 和元素 array2D[1][0] 是相邻的。

推荐阅读