一、指针数组与数组指针:两个容易搞混的兄弟

说实话,指针数组和数组指针这两个概念,是我在教C语言时被问得最多的问题。很多初学者甚至工作两三年的工程师,一提到这两个词就开始犯迷糊。我自己刚入行时也踩过这个坑——有一次在嵌入式项目中,因为把数组指针声明错了,导致整个数据采集系统跑飞,排查了整整一个下午。

今天我们就彻底把它们掰扯清楚。

1.1 指针数组:数组里装的是指针

先看指针数组。说白了,它就是一个数组,只不过数组里的每个元素都是指针。

声明方式:

int *arr[5];  // 指针数组:arr是一个数组,有5个元素,每个元素都是int*类型

怎么理解?你想想看,[]的优先级比*高,所以arr先和[5]结合,说明它是一个数组。数组的元素类型是int *,也就是指向整型的指针。

我在项目中遇到过这样一个场景:需要管理多个动态分配的缓冲区,每个缓冲区大小不同。用指针数组就特别方便:

#include <stdio.h>
#include <stdlib.h>

int main() {
    char *str_arr[3];  // 指针数组,每个元素指向一个字符串
    
    str_arr[0] = (char *)malloc(20);
    str_arr[1] = (char *)malloc(30);
    str_arr[2] = (char *)malloc(10);
    
    sprintf(str_arr[0], "Hello");
    sprintf(str_arr[1], "Embedded World");
    sprintf(str_arr[2], "C");
    
    for (int i = 0; i < 3; i++) {
        printf("%s\n", str_arr[i]);
        free(str_arr[i]);
    }
    
    return 0;
}

核心记忆点:指针数组的本质是「数组」,里面存的是指针。

1.2 数组指针:指向数组的指针

数组指针就反过来了。它是一个指针,指向的是一个数组。

声明方式:

int (*ptr)[5];  // 数组指针:ptr是一个指针,指向一个包含5个int元素的数组

注意看,这里加了括号。(*ptr)说明ptr首先是一个指针,然后它指向的类型是int [5],也就是一个长度为5的整型数组。

为什么会这样?因为[]优先级高,如果不加括号,int *ptr[5]就成了指针数组。所以括号是关键。

我曾经在调试一个二维数组的传参问题时,就靠数组指针搞定的。来看个例子:

#include <stdio.h>

void print_matrix(int (*matrix)[3], int rows) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%d ", matrix[i][j]);
        }
        printf("\n");
    }
}

int main() {
    int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
    // arr是二维数组,arr[0]的类型是int (*)[3],即数组指针
    print_matrix(arr, 2);
    return 0;
}

核心记忆点:数组指针的本质是「指针」,它指向一个数组。

1.3 对比表格:一目了然

概念 声明 本质 sizeof结果 典型用途
指针数组 int *p[5] 数组,元素是指针 5 * sizeof(int*) 字符串数组、动态缓冲区管理
数组指针 int (*p)[5] 指针,指向数组 sizeof(int*) 二维数组传参、矩阵操作

小技巧:读声明时从变量名开始,向右看遇到[]就是数组,向左看遇到*就是指针。比如int *p[5],从p向右看到[5],所以是数组。

1.4 应用场景:什么时候用哪个?

指针数组的应用场景:

  • 命令行参数处理:main(int argc, char *argv[])中的argv就是指针数组,每个元素指向一个参数字符串。
  • 字符串查找表:比如错误码对应的错误信息,用指针数组存起来,通过索引快速访问。
  • 动态对象管理:多个动态分配的对象,用指针数组统一管理,方便遍历和释放。

数组指针的应用场景:

  • 二维数组传参:函数需要接收一个二维数组时,用数组指针作为参数,明确指定列数。
  • 矩阵运算:处理固定大小的矩阵时,数组指针能让代码更清晰。
  • 多维数组的逐行处理:通过数组指针遍历二维数组的每一行。

避坑指南:我曾经在项目里把int (*p)[5]写成了int *p[5],结果编译器没报错,但运行时数据全乱了。因为前者p+1跳过一个数组(20字节),后者p+1只跳过一个指针(4或8字节)。这个区别在嵌入式开发中尤其致命。

1.5 知识体系图

下面这张图帮你理清指针数组和数组指针的关系:

指针数组 vs 数组指针 知识体系 指针数组 int *p[5] 本质:数组,元素是指针 sizeof(p) = 5 * 4/8 字节 应用:字符串数组、参数列表 数组指针 int (*p)[5] 本质:指针,指向数组 sizeof(p) = 4/8 字节 应用:二维数组传参、矩阵 容易混淆 记忆口诀 先看变量名右边:有[]是数组,有)是指针

1.6 一个综合例子

最后,我写一个综合例子,把指针数组和数组指针都用上。这个例子模拟了一个简单的成绩管理系统:

#include <stdio.h>

// 用数组指针接收二维数组
void print_scores(int (*scores)[4], int students) {
    for (int i = 0; i < students; i++) {
        printf("学生%d: ", i+1);
        for (int j = 0; j < 4; j++) {
            printf("%d ", scores[i][j]);
        }
        printf("\n");
    }
}

int main() {
    // 二维数组:3个学生,每人4门课成绩
    int scores[3][4] = {
        {85, 90, 78, 92},
        {88, 76, 95, 89},
        {91, 82, 87, 94}
    };
    
    // 指针数组:存储每个学生的名字
    char *names[3] = {"张三", "李四", "王五"};
    
    printf("成绩单:\n");
    for (int i = 0; i < 3; i++) {
        printf("%s: ", names[i]);
    }
    printf("\n\n");
    
    // 调用函数,传入数组指针
    print_scores(scores, 3);
    
    return 0;
}

嗯,这个例子虽然简单,但把两个概念都用上了。names是指针数组,存了三个字符串指针;print_scores的参数scores是数组指针,指向一个4列的二维数组。

我个人习惯在写代码时,遇到这种容易混淆的地方,就加个注释。比如:

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

这样过几个月回头看,也不会搞混。

总结一下:指针数组和数组指针,一个存指针,一个指数组。记住「先看变量名右边」这个原则,你就能轻松区分。实际开发中,指针数组常用于管理多个独立的数据块,数组指针则擅长处理二维数组的传参和遍历。


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