第一章:内存与地址(基础中的基础)

1.1 计算机内存就像一栋大楼

想象计算机内存是一栋超级大的公寓楼

内存大楼
┌─────────────────────────┐
│ 房间 0x1000: [ 10 ]     │ ← 每个房间有门牌号(地址)
│ 房间 0x1001: [ 20 ]     │ ← 房间里住着数据
│ 房间 0x1002: [ 'A' ]    │
│ 房间 0x1003: [ 30 ]     │
│ ...                     │
└─────────────────────────┘
  • 门牌号 = 内存地址(比如 0x1000)
  • 房间里住的人 = 数据(比如数字10)
  • 变量 = 给某个房间起的外号(比如 int a = 10;

1.2 变量是什么?

int a = 10;

这行代码做了两件事:

  1. 在内存中找了一个空房间
  2. 在房间里放了数字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不是指针

最后的话

记住这三句话就够了:

  1. 指针就是地址* 就是”沿着地址去找”
  2. . 用于变量-> 用于指针-> 自带解引用)
  3. p->成员 就是 (*p).成员,箭头是懒人专用

现在再看这段代码,是不是清楚多了?

void display(const table *t) {
    for(int i = 0; i < t->length; i++) {
        // t->length  =  去t指向的地址找到结构体,再访问length
        printf("%d", t->head[i]);
    }
}

有任何疑问,随时问我!😊