2、指针与变量:指针的声明与初始化、指针与普通变量的关系、空指针与野指针
好,咱们今天聊聊指针和变量那点事儿。
说实话,指针这东西,刚学的时候我也觉得绕。但后来我发现,只要搞明白它和普通变量的关系,后面就顺了。说白了,指针就是一个“存放地址的变量”。
2.1 指针的声明与初始化
先看声明。指针的声明长这样:
int *p; // p 是一个指向 int 的指针
char *cp; // cp 是一个指向 char 的指针
double *dp; // dp 是一个指向 double 的指针
注意那个星号 *,它告诉编译器:这个变量存的是地址,不是普通数值。
我个人习惯把星号紧挨着变量名写,比如 int *p。这样读起来更自然——「p 是一个指针,指向 int」。当然也有人写成 int* p,语法上没问题,但容易让人误解成 int* 是一种类型。其实 int* p, q 这种写法里,只有 p 是指针,q 是普通 int。你想想看,是不是挺坑的?
初始化就更关键了。指针必须指向一个合法的地址才能用:
int a = 10;
int *p = &a; // 把 a 的地址赋给 p
这里的 & 是取地址运算符。它就像一把钥匙,帮你找到变量在内存里的门牌号。
核心要点:指针存的是地址,不是值。想拿到指针指向的值,用 *p 解引用。
2.2 指针与普通变量的关系
普通变量和指针,其实是一枚硬币的两面。
普通变量直接存值,比如 int a = 10;,a 这个盒子里装的就是 10。指针变量存的是地址,比如 int *p = &a;,p 这个盒子里装的是 a 的门牌号。
它们之间的关系可以用这张图来理解:
你看,p 里存的是 a 的地址。通过 *p 就能拿到 a 的值 10。这就是指针和普通变量最本质的关系——指针是间接层,变量是直接层。
我在项目中遇到过这样一个场景:需要在一个函数里修改多个变量的值。如果传普通变量,函数里改的是副本,外面纹丝不动。传指针就对了,函数里 *p = 新值,外面也跟着变。这就是指针的威力。
小技巧:用 printf("%p", p) 打印指针的值,看看地址长什么样。我当年就是这么把指针搞明白的。
2.3 空指针与野指针
这两个概念,是新手最容易踩的坑。我一个个说。
空指针
空指针就是指向「空」的指针。在 C 里用 NULL 表示,C++11 之后推荐用 nullptr。
int *p = NULL; // C 风格
int *q = nullptr; // C++ 风格
空指针的意义很明确:这个指针当前不指向任何有效对象。它是一种安全状态,你可以判断它是不是空,然后决定要不要用。
我曾经在代码里看到有人这样写:
int *p;
// ... 中间隔了 200 行代码 ...
if (p != NULL) {
*p = 10;
}
嗯,这里有个大问题——p 根本没初始化!它不是空指针,它是野指针。后面会讲。
警告:对空指针解引用会导致程序崩溃。所以用之前一定要检查:if (p != nullptr) { ... }
野指针
野指针才是最可怕的。它指向一个随机的、非法的内存地址。你根本不知道它指向哪。
野指针是怎么产生的?常见三种情况:
- 未初始化:
int *p;然后直接用。p 里是个随机值。 - 指向已释放的内存:
free(p)之后没把 p 置 NULL,继续用。 - 指向栈上变量的地址:函数返回后,局部变量的地址就失效了。
看个例子:
int* getBadPointer() {
int a = 10;
return &a; // 危险!a 是局部变量,函数结束就销毁了
}
int main() {
int *p = getBadPointer();
*p = 20; // 野指针!访问了已释放的内存
return 0;
}
我曾经在维护一个老项目时,遇到一个间歇性崩溃的 bug。查了三天,最后发现是有人把局部变量的地址返回了。那个地址在函数返回后被系统回收,但调用方还在用。崩溃时间完全随机,特别难复现。
从那以后,我给自己定了个规矩:指针要么指向合法对象,要么就是 nullptr。没有中间状态。
避坑指南:
- 声明指针时立即初始化,不知道指向哪就赋 nullptr
- 释放内存后,立即把指针置 nullptr
- 不要返回局部变量的地址
- 解引用前,先判断是不是 nullptr
2.4 总结一下
指针和普通变量的关系,说白了就是「间接」和「直接」的区别。指针存地址,变量存值。通过 & 取地址,通过 * 解引用。
空指针是安全的「空」状态,野指针是危险的「随机」状态。写代码时,脑子里要时刻清楚:这个指针指向哪?它合法吗?
嗯,这一节就到这。记住我说的,指针这东西,多练练就熟了。
个人建议:刚开始学的时候,每写一个指针,就在纸上画一下内存图。变量在哪,指针指向哪,画清楚了,代码就不会错。