📄 一句话概括
声明二维数组就是多写一对方括号:类型 数组名[行数][列数];。行在前、列在后,两个数字都必须是常量。它在内存里仍然是连续的一排,按行优先铺开——所以格数 = 行 × 列,字节数 = 行 × 列 × 每格字节。
📝 本节和前面几节的关系
7.2 讲了"二维数组是一张表"。这一节落地"表怎么订出来"。规则几乎照搬 7.1.1:类型 + 名字 + 方括号里的常量 + 分号。唯一的增量是多了一维,以及由此带来的"行优先"内存布局——这条马上会在 7.2.3 的初始化和 7.5.2 的指针遍历里用上。
一、声明的格式
类型 数组名[行数][列数];
int score[3][4]; // 3 行 4 列,共 12 格整数
double matrix[2][2]; // 2 行 2 列,共 4 格小数
char board[19][19]; // 19 行 19 列,围棋盘
两对方括号,缺一不可,顺序也不能反:
| 写法 | 含义 | 结果 |
|---|---|---|
int a[3][4]; |
3 行 4 列 | ✅ 12 格 |
int a[4][3]; |
4 行 3 列 | ✅ 也是 12 格,但行列反了 |
int a[3, 4]; |
逗号表达式!等于 int a[4]; |
❌ 变成一维数组(3.1.9 的逗号运算符) |
int a[12]; |
一维 12 格 | ❌ 不是二维,a[1][0] 会报错 |
a[3, 4] 那个坑值得停一下:方括号里写逗号,编译器不会拦你——那时 , 是 3.1.9 讲的逗号运算符(取最后一个值),所以 a[3,4] 等价于 a[4],你得到的是一个一维数组。后面所有 a[i][j] 的写法全部报错。
二、行优先:两维怎么摊平成一排
int a[3][4] 在内存里的真实样子:
下标 [0][0] [0][1] [0][2] [0][3] │ [1][0] [1][1] [1][2] [1][3] │ [2][0] [2][1] [2][2] [2][3]
└──────── 第 0 行 ────────┘ └──────── 第 1 行 ────────┘ └──────── 第 2 行 ────────┘
地址 1000 1004 1008 1012 1016 1020 1024 1028 1032 1036 1040 1044
读法:先把第 0 行 4 格从左到右摆完,紧接着摆第 1 行,再摆第 2 行。所以:
a[0][3]的下一个是a[1][0](不是a[0][4],那一格不存在);- 第 i 行第 j 列的偏移 =
i * 列数 + j。比如a[1][2]是第1*4+2 = 6格; - 这条公式就是 7.5.2 用"一个指针走遍整张表"的原理。
三、声明的三个自查问题
int a[3][4];
sizeof(a) // 48:整张表 48 字节(3 × 4 × 4)
sizeof(a[0]) // 16:一行 16 字节
sizeof(a[0][0]) // 4 :一格 4 字节
sizeof(a) / sizeof(a[0]) // 3:行数
sizeof(a[0]) / sizeof(a[0][0]) // 4:列数
最后两行是标准"数行列"写法,和 7.1.1 的"数格子"一个套路:将来把 [3][4] 改成 [5][6],不用动任何循环条件。
四、动手 Demo:量一张 3×4 表
#include <iostream>
using namespace std;
int main() {
int a[3][4];
cout << "整张表字节 = " << sizeof(a) << endl;
cout << "一行字节 = " << sizeof(a[0]) << endl;
cout << "一格字节 = " << sizeof(a[0][0]) << endl;
cout << "行数 = " << sizeof(a) / sizeof(a[0]) << endl;
cout << "列数 = " << sizeof(a[0]) / sizeof(a[0][0]) << endl;
cout << "a[0] 的地址 = " << a[0] << endl;
cout << "a[1] 的地址 = " << a[1] << endl;
cout << "两者相差字节 = " << (char*)a[1] - (char*)a[0] << endl;
return 0;
}
看到什么算成功:依次是 48、16、4、3、4;最后三行会打印两个地址(每次运行不同),且相差恰好 16 字节——正好是一行的字节数。
最后那个数字最说明问题:a[1] 比 a[0] 大 16 字节,证明每一行占据连续的 16 字节,行与行紧挨着。这就是"行优先、连续存放"的直接证据。
30 秒实验:把声明改成 int a[5][6];,重跑——五个数字自动变成 120、24、4、5、6。仍然一处都不用手改。
五、常见误区
- "
a[3][4]是 3 列 4 行"——行在前、列在后:3 行 4 列。读法照抄int a[行][列]。 - "
int a[3, 4];也能声明二维数组"——它其实是一维数组a[4](逗号运算符取末值)。这类错误编译器不一定报警,但后面全崩。 - "行数列数可以是变量"——和 7.1.1 一样,必须是编译期常量。只有
const常量、宏、字面量才行。 - "声明完格子里是 0"——不保证,整张表 12 格都是垃圾值。要干净就用 7.2.3 的初始化。
自测三问(点开对答案)
1. `int a[2][5]` 一共几格、多少字节(int)?——10 格、40 字节。 2. `a[1][2]` 是内存里的第几格(从 0 数,列数为 5)?——第 `1*5+2 = 7` 格。 3. 为什么 `int a[3, 4];` 不是二维数组?——`,` 是逗号运算符,`a[3,4]` 等价于 `a[4]`,声明成了一个一维数组。- ☐ 7.2.2 二维数组元素的引用(下一节)
相关笔记
- 二维数组 —— 二维数组是什么、为什么要有
- 一维数组的声明 —— 声明规则的来源
- 逗号运算符 ——
a[3,4]为什么不是二维 - sizeof运算符 —— 数行数列的尺子
- 🔷 C++基础 · 索引 —— 全课路线图