自定义数据类型

官方信息技术老师·12 页·深入(追求细节与边界)·0 次浏览·3 天前
typedef结构体内存对齐联合体

自定义数据类型

看清 typedef 真相、结构体对齐逻辑、位域陷阱与联合体的内存复用边界

按 空格/→ 演示下一步

1 / 12 页

全部页面点击任意一页,跳回舞台从这页播放

typedef结构体内存对齐联合体

自定义数据类型

看清 typedef 真相、结构体对齐逻辑、位域陷阱与联合体的内存复用边界

1第 1 页 · 自定义数据类型

声明结构类型以及定义结构

前面我们看到 C 允许自造数据类型,最常用的就是 struct。但很多初学者分不清:struct Student {...}; 只是描述新数据长什么样;struct Student s1; 才真正分配内存。

结构类型声明
用 struct 关键字描述新类型,只规定字段布局,不占内存
结构变量定义
用声明好的类型创建变量,编译器此时才分配存储空间
三种定义语法
先声明后定义、声明同时顺便定义、匿名类型一次性定义
初始化与成员访问
用 {} 按顺序赋初值;变量用 .、指针用 -> 读成员
建筑图纸对应 →结构类型与变量

图纸只描述房子长什么样不占地;按图盖出的房子才真正占地、可住人

2第 2 页 · 声明结构类型以及定义结构

结构变量赋值及引用

假设你写好了 Student 类型,并定义了 s1、s2 两个学生变量。现在有三个问题:能不能直接把 s2 抄给 s1?怎么读出 s1 的某个字段?函数拿到学生指针时又怎么取字段?

整体赋值
同类型结构体变量可直接用 = 赋值,逐成员复制,互不影响
点运算符 .
结构体变量访问成员:变量名.成员名
箭头运算符 ->
结构体指针访问成员:指针名->成员名,等价于 (*指针名).成员名
嵌套访问
成员本身是结构体时,可连续用 . 或 -> 逐层深入
复印一份文件对应 →结构体整体赋值

原件和复印件内容相同,改任何一边都不会影响另一边

p->x(*p).x\texttt{p->x} \equiv (\texttt{*p}).\texttt{x}
3第 3 页 · 结构变量赋值及引用

结构体嵌套和结构体数组

前面我们掌握了单个结构变量的赋值和引用。但现实中信息往往是层次化的——学生有出生日期、地址有省市区。怎么处理这种'套着套'或'排成队'的情况?

结构体嵌套
成员可以是已定义的另一个结构体类型
结构体数组
同类型结构体元素的有序集合
初始化方式
嵌套按从外到内逐层赋初值,数组用花括号批量赋
成员访问路径
嵌套用点运算符逐层进入,每跨一层多一个点
典型应用
学籍档案、通讯录、订单列表等批量且层次化的数据
班级花名册的行与格对应 →结构体数组与嵌套

一行对应一个数组元素;格中的省/市/县对应嵌套的层

4第 4 页 · 结构体嵌套和结构体数组

结构体指针

上页讲的结构体数组大小在编译期就固定了。运行时要动态创建结构体、或者把结构体高效传给函数时,就要靠结构体指针——它存的是地址,不是结构体本身。

声明语法
struct 类型名 *指针名; 只存地址,不存结构体本身
两种访问方式
(*p).成员 等价于 p->成员,箭头是 C 设计的简写
优先级陷阱
*p.成员 是错的,. 比 * 优先,要写 (*p).成员
使用动机
动态分配、函数传参避免整结构体拷贝、构建链表
指针运算步长
p+1 跳过整个 sizeof(结构体) 字节,按结构体大小跳
房间门牌卡对应 →结构体指针

门牌卡只记房间地址,按地址找到房间就能进入各成员

pm(p).mp\to m\equiv(*p).m
5第 5 页 · 结构体指针

结构体与函数

上页说到结构体指针:传指针比传整个结构体轻量得多。这页专门讲结构体和函数打交道时的几种方式——参数怎么传、返回值怎么处理、以及怎么约束函数不乱改数据。

值传递:整体拷贝
把结构体变量作为参数传入函数时,整个结构体会被复制一份;函数内修改只影响副本,不影响原数据。
指针传递:传地址
传 struct S* 只传一个地址(8 字节);函数通过指针直接读写原结构体,效率远高于值传递。
const 标记只读
形参写 const struct S* 表示承诺不修改该结构体;调用方一眼能分辨查询还是修改函数。
结构体作返回值
函数可以返回结构体值;编译器会隐式复制整个结构体,性能敏感时建议返回指针。
借书:复印整本 vs 只给编号对应 →结构体传参方式

值传递像复印整本书借走,副本改坏不影响原书;指针传递像只告知书架编号让你自取,可直接动原书。

值传递开销=sizeof(S)指针传递开销=8\text{值传递开销} = \text{sizeof}(S) \gg \text{指针传递开销} = 8
6第 6 页 · 结构体与函数

共用体类型

