int (p)[n] 指向含n个int的数组(整行),如int a3中p=a使p++跳过一行;int p[n]是含n个int的数组,适合锯齿数组或动态行;传参应选int ()[n]而非int**,因二维数组名退化为行指针而非二级指针。

用 int (*p)[N] 指向二维数组的首行
这是最常被混淆也最实用的写法:声明一个指向「含 N 个 int 的数组」的指针。它不是指向单个元素,而是整行。
比如 int a[3][4],它的每一行是长度为 4 的 int 数组,类型是 int[4],所以指针类型必须是 int (*)[4](括号不能省,否则变成指针数组)。
-
int (*p)[4] = a;—— 合法,a隐式转为指向首行的指针 -
p++;会让p指向下一行(偏移4 * sizeof(int)),不是下一个元素 -
(*p)[2]等价于a[0][2];p[1][3]等价于a[1][3](因为p支持下标运算) - 错误写法:
int *p = a;—— 编译可能通过但行为未定义;a不是int*,强转后p+1只偏移 4 字节,会跨列而非跨行
用 int *p[N] 存储每行首地址(指针数组)
这是真正意义上的「数组」,每个元素都是 int* 类型,适合各行长度不一(即模拟“锯齿数组”),或需要单独管理每行内存的情况。
对固定大小二维数组,你可以手动初始化它来间接访问:
int a[3][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};
int *p[3] = {a[0], a[1], a[2]}; // 或写成 &a[0][0], &a[1][0], &a[2][0]
-
p[i][j]是合法且常用的写法,等价于*(p[i] + j) - 和
int (*p)[4]不同,p本身是栈上数组,3 个指针共占3 * sizeof(int*),不共享原数组内存布局 - 若后续某行指向堆内存(如
p[1] = new int[10];),就彻底脱离原二维数组,不能再假设行列连续 - 别写成
int *p[] = {a};——a无法隐式转为int*,编译失败
传参时怎么选:函数形参写 int (*)[N] 还是 int**?
关键看调用方是否能提供连续内存块。C++ 中二维数组名传给函数时,退化为指向首行的指针,不是 int**。
- 接收固定列数的二维数组:
void f(int (*a)[4], int rows) { ... }—— 安全、高效,编译器知道步长 - 用
int**的前提是调用方真有int**(比如先分配int*[],再每行new),否则传a会编译报错或运行崩溃 - 常见错误:
void f(int** a) { a[1][2] = 0; }+f(a);(其中a是int[3][4])—— 类型不匹配,UB - 万能但啰嗦的方式:
void f(int* base, int rows, int cols) { base[i * cols + j] = ...; },把二维当一维用
容易忽略的边界:sizeof 和类型推导
sizeof 是暴露理解深度的试金石。同一段内存,不同指针类型下 sizeof 结果完全不同。
-
int a[3][4]; sizeof(a) == 48(假设int为 4 字节) -
int (*p)[4] = &a[0]; sizeof(p) == 8(指针大小,非所指内容) -
int *q[3]; sizeof(q) == 24(3 个指针) -
auto p1 = a;→ 推导为int (*)[4](C++11 起) -
auto p2 = &a[0][0];→ 推导为int*,此时p2 + 1移动 4 字节,不是跳到下一列起点
二维数组本质是连续内存块,但“怎么看它”,完全取决于你用什么类型去解释那片内存 —— 指针类型就是你的解释器。写错类型,轻则越界访问,重则整个逻辑偏移。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











