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;

StudentIdint 仍可互换,编译器不会阻止把普通计数误传为学生编号。若需要强类型隔离,应定义包装结构体或类,而不是只用别名。

带标签联合体必须同步两个状态

enum class ValueKind { integer, real };
 
struct Value {
    ValueKind kind;
    Number payload;
};

写 payload 时必须同步 kind,读取前先按 kind 选择活动成员。若标签写着 integer、实际最后激活 real,代码即使布局没变也失去语义安全。这类“两个字段必须一起变化”的约束正是更高层类或 variant 应封装的责任。

先预测对象布局与合法状态

对 Student 列出三个成员同时存在;对 Number 画出共享存储并标记唯一活动成员;对枚举列出所有允许状态和转换;对别名记录它仍与原类型相同。不同类型设施的“组合”与“选择”语义不能混用。

三步选择合适类型设施

分步1 / 3

第一步:判断同时存在还是互斥选择

同一实体的异类字段同时存在用 struct;有限命名状态用 enum;同一存储的互斥表示才考虑 union。

正式节点与章专属证据

  • 结构体:在“Lesson 11:各种类型”中核对输入、状态变化、失败模式和可复现证据;第 1 个节点必须能回到“对象的标签与当前活动数据成员同步,读取方式与最近一次有效写入一致。”。
  • 枚举:在“Lesson 11:各种类型”中核对输入、状态变化、失败模式和可复现证据;第 2 个节点必须能回到“对象的标签与当前活动数据成员同步,读取方式与最近一次有效写入一致。”。
  • 联合体:在“Lesson 11:各种类型”中核对输入、状态变化、失败模式和可复现证据;第 3 个节点必须能回到“对象的标签与当前活动数据成员同步,读取方式与最近一次有效写入一致。”。
  • 类型别名:在“Lesson 11:各种类型”中核对输入、状态变化、失败模式和可复现证据;第 4 个节点必须能回到“对象的标签与当前活动数据成员同步,读取方式与最近一次有效写入一致。”。

先用输入合同检查本页正式节点,再在相同初值下逐步比较正常和失败轨迹,最后只启用“联合体写入一个成员后按另一个成员解释同一存储”完成反例与复位。三个交互都必须能独立重置,且重置后再次满足“对象的标签与当前活动数据成员同步,读取方式与最近一次有效写入一致。”。

输入与状态合同

Lesson 11:各种类型

哪种用户定义类型能让非法状态最难表达,并保留清晰的活动成员证据?

必须先声明

为结构体声明输入类型、有效范围、对象生命周期和失败策略。

可复核证据

保存Lesson 11:各种类型的原始输入、初值与第一条可检查诊断。

正式节点:结构体、枚举、联合体、类型别名

编译与运行轨迹

同一输入下比较正常与失败路径

  1. 01选择能表达状态的类型
  2. 02初始化标签与数据
  3. 03只读取活动成员
  4. 04转换状态时同步更新

不变量:对象的标签与当前活动数据成员同步,读取方式与最近一次有效写入一致。

故障定位与复位

一次只破坏一个前提

基线可继续:对象的标签与当前活动数据成员同步,读取方式与最近一次有效写入一致。

小结

  • struct 组合同时存在的异类成员,数组组织同类序列,两者表达不同数据形状
  • 聚合初始化按成员顺序,结构体复制语义由每个成员类型共同决定
  • enum 为有限状态集合命名,enum class 提供作用域与更强类型边界
  • union 成员共享存储,同一时刻只有一个活动成员;标签必须与实际成员同步
  • using/typedef 只创建别名,不建立新的类型身份;强隔离需要包装类型

练习

  1. 问题 1:选择形状。 学生的编号、姓名、分数为何用 struct,而五次成绩为何适合数组?
  1. 问题 2:审计 union。number.integer=42 后再写 number.real=3.5,此时哪个成员有效?
  1. 问题 3:判断别名。 using StudentId=int 能否阻止把普通 int 计数传给学生编号参数?

名词解释

名词解释

本章出现的专业名词,用大白话再讲一遍。

结构体
把多个可异类成员组合为记录对象的类型。
成员
属于结构体、类或联合体内部的命名子对象或函数。
枚举
用有限命名常量表达状态或分类的类型。
联合体
成员共享同一存储且仅一个活动的类型。
类型别名
已有类型的另一个名字,不创建独立类型身份。

资料与写作方式声明

本章以高桥麻奈 2017 年 C++ 入门教材第 5 版权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…