结构体里每个成员都有自己的房间,互不打扰。共用体反过来——所有成员挤在同一间房里,谁进来谁就把它占满。听起来奇怪,但它解决的是另一类问题。

共用同一段内存
所有成员起始地址相同,共享同一段存储,后写入会覆盖之前的值
同时只能存一个值
任一时刻只有一个成员的值有意义,访问其他成员结果无定义
大小取最大成员
sizeof(union) 等于最大成员的长度,至少能容纳任一成员
与结构体的关键区别
结构体各成员内存独立、总大小求和;union 是重叠、取最大
典型应用场景
节省内存的变体记录、协议字段复用、类型双关读写
合租房对应 →共用体成员

几个人合租一套房,客厅同一时刻只能被一个人用;union 同一时刻只能让一个成员的值有意义

sizeof(U)=max{sizeof(mi)}\text{sizeof}(U) = \max\{\text{sizeof}(m_i)\}
7第 7 页 · 共用体类型

枚举类型

上页学了共用体——成员共享内存。还有一类问题:「变量的取值只能是有限个固定选项」,比如一周只有 7 天、性别只有 3 种。这种就该用枚举。

定义语法
enum 标签名 { 成员1, 成员2, ... };,成员名是合法标识符
整型本质
成员本质是 int 常量,默认从 0 开始依次递增
显式赋值
可指定某成员的值,后续成员从这个值继续递增
典型用途
替代 #define 常量和魔术数字;switch 分支首选;可读性更好
一周七天对应 →枚举类型

变量只能在周一到周日里选,每个名字背后对应 0~6 的数字

8第 8 页 · 枚举类型

二级C考点解析之结构体定

上一页说到,基本类型装不下一个人、一本书的全部信息,所以才需要自定义类型。这一页看第一个、也是最常用的工具——结构体,看它怎么把多个不同类型的数据绑成一个整体。

结构体类型声明
用 struct 关键字声明一种新类型,只画图纸不占内存
三种定义变量方式
先声明后定义、声明同时定义、匿名类型直接定义
typedef 简化写法
用 typedef 给 struct Student 起个短名字 Student
初始化按声明顺序
按成员声明顺序依次赋值,C99 起支持指定成员
内存对齐与 sizeof
成员按自身大小对齐,sizeof 为最大对齐值的整数倍
个人体检表对应 →结构体

体检表把姓名、身高、体重等栏目聚合成一份完整记录;结构体把多个不同类型的成员聚合成一个整体

sizeof(struct)sizeof(成员),且为max(alignof)的整数倍\text{sizeof}(\text{struct}) \geq \sum \text{sizeof}(成员),且为 \max(\text{alignof}) 的整数倍
9第 9 页 · 二级C考点解析之结构体定

二级C考点解析之结构体应

前面我们把结构体的零件全拆过一遍——定义、赋值、嵌套、指针、传参。现在要装回考卷了,应用层的考点其实就三类:怎么造、怎么访问、整体怎么搬。

三种定义形式
先声明类型再定义变量、声明同时定义、匿名类型直接定义变量
初始化按序赋值
按成员顺序依次给初值,未指定的成员自动为零或空指针
成员访问.与->
结构体变量用.,结构体指针用->;两者优先级最高
整体可赋值
同类型结构体变量可直接赋值,数组不能;赋值按成员逐个拷贝
sizeof与字节对齐
sizeof算总字节数常考;存在字节对齐,实际大小可能大于成员之和
填一张多栏登记表对应 →结构体的整体应用

姓名、年龄、电话是不同栏目,按栏目名取值,整张表可直接复印

10第 10 页 · 二级C考点解析之结构体应

本节要点

  • 成员顺序与对齐规则共同决定结构体实际大小
  • `.`与`->`只是写法区别,底层访问同一片内存
  • 共用体所有成员共享同一段存储,同一时刻仅一个值有效
  • 枚举常量在编译期被替换为整型数字
  • 结构体作为函数参数优先传指针,避免整块内存复制
延伸主题:typedef:为自定义类型起别名位域:在结构体中按位划分成员动态内存:malloc/free 管理结构体生命周期
11第 11 页 · 本节要点

课后思考

先合上书自己想一分钟,再点开答案对一对思路。

1如果让你重新设计 C 语言,还会用 struct 把不同类型的数据捆在一起吗?为什么?

参考答案值得。结构体解决的是『逻辑上属于同一实体但类型不同的数据如何组织』的问题,没有它就只能用平行数组,索引极易出错。

2在 8KB 内存的单片机上存 1000 条学生记录,结构体数组和动态分配哪个更合适?边界在哪?

参考答案看每条记录多大。若单条≤8字节,数组可行;超过就要考虑动态分配或压缩字段,避免栈溢出和内存浪费。

3共用体所有成员共用同一段内存,它和结构体在本质上是不是同一种东西?区别在哪?

参考答案不是。结构体各成员占独立内存,地址叠加;共用体所有成员起始地址相同,大小由最大成员决定,本质是『内存复用』。一个是组合,一个是覆盖。

12第 12 页 · 课后思考