把一维数组硬当成二维用:一次讲透 C 语言“指向数组的指针”

1. 故事开场:一段“诡异”代码

int a[] = { 10,12,30,14,50,60,71,80,90,10 };
int (*p)[4] = (int (*)[4])a;
printf("%d\n", p[1][2]);   // 输出 71

短短三行,把不少同学看懵:
“一维数组怎么突然 p[1][2] 了?”、“int (*p)[4] 到底是个啥?”

下面按“理解→实战→总结”三步,一次性拆干净。


2. 拆开类型: int (*p)[4] 怎么读

写法 含义 加减 1 的步长
int *p 指向 单个 int sizeof(int)
int (*p)[4] 指向 一整行 4 个 int 的数组 4*sizeof(int)

记忆技巧:
(*p) 看成整体,它就是“数组名”,p 自然就是“指向那个数组的指针”。
括号必不可少,否则 int *p[4] 变成“指针数组”——语义完全不同。


3. 内存布局:为什么 p[1][2] 是 7

强转之后,编译器把原内存按“每 4 个 int 一行”重新解释:

地址低 → 高
+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+
|   10   |   12   |  30    |   14   |  50    |   60   |   71   |   80   |   90   |   10   |
+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+
 \____________  row 0  __________/ \____________  row 1  __________/ \_____  row 2 _____/
  • p 指向 row 0
  • p + 1 跳过 一整行 → 指向 row 1 的首元素 5
  • *(p + 1) 拿到 row 1 这一整行(数组)
  • [2] 取下标 → 拿到 7

4. 实战场景:什么时候真有必要用“指向数组的指针”

场景 int (*p)[N] 的理由 用普通 int *p 的痛点
二维数组传参 列宽 N 写进类型,编译期检查;函数内仍可用 p[i][j] 语法 列宽靠额外参数,易传错;索引需手动 i*col+j
按行迭代 p++ 一次跳一行,代码短 只能 q += col,列宽一改全得改

示例:类型安全的传参

void foo(int (*row)[4], int n_row)
{
    for (int i = 0; i < n_row; ++i)
        printf("%d\n", row[i][2]);   // 直接写列号 2
}

int main(void)
{
    int m[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
    foo(m, 3);          // 列宽 4 已硬编码在类型里
}

若误传 int x[3][5],编译器立刻报错,防止越界。


5. 易混雷区: m[1] 到底是不是指针?

int m[3][4] = {...};
printf("%p\n", (void*)m[1]);   // 能打印地址
printf("%d\n", *m[1]);         // 输出 5
printf("%d\n", m[1][1]);       // 输出 6
  • m[1] 类型int[4]——数组,不是指针。
  • 只有当数组出现在 下标运算、函数实参 等上下文时,编译器才临时把它退化成 int*
  • 因此 *m[1] 等价于 *(m[1]退化后的指针 + 0),拿到首元素 5
    m[1][1] 等价于 *(m[1]退化后的指针 + 1),拿到 6
  • *m[1][1] 会报错,因为 m[1][1] 已是纯 int,不能再解引用。

6. 小结一张图

源码写法          实际类型           步长(+1)        常见用途
int *p            int *              sizeof(int)       一维/扁平缓冲区
int (*p)[4]       指向int[4]         4*sizeof(int)     二维数组按行传参、按行迭代
int *p[4]         4个int*的数组      sizeof(int*)      指针数组(完全不同)

7. 结语

  • 日常元素访问直接写 m[i][j] 最清爽。
  • 一旦涉及“把二维数组当整体传参”或“按行遍历”,int (*p)[N]唯一能把列宽写进类型、让编译器帮你查错 的利器。
  • 记住“数组名先退化再运算”这条核心规则,一切指针花样都会变得透明。

希望这篇小文能把“指向数组的指针”彻底从玄学变成工具,不过是让编译器记住‘一行有多宽’罢了!”

关键词:C 语言、数组退化、指向数组的指针、二维数组传参、指针运算

posted @ 2025-12-17 09:49  Tlink  阅读(10)  评论(0)    收藏  举报