8. 指针的指针:二级指针到底有什么用?
说实话,很多C语言学习者学到指针就卡住了。等学到二级指针,更是直接懵圈。
“指针不就是存地址吗?那指针的指针,不就是存地址的地址?这玩意儿到底有啥用?”
嗯,我当年学的时候也这么想过。直到我在项目中栽了个大跟头,才真正明白二级指针的价值。
8.1 先搞清楚:二级指针到底是什么?
咱们先别急着上代码。你想想看,一级指针存的是普通变量的地址。那二级指针呢?它存的是指针变量的地址。
画个图就清楚了:
说白了,二级指针就是指向指针的指针。它不直接指向数据,而是指向一个“存着数据地址”的指针变量。
核心理解:
int a = 42; // 普通变量
int *p = &a; // 一级指针,存 a 的地址
int **pp = &p; // 二级指针,存 p 的地址
int a = 42; // 普通变量
int *p = &a; // 一级指针,存 a 的地址
int **pp = &p; // 二级指针,存 p 的地址
8.2 二级指针最常见的两个用途
我做了这么多年嵌入式开发,二级指针其实就两个核心用途。搞懂这两个,你就掌握了。
用途一:在函数内部修改指针本身
这是最经典的应用场景。你想想看,C语言函数传参是值传递。如果你传一个指针进去,函数里能改指针指向的内容,但改不了指针本身。
举个例子:
// 错误示范:想通过函数修改指针
void alloc_memory_wrong(int *p) {
p = (int *)malloc(sizeof(int) * 10);
// 这里改的是 p 的副本,外面的指针没变!
}
// 正确做法:用二级指针
void alloc_memory_correct(int **pp) {
*pp = (int *)malloc(sizeof(int) * 10);
// *pp 就是外面的那个指针,这下改到了
}
int main() {
int *arr = NULL;
alloc_memory_correct(&arr); // 传指针的地址
// arr 现在指向了堆内存
return 0;
}
⚠️ 我曾经踩过的坑:
刚入行时写链表,想在函数里给头指针分配内存。传了一级指针进去,结果函数返回后头指针还是 NULL。查了半天才发现,我改的只是形参的副本。从那以后,凡是需要在函数里“改变指针指向”的,我必用二级指针。
用途二:操作指针数组
比如你有一个字符串数组:
char *names[] = {"Alice", "Bob", "Charlie"};
// names 本身是一个数组,每个元素是 char* 指针
// 要遍历或排序这个数组,用二级指针就很自然
void sort_names(char **arr, int count) {
// arr 就是二级指针,指向数组首元素
// 每个元素 arr[i] 是一个 char*
for (int i = 0; i < count - 1; i++) {
for (int j = i + 1; j < count; j++) {
if (strcmp(arr[i], arr[j]) > 0) {
char *tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
}
}
你看,这里 char **arr 就是二级指针。它指向一个指针数组的首元素。这种写法在嵌入式里处理配置表、命令表时特别常见。
8.3 二级指针与多维数组
很多人把二维数组和二级指针搞混。我明确告诉你:它们不是一回事。
| 特性 | 二维数组 int a[3][4] | 二级指针 int **p |
|---|---|---|
| 内存布局 | 连续的一块内存 | 可能不连续,每行单独分配 |
| 类型 | int (*)[4] | int ** |
| 能否用 a[i][j] | 可以 | 可以(前提是正确分配) |
| 函数传参 | 需要指定列数 | 直接传二级指针 |
💡 我的建议:
在嵌入式开发中,如果数组大小固定,用二维数组更高效(内存连续,缓存友好)。如果大小动态变化,或者每行长度不同,用二级指针更灵活。
8.4 避坑指南:二级指针的常见错误
我见过太多人在二级指针上翻车。这里列几个最常见的:
- 忘记初始化:声明了
int **pp就直接用,结果野指针崩溃。 - 混淆 *pp 和 **pp:
*pp是一级指针,**pp才是最终的数据。 - 内存泄漏:用二级指针动态分配了二维结构,释放时只释放了外层。
// 错误示例:只释放了外层
int **matrix = (int **)malloc(rows * sizeof(int *));
for (int i = 0; i < rows; i++) {
matrix[i] = (int *)malloc(cols * sizeof(int));
}
free(matrix); // ❌ 内层每行的内存没释放!
// 正确释放
for (int i = 0; i < rows; i++) {
free(matrix[i]); // 先释放每行
}
free(matrix); // 再释放行指针数组
8.5 总结一下
二级指针其实没那么玄乎。你只要记住两件事:
- 它存的是指针变量的地址
- 它主要用来在函数里修改指针本身,或者操作指针数组
我个人习惯在写链表、树这类数据结构时,大量使用二级指针。尤其是需要修改头指针的场景,二级指针比返回值更直观、更安全。
嗯,二级指针就讲这么多。你只要动手写几个例子,很快就能掌握。
一句话记住:
一级指针改数据,二级指针改指针。
一级指针改数据,二级指针改指针。