指针的本质:指针变量与地址、指针的算术运算、空指针与野指针
大家好,我是你们的老朋友。今天咱们来聊聊指针的本质。说实话,指针这东西,我当年学的时候也头疼过一阵子。但后来我发现,只要把几个核心概念吃透了,指针其实没那么可怕。
指针是什么?说白了,它就是用来存放地址的变量。你想想看,我们平时用的 int a = 10,a 里面存的是数值 10。而指针变量 p 里面存的是什么呢?是另一个变量的地址。就这么简单。
指针变量与地址
每个变量在内存中都有个“门牌号”,这个门牌号就是地址。指针变量就是专门用来记录这个门牌号的。
int a = 42;
int *p = &a; // p 里面存的是 a 的地址
这里有个细节要注意:& 是取地址运算符,* 是解引用运算符。我个人习惯把 int *p 读作“p 是一个指向 int 的指针”。
核心理解:指针变量本身也有自己的地址!它也是变量,只是它存的是别人的地址。
我在项目中遇到过不少新手,以为 p 就是地址,*p 就是值。其实 p 是变量名,它里面存的是地址,*p 才是通过这个地址找到的那个值。
指针的算术运算
指针能做加减运算,但这里的加减和普通整数不一样。你想想看,如果 p 指向 int(4字节),那么 p+1 不是地址加1,而是加4个字节。
int arr[] = {10, 20, 30, 40};
int *p = arr; // p 指向 arr[0]
printf("%d\n", *p); // 输出 10
printf("%d\n", *(p+1)); // 输出 20,地址偏移了 4 个字节
为什么会这样?因为指针的加减是以“它所指向的类型大小”为单位的。说白了,p+1 就是跳到下一个同类型的元素。
| 指针类型 | p+1 实际偏移字节数 |
|---|---|
| char* | 1 字节 |
| int* | 4 字节 |
| double* | 8 字节 |
小技巧:遍历数组时,用 p++ 比 p+1 更常见。但要注意,p++ 会改变 p 本身的值,而 p+1 不会。
嗯,这里要注意:指针减法也是类似的。两个指针相减,得到的是它们之间相差的元素个数,不是字节数。
int *p1 = &arr[0];
int *p2 = &arr[3];
ptrdiff_t diff = p2 - p1; // diff 等于 3,不是 12
空指针与野指针
这两个概念,我当年栽过不少跟头。先说说空指针。
空指针就是指向“空”的指针,通常用 NULL 或 nullptr(C++11以后)表示。它表示这个指针当前不指向任何有效对象。
int *p = NULL;
if (p != NULL) {
*p = 42; // 安全:只有 p 非空时才解引用
}
我曾经在项目中见过有人直接 *p = 42 而不检查 p 是否为空。结果程序跑着跑着就崩了,查了半天才发现是空指针解引用。嗯,从那以后我养成了习惯:解引用前一定检查。
警告:空指针解引用是未定义行为!程序可能崩溃,也可能静默地破坏数据。千万别赌。
再说野指针。野指针比空指针更危险。它指向的内存地址是随机的、不确定的。说白了,就是指针变量里存了一个垃圾值。
野指针怎么来的?常见三种情况:
- 指针变量未初始化
- 指针指向的内存被释放了(悬空指针)
- 指针越界访问
int *p; // 未初始化,p 是野指针
*p = 10; // 危险!不知道写到哪里去了
int *q = (int*)malloc(sizeof(int));
free(q);
*q = 20; // 危险!q 指向的内存已经释放了
避坑指南:我曾经在维护一个老项目时,发现一个函数里有个指针没初始化就用了。那个 bug 时隐时现,调试了整整两天。最后发现是野指针在作祟。从那以后,我写指针必初始化,释放后必置 NULL。
怎么避免野指针?我的建议是:
- 声明指针时立即初始化,要么指向有效对象,要么赋 NULL
- free 或 delete 后,立即将指针置为 NULL
- 不要返回局部变量的地址
- 使用智能指针(C++中)
下面这张图,是我自己总结的指针核心知识体系,希望能帮你理清思路。
最后说一句:指针本身并不可怕,可怕的是对它的无知。你只要记住——指针就是存地址的变量,加减运算以类型大小为单位,空指针要检查,野指针要避免。把这三点吃透了,指针这块你就拿下了。
个人经验:我每次写指针代码,都会在心里默念一遍:“这个指针指向哪里?它指向的内存还活着吗?我有没有越界?” 养成这个习惯,能帮你省下大量调试时间。
可可精品资料库