Lesson 11:各种类型
比较结构体、枚举、联合体和类型别名的状态空间与合法读取规则。
学习目标
- 能解释结构体、枚举、联合体和类型别名分别解决哪一种数据建模问题
- 能实现聚合初始化、成员访问和枚举状态转换,保证每个对象处于合法状态
- 能分析联合体活动成员与别名边界,发现错误读取、未初始化字段和“别名等于新类型”的误解
来源、版次与标准边界
“Lesson 11:各种类型”以 SB Creative 出版社书页核定高桥麻奈著、第 5 版、2017 年 6 月 14 日发行、ISBN 978-4-7973-9259-3;出版社页面标 596 页。CiNii Books 书目与 16 课目录记录正文 xxiii+571 页,日本国会图书馆书目记录 571 页。页数口径存在前置页差异,因此本课程不拿页码充当目录证据。
“Lesson 11:各种类型”的中文说明、示例、交互、练习和答案均为独立教学重写。公开资料只确认 16 个 Lesson 标题;本页列出的细分概念是课程教学映射,不冒充原书逐级小节。技术规则参考 ISO C++ 标准入口复核,但 2017 年教材的 Visual Studio 2017 语境不会被静默升级为 C++23;现代写法只能明确标为迁移说明。
围绕“哪种用户定义类型能让非法状态最难表达,并保留清晰的活动成员证据?”,本页要求保留“类型选择理由、对象布局、标签、活动成员、写入读取序列和非法状态反例。”。若故障“联合体写入一个成员后按另一个成员解释同一存储”无法在同一输入下制造首个分岔,应拒绝当前解释,而不是追加随机样例。
为什么一种内置类型不够表达一条记录
一个学生同时有编号、姓名和分数,三个值必须作为一个整体传递和返回。把它们保存在互不相关的变量中,会让调用者依赖位置和命名约定;结构体把共同描述一个实体的字段放入同一类型。
↡把若干可具有不同类型的成员组合为一个记录对象的用户定义类型。#include <string>
struct Student {
int id;
std::string name;
double score;
};
Student learner{1001, "Mina", 92.5};数组表达“多个同类元素”,结构体表达“一个实体的多个异类属性”。建模时先问数据是重复序列还是命名记录,不要为了方便下标把编号、分数等强塞进同类型数组。
成员访问保留对象边界
对象使用 . 访问成员,指向结构体的指针使用 ->。每个成员有独立类型和值,但生命周期通常随整个结构体对象开始与结束。
learner.score = 95.0;
Student* selected = &learner;
selected->name = "Mana";聚合初始化按成员声明顺序对应,顺序改变会影响调用代码。随着字段增多,应提供清楚的构造接口或使用指定初始化能力,避免调用点出现一串难以辨认的同类型值。
复制结构体会复制各成员状态
Student copy = learner;
copy.score = 60.0;对这份结构体,copy 是独立对象,修改分数不会改变 learner。成员若包含指针,复制地址并不复制目标对象,两个记录可能成为别名;因此“结构体复制”具体语义由成员类型共同决定。
传递较大结构体时可用 const Student& 只读借用,返回普通值通常仍清楚安全。不要为了避免复制返回局部结构体引用,它会在函数结束后悬空。
枚举把有限状态命名
许多状态只能从固定集合中选择,例如连接为 disconnected、connecting、connected。枚举把集合成员变成类型内的命名值,比散落的 0、1、2 更可读。
↡定义一组有限命名常量来表达状态或分类的用户定义类型。enum class ConnectionState {
disconnected,
connecting,
connected,
};
ConnectionState state = ConnectionState::disconnected;enum class 的枚举名位于自身作用域,不会隐式当作普通整数使用,能减少名字冲突和意外运算。若需要持久化或协议传输,应显式映射到稳定编码,不依赖编译器内部表示。
switch 应覆盖每个枚举状态
const char* label(ConnectionState state) {
switch (state) {
case ConnectionState::disconnected: return "offline";
case ConnectionState::connecting: return "connecting";
case ConnectionState::connected: return "online";
}
return "invalid";
}开启警告后,不写 default 有时能让编译器提醒新增枚举项未覆盖;边界输入来自反序列化时,又需要显式拒绝非法底层值。覆盖策略取决于值是否只在受控程序内部产生。
联合体让成员共享同一段存储
联合体声明多个可选成员,但同一时刻只有一个活动成员。它适合底层布局或带标签的变体实现,不适合把几个独立字段压缩进一个对象。
↡多个成员共享同一存储、任一时刻只有一个活动成员的用户定义类型。union Number {
int integer;
double real;
};
Number number{};
number.integer = 42; // integer 成为活动成员写入 real 后再读取 integer 通常不是“查看同一值的另一种类型”,而是违反活动成员规则或依赖不可移植细节。若必须知道当前有效项,需要额外标签与联合体同步维护;现代 C++ 的 std::variant 能封装这份标签和访问检查。
类型别名改善领域语言
using 或传统 typedef 可为已有类型提供另一个名字。别名能缩短复杂声明、强调单位或角色,但不会创建与原类型不兼容的新类型。
using StudentId = int;
using Score = double;
StudentId id = 1001;
Score score = 92.5;StudentId 与 int 仍可互换,编译器不会阻止把普通计数误传为学生编号。若需要强类型隔离,应定义包装结构体或类,而不是只用别名。
带标签联合体必须同步两个状态
enum class ValueKind { integer, real };
struct Value {
ValueKind kind;
Number payload;
};写 payload 时必须同步 kind,读取前先按 kind 选择活动成员。若标签写着 integer、实际最后激活 real,代码即使布局没变也失去语义安全。这类“两个字段必须一起变化”的约束正是更高层类或 variant 应封装的责任。
先预测对象布局与合法状态
对 Student 列出三个成员同时存在;对 Number 画出共享存储并标记唯一活动成员;对枚举列出所有允许状态和转换;对别名记录它仍与原类型相同。不同类型设施的“组合”与“选择”语义不能混用。
三步选择合适类型设施
第一步:判断同时存在还是互斥选择
同一实体的异类字段同时存在用 struct;有限命名状态用 enum;同一存储的互斥表示才考虑 union。
正式节点与章专属证据
- 结构体:在“Lesson 11:各种类型”中核对输入、状态变化、失败模式和可复现证据;第 1 个节点必须能回到“对象的标签与当前活动数据成员同步,读取方式与最近一次有效写入一致。”。
- 枚举:在“Lesson 11:各种类型”中核对输入、状态变化、失败模式和可复现证据;第 2 个节点必须能回到“对象的标签与当前活动数据成员同步,读取方式与最近一次有效写入一致。”。
- 联合体:在“Lesson 11:各种类型”中核对输入、状态变化、失败模式和可复现证据;第 3 个节点必须能回到“对象的标签与当前活动数据成员同步,读取方式与最近一次有效写入一致。”。
- 类型别名:在“Lesson 11:各种类型”中核对输入、状态变化、失败模式和可复现证据;第 4 个节点必须能回到“对象的标签与当前活动数据成员同步,读取方式与最近一次有效写入一致。”。
先用输入合同检查本页正式节点,再在相同初值下逐步比较正常和失败轨迹,最后只启用“联合体写入一个成员后按另一个成员解释同一存储”完成反例与复位。三个交互都必须能独立重置,且重置后再次满足“对象的标签与当前活动数据成员同步,读取方式与最近一次有效写入一致。”。
输入与状态合同
Lesson 11:各种类型
哪种用户定义类型能让非法状态最难表达,并保留清晰的活动成员证据?
必须先声明
为结构体声明输入类型、有效范围、对象生命周期和失败策略。
可复核证据
保存Lesson 11:各种类型的原始输入、初值与第一条可检查诊断。
正式节点:结构体、枚举、联合体、类型别名
编译与运行轨迹
同一输入下比较正常与失败路径
- 01选择能表达状态的类型
- 02初始化标签与数据
- 03只读取活动成员
- 04转换状态时同步更新
不变量:对象的标签与当前活动数据成员同步,读取方式与最近一次有效写入一致。
故障定位与复位
一次只破坏一个前提
小结
- struct 组合同时存在的异类成员,数组组织同类序列,两者表达不同数据形状
- 聚合初始化按成员顺序,结构体复制语义由每个成员类型共同决定
- enum 为有限状态集合命名,enum class 提供作用域与更强类型边界
- union 成员共享存储,同一时刻只有一个活动成员;标签必须与实际成员同步
- using/typedef 只创建别名,不建立新的类型身份;强隔离需要包装类型
练习
- 问题 1:选择形状。 学生的编号、姓名、分数为何用 struct,而五次成绩为何适合数组?
- 问题 2:审计 union。 写
number.integer=42后再写number.real=3.5,此时哪个成员有效?
- 问题 3:判断别名。
using StudentId=int能否阻止把普通 int 计数传给学生编号参数?
名词解释
名词解释
本章出现的专业名词,用大白话再讲一遍。
- 结构体
- 把多个可异类成员组合为记录对象的类型。
- 成员
- 属于结构体、类或联合体内部的命名子对象或函数。
- 枚举
- 用有限命名常量表达状态或分类的类型。
- 联合体
- 成员共享同一存储且仅一个活动的类型。
- 类型别名
- 已有类型的另一个名字,不创建独立类型身份。