如果你问学 C 的人,哪一块最难啃?十个里面有九个会告诉你:指针。
它就像一只看不见摸不着的猫,稍不注意就抓你一爪子。初学者的常见体验是:写出来的代码不是报错,就是跑飞。更离谱的是:有时候根本没报错,结果却完全不对。
我当年学单片机的时候,第一次接触指针,几乎整整一个星期都卡在这里。今天,就带大家走一遍从“野指针”到“指针数组”的血泪史。
1. 野指针:最常见的坑
所谓野指针,就是指向了不确定内存区域的指针。比如:
#include <stdio.h>
int main() {
int *p; // 声明了指针,但没初始化
*p = 10; // 写入数据
printf("%d\n", *p);
return 0;
}
这段代码看似没问题,编译器也不会报错,但运行起来可能直接段错误(Segmentation Fault)。因为 p 没有指向合法内存。
生活类比:这就像拿到一把钥匙,但你不知道它能开哪扇门,结果乱开,直接触发了警报。
解决方法:要么指向合法的变量,要么先分配内存。
int a = 0;
int *p = &a; // 指向合法地址
*p = 10;
printf("%d\n", *p);
2. 指针和数组:永远绕不开的孪生兄弟
初学者常常迷惑:数组和指针到底是什么关系?
int arr[3] = {1, 2, 3};
int *p = arr;
printf("%d\n", *(p+1)); // 输出 2
数组名在大多数情况下,会被当作指向首元素的指针。所以 arr 就等价于 &arr[0]。
但要注意:数组名本身不是变量,不能被重新赋值,而指针是可以改来改去的。
血泪案例:
int arr[3] = {1, 2, 3};
arr = arr + 1; // ❌ 这行编译直接报错
这就是典型的“数组不是指针”的坑。
3. 指针传参:值传递 vs 引用传递
函数参数如果用指针,可以实现“改变外部变量”的效果:
#include <stdio.h>
void change(int *p) {
*p = 100; // 修改指向的变量
}
int main() {
int a = 10;
change(&a);
printf("%d\n", a); // 输出 100
return 0;
}
如果你忘了传地址:
change(a); // ❌ 编译报错,或者类型不匹配
那就凉凉了。
类比:你想把钥匙交给朋友,让他去你家帮忙换灯泡。如果你只是告诉他“我家在北京”,不给钥匙(地址),他怎么可能进得去?
4. 内存越界:最隐蔽的炸弹
这是初学者最难排查的错误:访问数组边界以外的空间。
int arr[3] = {1, 2, 3};
printf("%d\n", arr[5]); // ❌ 未定义行为
C 语言不会帮你检查越界,你打印出来可能是乱七八糟的数字,也可能直接崩溃。
类比:这就像你家有三个房间,你却硬闯第 5 个,结果进去的是别人家,直接被赶出来。
5. 指针数组:高级但实用的工具
当你搞懂了指针和数组后,就能看懂这种“恐怖写法”:
#include <stdio.h>
int main() {
char *strs[] = {"C语言", "单片机", "指针"};
for(int i=0; i<3; i++) {
printf("%s\n", strs[i]);
}
return 0;
}
strs 是一个数组,里面的每个元素都是一个 char*(字符串指针)。
它非常常见,比如在菜单系统、字符串表里都要用。
类比:这就像一个电话本,每一页写的不是电话号码本身,而是一个“联系人”,你要再通过联系人去拨号。
如何才能不怕指针?
-
先学会“地址”的概念,理解 & 和 *。
-
多画图,把变量、地址、指针画出来,心里才不乱。
-
不要害怕报错,报错是成长的一部分。
-
从野指针到指针数组,这是必经之路。
很多人说“指针是 C 语言的灵魂”,其实更准确的是:指针是你和底层硬件对话的钥匙。只要你能用好它,你写单片机、操作系统、驱动,都会事半功倍。