链表(一):结构体与联合
看完你能亲手画出 struct 内存布局并讲透 union 共用原理
按 空格/→ 演示下一步
全部页面点击任意一页,跳回舞台从这页播放
链表(一):结构体与联合
看完你能亲手画出 struct 内存布局并讲透 union 共用原理
为什么需要结构体
假设你是班长,老师递给你一张表:全班姓名、学号、年龄、高考总分各一栏。四个数组分开存?插入转班同学时怎么保证四份数据对齐?聚合需求就从这里冒出来了。
卡上姓名籍贯绑定一人不可拆,结构体把同实体的多字段捆成一个变量
结构体的定义与声明
struct 三种定义写法 + typedef 别名写法同台对比,标签命名一目了然。
Student、Point 只是类型标签,不是变量;形式二、三只能在定义处声明变量;typedef 把 struct Node 简写成 Node,是链表代码里最常用的形态。
结构体变量的创建与初始化
上页画好了'学生档案'的空白模板,但模板不会自动装数据。要存小明的各科成绩,得真正'建档'——创建结构体变量,再用初始值把字段填上。
户型图对应定义、房子对应变量、点号对应拿钥匙开某一间房
结构体变量的使用示例
看一段完整示例:顺序初始化、整体赋值、. 与 ->、嵌套成员、复合字面量五种典型操作。
演示结构体可整体赋值(数组不行)、. 与 -> 的等价关系、嵌套成员的链式访问,以及 C99 复合字面量。
结构体的内存布局
内存对齐规则与 sizeof 计算边界情况
为什么需要结构数组
上一页我们能用一个结构体装下「学号+姓名+成绩」——但一个班有 50 个学生,难道要手写 50 个变量吗?这正是结构数组要解决的问题。
座位号对应下标,每人占一格连续排开
结构数组的定义与初始化
三种常见写法:先声明再赋值、按位批量初始化、指定下标初始化。
外层 { } 装数组的 N 个元素,内层 { } 装该元素的成员——嵌套两层大括号是结构数组初始化的标志。
结构数组的内存布局
横向箭头表示内存从低到高,每格是一个结构体,字段紧凑排列
结构数组的遍历与操作
用三种写法遍历同一个 Student 数组——下标、指针算术、指针自增,输出完全一致。
三段循环打印同一片内存:arr[i] 与 *(arr+i) 同义;p++ 的真实步长是 sizeof(Student) 字节——这就是「指针算术」比裸 +1 聪明的地方。
结构指针的概念与意义
结构体变量作为实参传递时,整个结构会被复制一份。大结构体复制既费内存又费时间,而且函数内改不到外面。这些问题,正好由指针来解决。
房卡只是张小卡片,不复制房间,却能定位并改到原房间
结构体指针的定义与解引用
对比 . 与 -> 两种访问方式,看清取地址与解引用在结构体上怎么工作。
L12 用 & 取 s 的地址赋给结构体指针 p;L14 的 p->name 是语法糖,等价于 L18 的 (*p).name;L20 是 . 高于 * 的优先级陷阱;L23 证明指针能改原变量成员。
结构体成员的指针访问
前一页拿到指向结构体的指针 p,紧接着的问题是:怎么拿到结构体里的成员?两种写法结果完全一样,但写法上有个让人栽跟头的优先级陷阱。
先走进房间(*p)再找物品(.member),等价于前台按房号直接取物(p->member)
结构指针的内存示意图
左边的盒子是指针变量 p(装着地址);右边是被指向的结构体对象,在堆上是一段连续内存。
结构体作为函数参数的问题
值传递的复制开销与边界场景分析
结构体值传递示例
演示把结构体按值传入函数时,函数内修改的是副本,原变量不受影响。
形参按值接收,函数内修改的是栈上的副本;这是结构体作为参数时的默认行为,也是上一节"参数问题"的根因。
结构体指针作为函数参数
上页看到值传递有两个痛点:拷贝整块结构浪费空间,函数内改不动原数据。这页看指针参数怎么一次解决——但它也带来新的注意事项。
房卡小、指向房间,能进房改东西;指针小、指向结构体,能改原数据
结构体指针传递示例
三个函数演示 in / out / in-out 三种契约:malloc 产出、const 只读、裸指针就地改写。
L11 是 out 契约(malloc 产出新节点指针);L19–L21 是 in 契约(const 指针只读,写会报错);L26 是 in/out 契约(裸指针允许改原节点)。三种签名对应副作用的三档边界。
结构体作为函数返回值
上一页把结构体『送进去』时——值传递是副本、指针传递共享原对象。今天反过来:函数把结构体『算出来』再『送出来』,调用方拿到的是副本还是地址?
原版随办公室销毁(局部变量),带回家的是独立副本(调用方栈帧)
联合体的引入
前面用结构体把多个相关字段合成一个对象,返回时每个成员各占一处、互不干扰。若同一时刻只需保留其中一种结果,能否让这些成员共用一份空间?这就引出联合体。
同一格口对应共同地址,换牌子对应改用不同成员解释
联合体的定义与使用
以 IP 地址为例,看联合体如何让同一段内存被解读为两种不同形态。
union 定义 IPAddress,addr 与 bytes 共享 4 字节内存;用 . 访问成员,-> 用于指针;sizeof 等于最大成员。
联合体的内存布局
左右对比:结构体成员错开铺,联合体成员全部从偏移 0 重合,总大小天差地别。
结构体 vs 联合体
语法像、内存语义却截然不同:struct 让所有成员同时存活,union 让所有成员共用同一块内存——这是初学者最常踩的概念混淆。
- 所有成员同时有效,互不干扰
- 总大小 = 各成员大小相加(含对齐)
- 适合组织多个独立字段并存
- 所有成员共享同一块内存
- 总大小 = 最大成员的大小
- 适合同一变量的多形态切换
本章要点
- ✓结构体本质是固定布局打包异构字段
- ✓大结构体传参用指针,避免整块内存拷贝
- ✓结构体与联合体内存模型相反:并存vs互斥
- ✓内存对齐与填充让sizeof可能大于成员之和
- ✓p->x等价于(*p).x,前提是p合法
课后思考
先别翻参考答案,给每题留一两分钟认真想——再对照下一段提示看自己想到哪一层。
参考答案匿名结构体只能在定义时直接定义变量,标准 C 里无法再声明新实例。没有类型名就没法写函数参数——这是 C 类型系统的硬约束。
参考答案理论上能压到 32 bit,但编译器对齐可能让它和一个 int 一样大。少见是因为可移植性差、字节访问慢、调试不直观、IPv4 也正被 IPv6 替代。
参考答案能,这就是静态链表,用 next[] 数组存下一个节点的下标。但下标空间有限、插入删除要维护空闲池——指针让节点能真正'动态生长'在堆上。