第42章:指针数组与数组指针——int *p[5] 和 int (*p)[5] 的区别

说实话,这道题几乎是每个C语言面试官的必问题。我当年第一次看到这两个声明时,也懵了好一阵子。明明都是p和5,怎么一个是指针数组,一个是数组指针?

嗯,咱们今天就把这个「老冤家」彻底讲清楚。你只要记住一句话:看优先级,看结合性

一、先看声明,别被符号绕晕

先上代码,咱们直接对比着看:

int *p[5];      // 指针数组
int (*p)[5];    // 数组指针

这两个声明,就差一对括号。但含义天差地别。

为什么会这样?因为C语言里,[] 的优先级高于 *。所以:

  • int *p[5]:p 先和 [5] 结合,说明 p 是一个数组,数组有5个元素。每个元素是什么?是 int *,也就是指向整型的指针。所以叫「指针数组」——数组里存的是指针。
  • int (*p)[5]:括号让 p 先和 * 结合,说明 p 是一个指针。这个指针指向什么?指向一个长度为5的整型数组。所以叫「数组指针」——指针指向一个数组。

核心口诀:

  • 先看 p 和谁结合
  • 和 [ ] 结合 → 数组
  • 和 * 结合 → 指针

二、用图说话:SVG结构对比

我画了一张图,帮你直观理解这两个东西在内存里长什么样:

指针数组 vs 数组指针 内存结构对比 int *p[5](指针数组) p[0] p[1] p[2] p[3] p[4] a b c d e 每个元素存的是地址 int (*p)[5](数组指针) p arr[0] arr[1] arr[2] arr[3] arr[4] p指向整个数组(5个int) 指针数组:数组里存的是指针,每个指针指向一个int变量 数组指针:一个指针,指向一个包含5个int的数组 常用于:字符串数组、指针表 常用于:二维数组的行指针 char *str_arr[3] = {"hi","ok","go"}; int (*row)[5] = &matrix[0];

三、代码实战:动手写一写

光看理论不够,咱们直接上代码。我写两个例子,你一看就明白:

示例1:指针数组

#include <stdio.h>

int main() {
    int a = 10, b = 20, c = 30;
    int *p[3];  // 指针数组:能存3个指针

    p[0] = &a;
    p[1] = &b;
    p[2] = &c;

    for (int i = 0; i < 3; i++) {
        printf("p[%d] = %d\n", i, *p[i]);
    }
    return 0;
}

输出:

p[0] = 10
p[1] = 20
p[2] = 30

你看,p 是一个数组,里面每个元素都是指针。我们用下标访问每个指针,再解引用拿到值。

示例2:数组指针

#include <stdio.h>

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int (*p)[5];  // 数组指针:指向一个长度为5的整型数组

    p = &arr;     // 注意:取的是整个数组的地址

    for (int i = 0; i < 5; i++) {
        printf("(*p)[%d] = %d\n", i, (*p)[i]);
    }
    return 0;
}

输出:

(*p)[0] = 1
(*p)[1] = 2
(*p)[2] = 3
(*p)[3] = 4
(*p)[4] = 5

这里 p 是一个指针,它指向整个数组。要访问数组元素,得先解引用拿到数组,再用下标。

小技巧:

我个人习惯这样记——看到 int *p[5],心里默念「p是数组,元素是指针」;看到 int (*p)[5],默念「p是指针,指向数组」。

另外,用 sizeof 也能帮你验证:

  • sizeof(p) 在指针数组里是数组总大小(5 * 8 = 40字节,64位系统)
  • sizeof(p) 在数组指针里是指针本身大小(8字节)

四、避坑指南:我踩过的雷

我曾经在一个嵌入式项目里,需要管理多个传感器的数据缓冲区。我本来想定义一个指针数组,每个指针指向一个缓冲区。结果手一抖,写成了数组指针——

// 我本来想这样:
int *buf[3];  // 3个指针,每个指向一个缓冲区

// 结果写成了这样:
int (*buf)[3];  // 1个指针,指向一个长度为3的数组

编译没报错,但运行时数据全乱套了。因为 buf[1] 在第一种写法里是第二个指针,在第二种写法里是数组的第二个元素。排查了整整一个下午,最后发现是声明写错了。

重要提醒:

  • 声明时一定要看清楚括号的位置
  • typedef 可以简化复杂声明,比如:typedef int (*ArrayPtr)[5];
  • 在函数参数中,数组指针常用于传递二维数组:void func(int (*arr)[5])

五、常见用法对比表

特性 int *p[5](指针数组) int (*p)[5](数组指针)
本质 数组,元素是指针 指针,指向数组
sizeof(p) 5 * 指针大小(如40字节) 指针大小(如8字节)
p+1 的步长 一个指针的大小 整个数组的大小(5 * int)
典型用途 字符串数组、函数指针表 二维数组的行指针、多维数组遍历
访问元素 *p[i] 或 **(p+i) (*p)[i] 或 p[0][i]

六、总结一下

说白了,这两个东西的区别就是「谁是谁的容器」:

  • 指针数组:数组是容器,里面装的是指针
  • 数组指针:指针是容器,指向一个数组

你想想看,是不是这个理?

嗯,这道题其实不难,就是符号优先级容易搞混。我的建议是:每次写这类声明时,先想清楚你要的是什么——是要一堆指针,还是要一个指向数组的指针。想清楚了再下笔,括号该加就加,别省。

我在实际项目中,指针数组用得更多一些,尤其是做命令表、状态机跳转表的时候。数组指针则常在处理二维数组时出现,比如图像处理中的像素矩阵操作。两种都有用武之地,关键是要用对地方。


公众号:蓝海资料掘金营,微信 deep3321