先别急着背语法。很多人学C语言的时候,卡就卡在“一级指针、二级指针、三级指针”这里。你问“int **p”是什么意思,书上会告诉你“指向指针的指针”;你问为什么要三级指针,答案常常是“指向指针的指针的指针”。这种车轱辘话背完就忘,一写代码照样懵。今天我就从实际使用场景出发,彻底把这层窗户纸捅破。
我不会从“什么是指针”这种教科书定义开始讲。咱们换个思路:先搞清楚“函数里到底要修改谁”,然后再看该传几层地址。你会发现,几级指针根本不用死记,它完全是由“修改对象”决定的。这篇文章适合刚学完指针基础、正在被多级指针折磨的初学者,也适合想彻底搞懂指针层数、在面试笔试里不再丢分的求职者。从一级到三级,每个层级我都会给出能直接跑的代码、常见的错误写法、还有我这些年踩过的坑。
1. 先从“改一个变量”说起:一级指针的核心语义
1.1 指针变量存的是谁的地址
想象一下,你有一栋楼,每户都有一个门牌号,比如302。你想让朋友帮你取东西,你不能直接把东西带过去,你得把“302”这个门牌号告诉他,他按着门牌号去开门,才能拿到里面的东西。
C语言里就是这么回事。int a = 10;,这里的a是房间里的东西,&a就是房间的门牌号。而指针变量int *p,本质就是一张“写着门牌号”的纸条:
c复制int a = 10;
int *p = &a; // p 这张纸条上写的是 a 的门牌号
*p = 99; // 顺着门牌号找到 a,把 10 改成 99
一级指针,就是这张纸条上存的是普通变量的地址。它做的事就一件:通过地址,直接操作那个变量本身。这套逻辑看着简单,但它是一切的根基。二级指针、三级指针,无非是“纸条上写的地址,指向的也是一个纸条”,一层套一层而已。
1.2 函数传参为什么绕不开指针
C语言函数参数是值传递,这是理解指针的关键中的关键。我见过太多新手在swap函数上翻车:
c复制void swap(int a, int b) {
int tmp = a;
a = b;
b = tmp;
}
int main() {
int x = 1, y = 2;
swap(x, y);
printf("%d %d\n", x, y); // 输出还是 1 2,没换过来
}
为什么?因为调用swap(x, y)时,函数拿到的是x和y的“拷贝”。你确实在函数里交换了拷贝,但外面那两扇门里的东西你从头到尾没碰过。要想真正改外面的x和y,必须把门牌号交进去,让函数按门牌号去开门拿东西:
c复制void swap(int *a, int *b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
swap(&x, &y);
这个例子值得反复咀嚼。它说明了一个核心规律:在C语言里,函数想要修改实参本身,就必须传入实参的地址。 一级指针,就是用来修改一个普通变量的。
1.3 一级指针最常见的三个坑
写C不可能不踩指针的坑,我在一线代码里见得最多的就是下面这三个:
第一个坑:声明了却不用。 很多人写int *p;就以为p有指向了,然后直接*p = 10;。这时候p的值是随机的,可能指向一个受保护的内存区域,程序直接段错误。正确做法是声明时立即初始化:
c复制int *p = NULL; // 先置空
p = (int*)malloc(sizeof(int)); // 或者指向一个有效变量
第二个坑:内存释放后没置空。 调用free(p)之后,指针里存的地址还在,但这个内存已经被回收了。此时再用p就是“悬空指针”,行为未定义。free之后顺手p = NULL;是一个能救命的习惯。
第三个坑:返回局部变量的地址。 函数内部的普通变量,在函数结束后就销毁了,把它的地址返回出去,拿到的人访问的是一块已经失效的栈内存:
c复制int* foo() {
int local = 10;
return &local; // 大错特错,local 已经没了
}
这三个坑,看似是一级指针的事,但二级指针的世界里同样会有这些问题,而且一旦出错,排查起来更难。所以基础阶段一定把上面这些细节抓实。
1.4 顺带说清数组名和指针的区别
很多教材一上来就说“数组名就是指针”,这话在函数传参的语境下大体没错,但较真起来是错的。int arr[5],arr的类型是int[5],值是&arr[0]。它和指针的经典区别是什么?
c复制int arr[5];
int *p = arr;
printf("%zu %zu\n", sizeof(arr), sizeof(p)); // 输出 20 和 8
sizeof(arr)是整个数组占的空间,sizeof(p)只是一个指针变量占的空间,二者不一样。你再算一下&arr + 1和arr + 1,前者跳过整个数组,后者只跳过一个int。数组名在大多数表达式中会“退化”成指针,但它本身并不是一个真正的指针变量,你不能对它执行arr++。搞清楚这一点,后面聊二级指针和二维数组名时就不会混了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 二级指针:当你要修改的是一级指针本身
2.1 核心场景:让函数给外部指针赋新值
现在难度升一级。你写了一个函数,想在函数内部申请一块内存,然后让外面的指针指向这块内存。典型的例子:
c复制void
