第一章:内存与地址(基础中的基础)
1.1 计算机内存就像一栋大楼
想象计算机内存是一栋超级大的公寓楼:
内存大楼
┌─────────────────────────┐
│ 房间 0x1000: [ 10 ] │ ← 每个房间有门牌号(地址)
│ 房间 0x1001: [ 20 ] │ ← 房间里住着数据
│ 房间 0x1002: [ 'A' ] │
│ 房间 0x1003: [ 30 ] │
│ ... │
└─────────────────────────┘
- 门牌号 = 内存地址(比如 0x1000)
- 房间里住的人 = 数据(比如数字10)
- 变量 = 给某个房间起的外号(比如
int a = 10;)
1.2 变量是什么?
int a = 10;这行代码做了两件事:
- 在内存中找了一个空房间
- 在房间里放了数字10,并给这个房间起名叫
a
地址 0x1000
┌─────────┐
│ a: 10 │ ← 房间名:a,住户:10
└─────────┘
第二章:指针是什么?
2.1 指针 = 记录门牌号的纸条
int a = 10; // 房间a里住着10
int *p = &a; // 纸条p上写着"房间a的门牌号"分解理解:
int *p:声明一张纸条,用来记录门牌号&a:取地址符,查看房间a的门牌号p = &a:把门牌号写在纸条上
内存示意图:
地址 0x1000 地址 0x2000
┌─────────┐ ┌─────────────┐
│ a: 10 │ │ p: 0x1000 │ ← p记录着a的地址
└─────────┘ └─────────────┘
▲ │
└──────────────────────┘
2.2 指针的通俗理解
| 概念 | 比喻 | 代码 |
|---|---|---|
| 变量 | 房间 | int a; |
| 地址 | 门牌号 | &a |
| 指针 | 写着门牌号的纸条 | int *p = &a; |
| 解引用 | 拿着纸条找到房间 | *p |
第三章:解引用(核心概念)
3.1 什么是解引用?
通俗定义: 解引用就是拿着门牌号找到房间,看/改里面的人
int a = 10; // 房间a: 住着10
int *p = &a; // 纸条p: 写着a的门牌号
*p = 20; // 解引用:找到房间a,把10改成20执行过程分解:
步骤1: p = 0x1000 (门牌号)
步骤2: *p → 去0x1000这个房间
步骤3: 找到房间,看到里面是10
步骤4: 把10改成20
步骤5: 现在 a = 20
3.2 为什么叫”解引用”?
- 引用 = 用门牌号指代房间(只说门牌号,不进去)
- 解引用 = 解除”门牌号”状态,进去实际操作
3.3 实战对比
#include <stdio.h>
int main() {
int a = 10;
int *p = &a;
printf("a = %d\n", a); // 直接看房间 → 10
printf("&a = %p\n", &a); // 看门牌号 → 0x1000
printf("p = %p\n", p); // 纸条内容 → 0x1000
printf("*p = %d\n", *p); // 解引用:找到房间 → 10
*p = 100; // 解引用:找到房间,改值为100
printf("a = %d\n", a); // 现在 a = 100
return 0;
}第四章:结构体与指针
4.1 结构体是什么?
结构体 = 一个套房(多个房间的组合)
typedef struct {
int length; // 子房间1
int size; // 子房间2
int *head; // 子房间3
} Table;内存布局:
Table t = {5, 10, 0x3000};
┌─────────────────────────────┐
│ 套房 t │
│ ┌─────────────────────┐ │
│ │ 房间 length: 5 │ │
│ │ 房间 size: 10 │ │
│ │ 房间 head: 0x3000 │ │
│ └─────────────────────┘ │
└─────────────────────────────┘
4.2 两种访问方式
方式1:直接用变量(. 操作符)
Table t = {5, 10, NULL};
t.length = 100; // 直接访问套房里的房间方式2:用指针(-> 操作符)
Table t = {5, 10, NULL};
Table *p = &t; // 纸条p上写着套房门牌号
p->length = 100; // 通过门牌号找到套房,访问length第五章:. 和 -> 的关系(重点!)
5.1 核心公式
p->length 等价于 (*p).length
5.2 逐步拆解 (*p).length
Table t = {5, 10, NULL};
Table *p = &t;
(*p).length = 100;执行步骤:
步骤1: p = 0x1000 (套房门牌号)
步骤2: *p
→ 解引用:找到0x1000这个套房
→ 得到整个结构体 t
步骤3: (*p).length
→ 在找到的套房里,访问length房间
→ 相当于 t.length
步骤4: (*p).length = 100
→ 把length房间的值改成100
5.3 -> 就是简写
p->length // 写法简洁
(*p).length // 完整写法(容易写错括号)为什么要发明 ->?
因为 (*p).length 太麻烦:
- 括号容易漏掉
.优先级高于*,不加括号会出错- 写起来啰嗦
-> 就是为了解决这个问题而生!
5.4 完整对比表
// 假设有结构体和指针
Table t = {5, 10, NULL};
Table *p = &t;
// 四种等价的访问方式
t.length = 100; // ✅ 直接用变量(用 .)
(*p).length = 100; // ✅ 解引用后用 .(完整写法)
p->length = 100; // ✅ 用 ->(推荐)
(&t)->length = 100; // ✅ 用地址直接访问(不常用)第六章:一张图看懂所有关系
内存大楼
┌─────────────┐
│ 地址 0x1000 │
│ ┌─────────┐│
│ │ t ││ ← 套房
│ │ length:5││
│ │ size:10 ││
│ │ head:---││
│ └─────────┘│
└─────────────┘
▲
│
┌─────┴─────┐
│ │
p = &t│ │t是变量名
│ │
│ ┌────────┴────────┐
│ │ 代码写法 │
│ │ t.length │ ← 直接用变量名
│ │ p->length │ ← 用指针(推荐)
│ │ (*p).length │ ← 用指针(完整)
└──────────────────┘
第七章:实战演练
7.1 基础练习
#include <stdio.h>
typedef struct {
int age;
char name[20];
} Person;
int main() {
// 1. 直接使用
Person p1 = {18, "小明"};
printf("年龄: %d\n", p1.age); // 用 .
// 2. 通过指针使用
Person *ptr = &p1;
ptr->age = 20; // 用 ->,修改年龄
printf("年龄: %d\n", ptr->age); // 用 ->,读取年龄
// 3. 两种方式等价
p1.age = 25; // 直接改
printf("%d\n", ptr->age); // 输出25(因为ptr指向p1)
return 0;
}7.2 你的原始代码改造
#include <stdio.h>
#include <stdlib.h>
#define Size 5
typedef struct Table {
int length;
int size;
int *head;
} table;
// 初始化:返回结构体
table init() {
table t;
t.head = (int*)malloc(Size * sizeof(int));
if (!t.head) {
printf("初始化失败\n");
exit(1);
}
t.length = 0;
t.size = Size;
return t;
}
// 显示:用指针传递(不复制整个结构体)
void display(const table *t) {
// t 是指针,用 ->
for (int i = 0; i < t->length; i++) {
printf("%d ", t->head[i]); // t->length 等价于 (*t).length
}
printf("\n");
}
int main() {
table t = init(); // t 是结构体变量
// 插入数据:t是变量,用 .
for (int i = 1; i <= Size; i++) {
t.head[i-1] = i;
t.length++; // 用 .
}
printf("表中的元素分别是:\n");
display(&t); // 传地址,所以display里用 ->
free(t.head); // 释放内存
return 0;
}第八章:记忆口诀
口诀1:点与箭
变量用点(.),指针用箭(->)
结构体变量用 .,结构体指针用 ->
口诀2:解引用
指针存地址,*来找实际
想去房间看,用 * 来开门
口诀3:等价关系
p->成员 = (*p).成员
箭头是简写,记住别搞混
第九章:常见错误与解决
错误1:忘记解引用
int a = 10;
int *p = &a;
p = 20; // ❌ 错误:把20当地址赋值给p
*p = 20; // ✅ 正确:解引用,修改a的值错误2:用 * 和 . 顺序错误
*p.length // ❌ 错误:先执行 .,再执行 *
(*p).length // ✅ 正确:先解引用,再访问成员
p->length // ✅ 更好:用箭头错误3:空指针解引用
int *p = NULL;
*p = 10; // ❌ 程序崩溃!NULL没有房间第十章:终极总结表
| 概念 | 符号 | 含义 | 类比 |
|---|---|---|---|
| 取地址 | & | 获取变量的地址 | 看门牌号 |
| 指针声明 | * | 声明一个指针变量 | 准备一张纸条 |
| 解引用 | * | 找到地址指向的数据 | 拿着纸条找房间 |
| 成员访问 | . | 访问结构体成员 | 进套房看子房间 |
| 箭头 | -> | 指针访问结构体成员 | 用门牌号进套房看子房间 |
关键等式
p->成员 = (*p).成员
什么时候用哪个?
✅ t.length → t是结构体变量
✅ p->length → p是指向结构体的指针
✅ (*p).length → 同p->length,但更麻烦
❌ p.length → p不是结构体变量
❌ *t.length → t不是指针
最后的话
记住这三句话就够了:
- 指针就是地址,
*就是”沿着地址去找” .用于变量,->用于指针(->自带解引用)p->成员就是(*p).成员,箭头是懒人专用
现在再看这段代码,是不是清楚多了?
void display(const table *t) {
for(int i = 0; i < t->length; i++) {
// t->length = 去t指向的地址找到结构体,再访问length
printf("%d", t->head[i]);
}
}有任何疑问,随时问我!😊