第十六章 多级指针:二级指针的定义、二级指针与二维数组、二级指针的应用
指针这东西,学完一级就觉得差不多了?
嗯,我当年也这么想。直到我在项目中遇到一个场景——需要在一个函数里修改传入的指针本身,而不是它指向的内容。那时候我才意识到,指针还能再套一层。
说白了,二级指针就是「指向指针的指针」。今天咱们就把这个看似绕、实则很实用的东西讲透。
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 **pp → pp 是一个指针,指向一个指针,这个指针指向 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 知识体系图
下面这张图帮你理清二级指针的核心脉络:
16.5 总结
二级指针其实没那么玄乎。记住三点:
- 定义上:
int **pp存的是指针变量的地址 - 与二维数组的关系:通过指针数组模拟,不能直接指向二维数组名
- 应用上:修改指针本身、动态二维数组、命令行参数,这三个场景足够覆盖90%的使用情况
嗯,今天就到这里。二级指针掌握好了,后面学链表、树这些数据结构时,你会觉得轻松很多。
公众号:蓝海资料掘金营,微信deep3321