🧠 第一章:物理模型——“内存是一个巨大的数组”
将内存想象成一个无限延伸的字节序列。每个字节都有一个唯一的、连续的编号(地址)。
- 变量名:是人类给某个编号起的“别名”。
- 指针变量:是一个专门用来存储“编号”的容器。
- 指针的类型:是告诉 CPU “从这个编号开始,往后看几格,用什么姿势看”。
核心原子观点:指针的值(地址)决定了从哪开始;指针的类型决定了在哪结束以及如何解释。
符号学:& 与 * 的能量守恒
指针的操作只有两个方向,它们互为逆运算:
- 取址 (
&):向上抽取。把一个具体的变量降维成它的地理坐标(地址)。 - 解引用 (
*):向下钻取。沿着坐标潜入内存,去操作那个位置的本体。
- 公式:
*(&a) == a。这像是一场折返跑,先拿到地址,再顺着地址找回去。
📏 第三章:指针算术——“跨步逻辑”
指针的加减法不是简单的数学加减,而是步长移动。
设指针 p 的地址值为 ,类型大小为 ,则:
| 类型 | 步长 () | 执行 p + 1 的后果 |
|---|---|---|
char* | 1 字节 | 移动到下一个字节(处理字符串) |
int* | 4 字节 | 移动到下一个整数(跳过 4 个字节) |
double* | 8 字节 | 移动到下一个双精度浮点数 |
void* | 未知 | 非法操作(编译器不知道该跨多大步) |
📦 第四章:数组与指针的“暧昧关系”
这是 C 语言最伟大的设计,也是最大的混乱来源:数组名在大多数情况下会“退化(Decay)”为指向首元素的指针。
- 等价公式:
a[i] \equiv *(a + i) - 区别:
数组名是常量指针,它不能被重新赋值(你不能搬走整栋房子)。指针变量是游标,它可以自由指向任何地方(你可以拿着门牌号到处跑)。
🔐 第五章:const 的位置陷阱
这是面试和工程中最容易翻车的地方。记住**“左定物,右定指”**法则(以 * 为界):
| 声明 | 谁被固定了? | 描述 |
|---|---|---|
const char *p | 内容 | 你不能通过 p 修改它指向的东西(内容是只读的)。 |
char * const p | 地址 | p 只能指向这个地址,不能再指向别处(指针是只读的)。 |
const char * const p | 全部 | 既不能改指向,也不能改内容。 |
🎭 第六章:多级指针——“套娃逻辑”
int **pp 并不是什么神秘的东西,它只是存储“指针变量地址”的变量。
pp:指向指针的地址。*pp:拿到第一级指针的值(即某个int的地址)。**pp:拿到最终的int本体。
应用场景:当你需要在函数内部修改函数外部的“指针指向”时,你必须传递“指针的地址”(二级指针)。
⚠️ 第七章:三大禁忌(原子级避坑)
- 野指针 (Wild Pointer):
- 声明了
char *p;却没给初值。它指向的是宇宙中的随机角落。永远在声明时给个NULL。
- 悬空指针 (Dangling Pointer):
- 指针指向的内存已经被释放(
free)了,但指针还留着那个地址。释放后立即置NULL。
- 栈内存溢出:
- 指针指向了一个函数内的局部变量,函数返回后,那个变量的“遗体”还在,但灵魂(合法性)已经没了。
🛠️ 进阶:函数指针
指针甚至可以指向代码段的起始位置:
int (*func_ptr)(int, int);
这让 C 语言具备了多态的萌芽——你可以把“逻辑”当作参数传来传去。
💡 总结感悟
指针不是为了增加难度而存在的,它是为了效率:
- 传递 10GB 的文件?传地址(8 字节)。
- 动态构造复杂的数据结构(树、链表)?靠指针连接。
- 直接操控硬件寄存器?把物理地址赋给指针。