多维数组
1. 什么是多维数组?
在 C++ 中,严格来说并没有原生的“多维数组”类型。我们所说的多维数组,实际上是“数组的数组” (Array of Arrays)。最常见的是二维数组,可以将其想象成一个表格或矩阵,包含行和列。三维数组可以看作是多个二维表格的集合,以此类推。
- 二维数组 (2D Array): 一个一维数组,其每个元素本身也是一个一维数组。
- 三维数组 (3D Array): 一个一维数组,其每个元素是一个二维数组。
- ...以此类推。
2. 声明多维数组
声明多维数组需要指定每个维度的大小(除了第一个维度在某些情况下可以省略)。
-
二维数组声明:
dataType arrayName[dimension1_size][dimension2_size];
其中dimension1_size通常代表“行数”,dimension2_size代表“列数”。int matrix[3][4]; // 声明一个 3 行 4 列的二维整数数组 (共 3 * 4 = 12 个整数) double coordinates[10][3]; // 声明一个包含 10 个点的三维坐标数组 (10 行, 3 列) char gameBoard[8][8]; // 声明一个 8x8 的字符数组,可用于棋盘游戏 -
三维及更高维数组声明:
dataType arrayName[dim1][dim2][dim3]...[dimN];float cube[5][5][5]; // 声明一个 5x5x5 的三维浮点数数组
3. 初始化多维数组
可以使用嵌套的花括号 {} 初始化列表来初始化多维数组。
-
二维数组初始化:
-
完整初始化: 外层花括号包含代表每一行的内层花括号。
int matrix[2][3] = { {1, 2, 3}, // 第一行 (matrix[0]) 的元素 {4, 5, 6} // 第二行 (matrix[1]) 的元素 }; -
部分初始化: 如果提供的初始值不足,剩余的元素会被自动零初始化。
int matrix[3][4] = { {1, 2}, // 初始化为 {1, 2, 0, 0} {3}, // 初始化为 {3, 0, 0, 0} // 第三行未指定,初始化为 {0, 0, 0, 0} }; // 结果: {{1, 2, 0, 0}, {3, 0, 0, 0}, {0, 0, 0, 0}} -
省略第一维度大小: 如果提供了完整的初始化列表,可以省略第一个维度的大小,编译器会自动推断。
int matrix[][3] = { // 编译器推断第一维大小为 2 {1, 2, 3}, {4, 5, 6} }; -
扁平化初始化 (不推荐,可读性差): 可以省略内层花括号,按行主序依次提供所有元素的值。
int matrix[2][3] = {1, 2, 3, 4, 5, 6}; // 等同于上面的嵌套初始化
-
完整初始化: 外层花括号包含代表每一行的内层花括号。
4. 内存布局:行主序 (Row-Major Order)
这是理解多维数组的关键。尽管我们将其想象为二维或三维结构,但在内存中,C++ (以及 C 和许多其他语言) 将多维数组存储为一维的连续数据块。存储顺序遵循行主序,意味着最后一个索引变化最快。
对于二维数组 int matrix[R][C];:
- 内存布局:
matrix[0][0], matrix[0][1], ..., matrix[0][C-1], matrix[1][0], matrix[1][1], ..., matrix[1][C-1], ..., matrix[R-1][0], ..., matrix[R-1][C-1] - 所有元素(
R * C个)连续存放。第一行的所有元素先存放,然后是第二行的所有元素,依此类推。
示例: int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
内存中的顺序是: 1, 2, 3, 4, 5, 6
5. 访问多维数组元素
使用多个方括号 [] 和对应的索引来访问元素。索引仍然是从 0 开始。
int matrix[3][4];
matrix[0][0] = 10; // 访问第一行第一列的元素
matrix[1][2] = 25; // 访问第二行第三列的元素
int value = matrix[1][2]; // 读取第二行第三列的值 (25)
// 访问越界同样会导致未定义行为!
// matrix[3][0] 是越界的 (行索引最大为 2)
// matrix[0][4] 是越界的 (列索引最大为 3)
编译器利用行主序布局和维度大小来计算元素在内存中的实际地址:
对于 matrix[r][c],其地址约为 base_address + (r * NumberOfColumns + c) * sizeof(elementType)。这就是为什么在函数参数中通常需要指定除第一维之外的所有维度大小——编译器需要这些信息来正确计算地址。
6. 将多维数组传递给函数
有几种常见的方法:
-
指定所有维度:
void processMatrix(int m[3][4]) { m[1][1] = 0; } // 缺点:函数只能处理固定大小 (3x4) 的数组 -
省略第一维度(常用):
void processMatrix(int m[][4], int rows) { // 必须指定除第一维外的所有维度大小 for (int i = 0; i < rows; ++i) { for (int j = 0; j < 4; ++j) { // 列数 4 是固定的 m[i][j] *= 2; } } } int myMatrix[5][4]; processMatrix(myMatrix, 5); // 可以处理行数不同,但列数必须是 4 的数组 -
使用指向数组的指针(与上一种等价):
void processMatrix(int (*m)[4], int rows) { // m 是一个指向包含 4 个 int 的数组的指针 // ... 函数体同上 ... } -
使用模板(更通用):
template <size_t ROWS, size_t COLS> void processMatrix(int (&m)[ROWS][COLS]) { // 传递数组的引用 m[ROWS / 2][COLS / 2] = 99; } int myMatrix[10][20]; processMatrix(myMatrix); // 可以处理任意大小的二维数组 -
使用
std::vector<std::vector<T>>(动态大小):
虽然这不是一个真正的、内存连续的多维数组(它是一个 vector,其元素是其他 vector),但它提供了动态调整大小的灵活性。#include <vector> void processDynamicMatrix(std::vector<std::vector<int>>& matrix) { if (!matrix.empty() && !matrix[0].empty()) { matrix[0][0] = -1; } }
7. 常见用途
- 矩阵运算: 线性代数、科学计算。
- 图像表示: 二维数组存储像素数据(颜色、灰度值)。
- 游戏棋盘: 如国际象棋、围棋、扫雷等。
- 表格数据: 存储行和列结构的数据。
- 三维模型或空间: 在图形学或物理模拟中使用三维数组。
总结: C++ 的多维数组本质上是“数组的数组”,在内存中按行主序连续存储。通过多个索引访问元素,初始化使用嵌套花括号。传递给函数时通常需要指定除第一维之外的大小。理解其内存布局对于高效使用和避免错误至关重要。
回顾一下:一维数组
记得吗?一维数组就像一排紧挨着的巧克力块:
[块1] [块2] [块3] [块4]
你只需要说“第几个”(比如第 2 块,编号从 0 开始就是 [1])就能找到它。
现在,二维数组 (2D Array):
想象一下,你拿到了一整板大巧克力!这块大巧克力不是只有一排,而是有好几排 (Rows),每一排又有好几块 (Columns)。它看起来像一个格子!
(列 Col 0) (列 Col 1) (列 Col 2)
排 Row 0: [ R0,C0 ] [ R0,C1 ] [ R0,C2 ]
排 Row 1: [ R1,C0 ] [ R1,C1 ] [ R1,C2 ]
排 Row 2: [ R2,C0 ] [ R2,C1 ] [ R2,C2 ]
排 Row 3: [ R3,C0 ] [ R3,C1 ] [ R3,C2 ]
这块大巧克力板就像电脑里的二维数组 (二维数组)。
-
怎么找到特定一块巧克力? 现在只说“第几块”就不够了。你需要说清楚是“第几排的第几块”。比如,“第 2 排(编号是 1)的那一排里的第 3 块(编号是 2)”。你需要用两个数字来定位!
-
巧克力板[排号][块号](Chocolate[row_number][column_number]) ->巧克力板[1][2]
-
或者,想想电影院的座位:
电影院里也不是只有一排座位,对吧?有很多排,每一排有很多个座位号。你要找到你的座位,票上会写着“几排几座”,比如“8 排 15 座”。这也像一个二维数组!
-
电影院[排号][座位号](Theater[row_number][seat_number])
更进一步:三维数组 (3D Array):
现在想象一下,你有好几板一模一样的大巧克力叠在一起!或者一个多层电影院,有好几个厅(或者楼层),每个厅里都有排和座。
要找到特定的一块巧克力或座位,你需要三个数字了:
- “第几板巧克力的、第几排的、第几块” (
巧克力[板号][排号][块号]) - “第几层楼的、第几排的、第几座” (
多层影院[楼层号][排号][座位号])
这就是三维数组 (三维数组)!
重点: 多维数组就像把我们的“小盒子”按照格子(二维)或者立方体(三维)的形状排列起来,你需要用多个数字(下标) 才能准确找到其中的一个“小盒子”。
画一维数组 (一排巧克力):
[ ] [ ] [ ] [ ](旁边写:一维数组,像一排)-
画二维数组 (巧克力板):
- 画一个矩形网格。
- 标注行号 (0, 1, 2...) 和列号 (0, 1, 2...)。
- 在格子里写上
[行,列],比如[0,0],[0,1],[1,0],[1,1]... - 旁边写上:“二维数组,像一个格子/表格!”
- 用箭头指向一个格子,例如
[1][2],并标注:“需要 2 个数字 找到它:第 1 排,第 2 列”。
列0 列1 列2 行0 [0,0][0,1][0,2] 行1 [1,0][1,1][1,2] <--- 指向 [1,2] 并说明 行2 [2,0][2,1][2,2] -
画三维数组 (叠起来的巧克力板 - 可选简化):
- 画几个稍微错开的二维网格,表示叠在一起。
- 标注板号 (0, 1...),以及每个板上的行号和列号。
- 旁边写上:“三维数组,像叠起来的格子!”
- 用箭头指向其中一个格子,例如
板[0] 的 [1][2],标注:“需要 3 个数字 找到它:第 0 板,第 1 排,第 2 列”。
看图理解: 图画能很清楚地显示出从一排变成一个平面格子,再变成一个立体结构。并且能看到找到一个格子需要多少个数字信息。
现在我们总结一下电脑是怎么理解这些“格子队伍”的:
-
多维数组 (
多维数组):就是电脑里一种更高级的“小盒子队伍”组织方式,不只是一条线,而是像一个平面网格(二维数组二维数组) 或者一个立体空间(三维数组三维数组)。 - 怎么用? 当我们需要存储像表格、棋盘、图片像素点(图片就是很多行很多列的颜色点组成的嘛!)这类有“行”和“列”结构的数据时,用二维数组就特别方便。
-
找盒子(访问元素): 要从多维数组里找到一个特定的“小盒子”,你需要提供多个“地址”数字(下标 Indices)。二维数组需要两个(行号,列号),三维需要三个,以此类推。
myGrid[row][column] - 电脑的小秘密(内存存储): 虽然我们把二维数组想象成一个格子,但在电脑的“储藏室”(内存)里,它还是会很聪明地把所有小盒子拉平成一条长长的线来存放!它会先把第一排的所有盒子放好,紧接着放第二排的所有盒子,再放第三排……(这叫做“行主序 Row-major order”)。
-
为什么要拉平? 因为电脑最擅长处理一条线的队伍!这样它还是能用它那个快速计算地址的“魔法”(
基地址 + 行号 * 每行的盒子数 + 列号),很快地找到你想要的那个盒子,即使它是在一个“格子”里!
记住: 多维数组帮助我们用电脑表示和处理像表格、棋盘、图片这样的“格子”信息。我们用多个下标数字来找到格子里的某个位置,而电脑内部则聪明地把它们变成一条线来快速存储和查找。