Chapter 4:Compound Types

对齐第6版 Chapter 4:掌握数组、C 字符串与 string 类、结构体/联合体/枚举、指针算术、自由存储区及 new/delete 的边界与所有权。

学习目标

  • 能比较数组、字符串、结构体、联合体和枚举的数据形状,选择能直接表达不变量的类型
  • 能绘制指针、数组首地址、长度、动态对象和所有者之间的关系,并验证每次解引用处于合法生命周期与范围
  • 能复现数组越界、缺失字符串终止符、错误 union 活动成员和动态内存泄漏,给出容器或 RAII 修复

机制总览

Chapter 4:Compound Types:机制路径

  1. 1

    为什么复合类型首先描述数据形状

    Chapter 3 选择单个值的表示,Chapter 4 把值组合成序列、记录、互斥状态和间接关系。数组表示固定数量同类型元素,struct 表示同时存在的异类字段,union 让候选表示共享存储,enum 命名有限状态,pointer 表示某个对象的地址。它们不是语法替代品,选择错误会把长度、活动成员或所有权藏到注释中。

  2. 2

    数组是固定长度的连续同类元素

    数组类型包含元素类型和长度。 int scores[4] 有四个 int,合法下标是 0、1、2、3,即半开区间 [0,4) 。初始化列表按顺序提供首值,未提供的剩余元素执行值初始化。

  3. 3

    C 字符串与 string 类有不同长度契约

    C 字符串用 char 数组保存字符,并以 \0 终止;容量必须包含终止符。

先按顺序建立机制,再进入实验切换阶段并检查失效证据。

章级决策实验

Chapter 4:Compound Types:机制与证据

切换《Chapter 4:Compound Types》的三个关键教学阶段,先解释机制,再用运行与失败证据验证结论。

选择推理阶段

当前阶段 · 为什么复合类型首先描述数据形状

Chapter 3 选择单个值的表示,Chapter 4 把值组合成序列、记录、互斥状态和间接关系。数组表示固定数量同类型元素,struct 表示同时存在的异类字段,union 让候选表示共享存储,enum 命名有限状态,pointer 表示某个对象的地址。它们不是语法替代品,选择错误会把长度、活动成员或所有权藏到注释中。

可核验证据

从干净构建开始,以固定输入运行本节示例,再加入一个边界或故障场景验证「为什么复合类型首先描述数据形状」的状态变化。

学完《Chapter 4:Compound Types》后,应能从输入和前置条件推导状态变化,并用可重复的构建、运行或边界测试证明结果。

失效—证据矩阵

Chapter 4:Compound Types:失效与核验

为什么复合类型首先描述数据形状

典型失效

若只复述「为什么复合类型首先描述数据形状」结论而不追踪状态、所有权和失败路径,示例扩展成多文件或多对象程序后就容易偏离预期。

核验证据

从干净构建开始,以固定输入运行本节示例,再加入一个边界或故障场景验证「为什么复合类型首先描述数据形状」的状态变化。

数组是固定长度的连续同类元素

典型失效

若只复述「数组是固定长度的连续同类元素」结论而不追踪状态、所有权和失败路径,示例扩展成多文件或多对象程序后就容易偏离预期。

核验证据

从干净构建开始,以固定输入运行本节示例,再加入一个边界或故障场景验证「数组是固定长度的连续同类元素」的状态变化。

C 字符串与 string 类有不同长度契约

典型失效

若只复述「C 字符串与 string 类有不同长度契约」结论而不追踪状态、所有权和失败路径,示例扩展成多文件或多对象程序后就容易偏离预期。

核验证据

从干净构建开始,以固定输入运行本节示例,再加入一个边界或故障场景验证「C 字符串与 string 类有不同长度契约」的状态变化。

每个判断都必须能落到观测、测试或产物,不能只凭代码表面推测。

为什么复合类型首先描述数据形状

Chapter 3 选择单个值的表示,Chapter 4 把值组合成序列、记录、互斥状态和间接关系。数组表示固定数量同类型元素,struct 表示同时存在的异类字段,union 让候选表示共享存储,enum 命名有限状态,pointer 表示某个对象的地址。它们不是语法替代品,选择错误会把长度、活动成员或所有权藏到注释中。

数组是固定长度的连续同类元素

数组类型包含元素类型和长度。int scores[4] 有四个 int,合法下标是 0、1、2、3,即半开区间 [0,4)。初始化列表按顺序提供首值,未提供的剩余元素执行值初始化。

int scores[4]{92, 85, 77, 100};
int total{0};
for (int score : scores) {
    total += score;
}

数组对象知道总大小,但在多数表达式和函数参数中会转换为首元素指针,长度不随裸指针传递。尾后指针可作为遍历终点,却不能解引用。二维数组是数组的数组,行列边界要分别证明。

