第十六章 多级指针:二级指针的定义、二级指针与二维数组、二级指针的应用

指针这东西,学完一级就觉得差不多了?

嗯,我当年也这么想。直到我在项目中遇到一个场景——需要在一个函数里修改传入的指针本身,而不是它指向的内容。那时候我才意识到,指针还能再套一层。

说白了,二级指针就是「指向指针的指针」。今天咱们就把这个看似绕、实则很实用的东西讲透。

16.1 二级指针的定义

先看定义。二级指针,就是一个变量,它存的是另一个指针变量的地址。

int a = 10;
int *p = &a;   // 一级指针,存 a 的地址
int **pp = &p; // 二级指针,存 p 的地址

你看,pp 前面有两个星号,意思就是「指向指针的指针」。解引用一次 *pp 得到 p,解引用两次 **pp 得到 a

核心理解:

  • pp 的类型是 int **
  • *pp 的类型是 int *
  • **pp 的类型是 int

我个人习惯把二级指针想象成「遥控器的遥控器」。一级指针直接控制变量,二级指针控制那个遥控器本身。

小技巧: 定义二级指针时,可以这样读:从右往左读变量声明。int **pppp 是一个指针,指向一个指针,这个指针指向 int

16.2 二级指针与二维数组

很多初学者会把二级指针和二维数组混为一谈。我刚开始学的时候也踩过这个坑。

二维数组名,比如 int arr[3][4],它的类型是 int (*)[4],也就是「指向长度为4的整型数组的指针」。这不是二级指针!

那二级指针怎么跟二维数组扯上关系?

实际项目中,我们经常用「指针数组」来模拟二维数组。比如:

int a[4] = {1, 2, 3, 4};
int b[4] = {5, 6, 7, 8};
int c[4] = {9, 10, 11, 12};

int *p[3] = {a, b, c};  // 指针数组
int **pp = p;            // 二级指针指向指针数组首元素

这时候 pp[i][j] 就可以像二维数组一样访问了。为什么?因为 pp[i] 等价于 *(pp + i),得到的是第 i 行的首地址,再 [j] 就是第 j 个元素。

注意: 二级指针不能直接指向二维数组名!int arr[3][4]; int **pp = arr; 这样写编译器会报警告或错误。类型不匹配。

我曾经在一个图像处理项目里,用二级指针管理多个图像行缓冲区。每行数据长度相同,用二级指针访问起来非常方便,比用二维数组灵活得多——因为行指针可以动态分配。

16.3 二级指针的应用

二级指针最经典的应用场景有三个。我一个个说。

16.3.1 在函数中修改指针本身

这是最常见的用法。你想在一个函数里改变传入指针的指向,必须传指针的地址。

void alloc_memory(int **p, int size) {
    *p = (int *)malloc(size * sizeof(int));
    if (*p == NULL) {
        printf("内存分配失败\n");
        exit(1);
    }
}

int main() {
    int *arr = NULL;
    alloc_memory(&arr, 10);  // 传二级指针
    // 现在 arr 指向了堆上分配的内存
    free(arr);
    return 0;
}

你看,如果不传 &arr,只传 arr,函数内部修改的只是形参,实参 arr 还是 NULL。这个坑我当年调试了整整一个下午才找到原因。

16.3.2 管理动态二维数组

当行数和列数在运行时才能确定时,二级指针是首选方案。

int rows = 3, cols = 4;
int **matrix = (int **)malloc(rows * sizeof(int *));
for (int i = 0; i < rows; i++) {
    matrix[i] = (int *)malloc(cols * sizeof(int));
}

// 使用 matrix[i][j] 访问元素

// 释放
for (int i = 0; i < rows; i++) {
    free(matrix[i]);
}
free(matrix);

这种结构在嵌入式系统中很常见,比如管理多个传感器的数据缓冲区。每个传感器的数据长度可能不同,用二级指针可以灵活处理。

16.3.3 处理命令行参数

你写过 main(int argc, char **argv) 吧?这个 argv 就是二级指针。它指向一个字符串指针数组,每个指针指向一个命令行参数。

int main(int argc, char **argv) {
    for (int i = 0; i < argc; i++) {
        printf("参数 %d: %s\n", i, argv[i]);
    }
    return 0;
}

说白了,argv 就是一个二级指针,它管理着一组字符串。你想想看,如果不用二级指针,你怎么在函数间传递这个参数列表?

避坑指南: 我曾经在解析命令行参数时,试图用 argv++ 来跳过程序名。这样做没问题,但要注意 argv 本身是 char ** 类型,argv++ 移动的是指针数组的偏移量,不是字符串的偏移量。别搞混了。

16.4 知识体系图

下面这张图帮你理清二级指针的核心脉络:

二级指针 定义与本质 与二维数组 三大应用场景 int **pp = &p; *pp → 一级指针 **pp → 实际变量 指针数组模拟 pp[i][j] 访问 不能直接指向二维数组名 修改指针本身 动态二维数组 命令行参数 argv 核心:二级指针 = 指向指针的指针

16.5 总结

二级指针其实没那么玄乎。记住三点:

  • 定义上int **pp 存的是指针变量的地址
  • 与二维数组的关系:通过指针数组模拟,不能直接指向二维数组名
  • 应用上:修改指针本身、动态二维数组、命令行参数,这三个场景足够覆盖90%的使用情况

嗯,今天就到这里。二级指针掌握好了,后面学链表、树这些数据结构时,你会觉得轻松很多。


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