C++ Primer Plus 6e:18 章总复习
用多态记录队列与文件往返项目串联官方 18 章,按构建、路径、对象、标准库和持久化故障定位证据。
学习目标
- 能解释官方 18 章如何形成从当前源码、类型值、控制路径到对象、范围和持久记录的端到端证据链
- 能复现并区分旧产物、输入/边界、浅复制/切片、算法前置条件和文件格式五类综合失败
- 能设计并实现一个多态记录队列往返项目,以故障注入和独立重读完成全书验收
机制总览
C++ Primer Plus 6e:18 章总复习:机制路径
- 1
为什么总复习要从失败反推最早证据
单章练习一次只改变一个概念;综合项目的症状会跨层传播。文件重复记录可能是 ios::app ,也可能是 EOF 循环多执行一次;派生行为消失可能是 nonvirtual,也可能是按 Base value 切片;崩溃可能来自本次逻辑,也可能运行了旧 executable。总复习先寻找最早失效契约,而不是重读所有摘要。
- 2
第一层:证明运行的是当前源码和完整模块
Chapter 1–4 负责源代码、工具链、类型值和复合数据;Chapter 9 负责翻译单元、声明/定义与链接。综合验收从删除旧产物、完整警告编译和记录链接输入开始。若构建失败却还能启动旧 app,任何运行结果都失去来源证明。
- 3
第二层:输入、控制和函数必须覆盖完整域
Chapter 5–8 提供 loop/branch/function/reference/overload/template 契约;Chapter 17 的 stream state 决定输入是否成功。先把记录输入划分为完整合法、边界、字段缺失、类型错误和 EOF,再让 extraction 成功驱动循环。
章级决策实验
C++ Primer Plus 6e:18 章总复习:机制与证据
切换《C++ Primer Plus 6e:18 章总复习》的三个关键教学阶段,先解释机制,再用运行与失败证据验证结论。
选择推理阶段
当前阶段 · 为什么总复习要从失败反推最早证据
单章练习一次只改变一个概念;综合项目的症状会跨层传播。文件重复记录可能是 ios::app ,也可能是 EOF 循环多执行一次;派生行为消失可能是 nonvirtual,也可能是按 Base value 切片;崩溃可能来自本次逻辑,也可能运行了旧 executable。总复习先寻找最早失效契约,而不是重读所有摘要。
可核验证据
从干净构建开始,以固定输入运行本节示例,再加入一个边界或故障场景验证「为什么总复习要从失败反推最早证据」的状态变化。
学完《C++ Primer Plus 6e:18 章总复习》后,应能从输入和前置条件推导状态变化,并用可重复的构建、运行或边界测试证明结果。
失效—证据矩阵
C++ Primer Plus 6e:18 章总复习:失效与核验
为什么总复习要从失败反推最早证据
典型失效
若只复述「为什么总复习要从失败反推最早证据」结论而不追踪状态、所有权和失败路径,示例扩展成多文件或多对象程序后就容易偏离预期。
核验证据
从干净构建开始,以固定输入运行本节示例,再加入一个边界或故障场景验证「为什么总复习要从失败反推最早证据」的状态变化。
第一层:证明运行的是当前源码和完整模块
典型失效
若只复述「第一层:证明运行的是当前源码和完整模块」结论而不追踪状态、所有权和失败路径,示例扩展成多文件或多对象程序后就容易偏离预期。
核验证据
从干净构建开始,以固定输入运行本节示例,再加入一个边界或故障场景验证「第一层:证明运行的是当前源码和完整模块」的状态变化。
第二层:输入、控制和函数必须覆盖完整域
典型失效
若只复述「第二层:输入、控制和函数必须覆盖完整域」结论而不追踪状态、所有权和失败路径,示例扩展成多文件或多对象程序后就容易偏离预期。
核验证据
从干净构建开始,以固定输入运行本节示例,再加入一个边界或故障场景验证「第二层:输入、控制和函数必须覆盖完整域」的状态变化。
为什么总复习要从失败反推最早证据
单章练习一次只改变一个概念;综合项目的症状会跨层传播。文件重复记录可能是 ios::app,也可能是 EOF 循环多执行一次;派生行为消失可能是 nonvirtual,也可能是按 Base value 切片;崩溃可能来自本次逻辑,也可能运行了旧 executable。总复习先寻找最早失效契约,而不是重读所有摘要。
第一层:证明运行的是当前源码和完整模块
Chapter 1–4 负责源代码、工具链、类型值和复合数据;Chapter 9 负责翻译单元、声明/定义与链接。综合验收从删除旧产物、完整警告编译和记录链接输入开始。若构建失败却还能启动旧 app,任何运行结果都失去来源证明。
↡能证明每个源文件如何形成目标文件、符号由哪个定义提供、最终可执行文件何时生成的命令和产物集合。rm -f build/ledger
c++ -std=c++17 -Wall -Wextra -Wconversion \
src/main.cpp src/record.cpp src/queue.cpp -o build/ledger
shasum build/ledger保留 compiler version、命令、warning、exit status 和 executable hash。undefined reference 要检查实现源文件是否编译/链接、签名和 namespace 是否一致;multiple definition 要检查普通定义是否放进头文件。
第二层:输入、控制和函数必须覆盖完整域
Chapter 5–8 提供 loop/branch/function/reference/overload/template 契约;Chapter 17 的 stream state 决定输入是否成功。先把记录输入划分为完整合法、边界、字段缺失、类型错误和 EOF,再让 extraction 成功驱动循环。
↡用代表值、上下边界、边界外值和格式失败覆盖每条分支、循环出口与函数错误语义的输入集合。std::string line;
std::size_t lineNumber = 0;
while (std::getline(input, line)) {
++lineNumber;
auto parsed = parseRecord(line);
if (!parsed) report(lineNumber, parsed.error());
else queue.enqueue(std::move(*parsed));
}
if (!input.eof()) throw std::runtime_error{"read failed"};parseRecord 负责字段完整和类型转换;Record constructor 负责领域不变量;Queue 负责容量。三层失败不要合并成“输入错误”,否则无法决定谁恢复、谁报告和状态是否提交。
第三层:对象 owner、复制和多态必须一致
Chapter 10–12 建立 class invariant、operator/conversion、dynamic member/copy control;Chapter 13–15 建立 is-a、多态、代码复用、异常和 RTTI。综合对象模型必须回答:谁拥有记录/节点、是否可复制、通过 Base owner 删除是否完整、throw 时哪些对象已提交。
↡标明每个动态对象唯一/共享 owner、所有 borrower、复制或移动边和最终销毁者的生命周期结构。class Record {
public:
virtual ~Record() = default;
virtual std::string kind() const = 0;
virtual void write(std::ostream&) const = 0;
};
using RecordOwner = std::unique_ptr<Record>;
std::vector<RecordOwner> records;Queue 若以 Node* 链表实现,应删除复制或定义深复制,不能保留默认浅复制。异质集合保存 unique_ptr<Record>,不能保存 Record value;Base destructor 必须 virtual。异常前先在 temporary 建立完整对象,成功后再提交到 Queue。
第四层:STL 算法必须满足范围和 callable 前置条件
Chapter 16 提供 string/container/iterator/algorithm/functor,Chapter 18 加入 move/lambda/wrapper。排序记录时,range 必须有效,比较器必须 strict weak ordering,lambda capture 必须活过调用;移动 owner 后不能读取未规定源状态。
↡由有效半开范围、迭代器能力、算法前置条件、输出容量和 callable 语义共同构成的处理约定。std::sort(records.begin(), records.end(),
[](const RecordOwner& left, const RecordOwner& right) {
return left->kind() < right->kind();
});用相等 key 验证 comp(x,x) 为 false,用三元组验证无比较环;容器修改后按具体 invalidation 规则更新 iterator。若 callable 只局部立即使用,保留具体 lambda;需要运行时异构存储才使用 std::function。
第五层:文件往返必须重建新对象
Chapter 17 负责 formatted/binary I/O、mode、stream state 和 random access;Chapter 18 的 scoped enum/move 可表达记录 tag 与 owner 转移。写入成功不以“文件存在”判定,而以临时文件完整写入、关闭、独立打开、逐条解析成新对象并比较为准。
↡把对象写出并关闭,再由独立输入流解析为新对象,比较数量、类型、字段和状态的双向验证。void save(const std::filesystem::path& path,
const std::vector<RecordOwner>& records) {
const auto temporary = path.string() + ".tmp";
std::ofstream output{temporary, std::ios::trunc};
if (!output) throw std::runtime_error{"open output failed"};
for (const auto& record : records) record->write(output);
output.close();
if (!output) throw std::runtime_error{"write failed"};
std::filesystem::rename(temporary, path);
}二进制格式还需 magic/version/length/endian;random access 还需验证 offset、seekg/seekp 与完整 transfer。任何短记录都必须在构造对象前拒绝,不能用旧字段补齐。
从症状回到正确章节层
诊断顺序从最早阶段开始:产物、输入/路径、对象生命周期、范围算法、持久格式。每次只注入或修复一个变量,保存预测、实际症状、第一条失败证据和最终根因。
用四类故障注入完成综合验收
先预测 shallow copy、slicing、bad ordering、short record 会在哪一层被拒绝,再切换实验面板核对保护策略。如果故障直到崩溃或最终输出才被发现,说明更早的不变量、类型或状态检查尚未建立。
第一步:建立四层正常基线
从干净构建开始,固定输入和临时路径;创建两种派生记录,经 Queue、sort、save、close、reload 后逐字段比较。
小结
- 18 章最终形成 build/value、path/function、object/reuse、library/persistence 四层端到端证据链
- 综合故障先定位最早失败层,旧产物、坏输入、浅复制、切片和短文件不能混成一个症状处理
- class owner、copy/move、virtual destructor 与 exception unwind 必须使用同一生命周期协议
- STL 算法以有效范围、iterator category 和 callable 语义为前置条件
- 文件写入只有在关闭、独立重读并重建等价新对象后才完成验收
练习
- 问题 1:链接层定位。 头文件可见但
Queue::enqueueundefined reference,应收集什么?
- 问题 2:对象与解析分层。 文件含字段完整但 score=120,parser、Record 和 Queue 各负责什么?
- 问题 3:最终验收。 列出正常往返和四类故障的最小断言。
名词解释
名词解释
本章出现的专业名词,用大白话再讲一遍。
- 端到端证据链
- 从源码、构建、值、路径、对象到持久结果都可复现的证明链。
- 构建证据
- 证明源、目标、符号和可执行产物关系的命令与结果。
- 边界样例集
- 覆盖代表值、上下边界、边界外和格式失败的输入集合。
- 所有权图
- 标明 owner、borrower、复制/移动边和销毁者的生命周期结构。
- 算法范围契约
- 范围、迭代器、算法、输出和 callable 的联合前置条件。
- 文件往返
- 写出关闭后独立重读并重建等价对象的验证。