C 字符串与 string 类有不同长度契约

C 字符串用 char 数组保存字符,并以 \0 终止;容量必须包含终止符。strlen 寻找终止符而不是读取数组类型长度,若终止符缺失会越过数组。输入到 char 数组还要限制最大字符数。

char shortName[4]{'A', 'd', 'a', '\0'};
std::string fullName{"Ada Lovelace"};
std::cout << shortName << " / " << fullName.size() << '\n';

std::string 自行管理动态容量和逻辑长度,支持赋值、拼接与比较,通常比裸 char 缓冲更安全。它仍有索引边界,size() 返回字符单元数量,不自动解决 Unicode 用户可见字符计数。

struct、union 与 enum 表达不同组合

struct 的所有成员同时存在,适合一条记录;union 的成员共享存储,一次只有一个活动成员;enum 为有限状态命名,避免用魔法整数猜语义。

enum class Status { pending, ready, failed };
 
struct Job {
    std::string name;
    Status status;
    int attempts;
};

union 若与标签分开维护,任何不同步都会让程序读取错误活动成员。现代代码常用 std::variant 把标签和存储绑在一起;本章理解 union 的核心是“共享存储不等于所有成员同时有值”。

指针保存地址,不携带所有权和长度

指针类型说明所指对象类型,&object 取得地址,*pointer 解引用访问对象。解引用前必须同时证明:指针非空、对象仍存活、地址对齐且访问落在对象/数组合法范围。

int value{42};
int* pointer{&value};
*pointer = 43;

指针加一按元素大小前进,不是固定加一个字节。pointer[i]*(pointer + i) 相关,但只有在同一数组对象及尾后范围内形成和比较这些指针才有定义。

自由存储区需要明确 owner

new 在自由存储区创建对象并返回地址,delete 结束单对象,delete[] 匹配数组分配。每次成功分配必须有唯一释放责任;提前 return、异常、复制指针和多出口都会让手工协议变脆弱。

std::size_t count{4};
int* data{new int[count]{}};
// 使用 data[0] 到 data[count - 1]
delete[] data;
data = nullptr;

设置 nullptr 只能避免该变量继续悬空,不能更新其他别名。现代实现优先用 std::vector<int> 管动态序列、std::array<int,N> 管固定序列,或用 std::unique_ptr<T[]> 表达单一所有权;但理解 new/delete 能解释这些设施在替你维护什么。

组合类型时让边界一起移动

数组的 struct、指向 struct 的指针、包含数组的 struct 都合法,但每增加一层都要保留对应边界。返回局部数组地址会悬空;返回动态数组地址若不说明 owner 会泄漏;把数组参数写成指针后仍需另传 count。

std::array 保留固定长度并提供容器接口,std::vector 让长度随对象移动,std::string 管理字符序列。这些替代方案的共同价值是把地址、长度和生命周期绑定到同一 owner。

四类故障揭示四种不变量

先预测 sanitizer、输出或内存工具可能看到什么,再运行故障版本。未定义行为的某次结果只是症状,不是规则;真正证据是语言契约被破坏的位置,以及修复后边界如何由类型或 owner 强制保持。

三步选择和验证复合类型

分步1 / 3

第一步:先画数据形状

判断字段是同时存在、互斥、有限状态、固定序列还是间接关系;写出长度、活动成员与合法状态。

小结

  • 数组表达固定连续同类元素,转换为裸指针后不再携带长度,尾后指针不能解引用
  • C 字符串依赖 \0,string 类自行管理长度;两者都有各自边界
  • 结构体成员同时存在,union 只有一个活动成员,枚举命名有限状态
  • 指针只保存地址,不自动携带长度、owner 或生命周期;解引用需要四项联合证明
  • new/delete 展示自由存储区协议,容器与 RAII 将地址、长度和释放责任绑定到对象

练习

  1. 问题 1:选择形状。 一条传感器记录、有限状态和三十个同类样本分别用什么类型?
  1. 问题 2:证明指针访问。 解引用 data[i] 前必须提供哪些证据?
  1. 问题 3:修复动态数组。 手工 new[]/delete[] 在多出口函数中有何风险,怎样重构?

名词解释

名词解释

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

数据形状
值按序列、字段、互斥候选或间接关系组成的结构与不变量。
数组
固定数量同类型连续元素组成的对象。
C 字符串
以空字符终止的 char 序列。
结构体
由同时存在的异类命名成员组成的记录类型。
活动成员
union 当前已构造并允许读取的成员。
指针
保存某种对象地址、解引用依赖有效性契约的类型化值。
自由存储区
由 new 创建并需显式或由 RAII 结束生命周期的动态对象区域。

资料与写作方式声明

本章以C++ Primer Plus, Sixth Edition, Chapter 4 Compound Types权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

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

讨论

评论区加载中…