C++ Primer Plus 6e:18 章总复习

用多态记录队列与文件往返项目串联官方 18 章,按构建、路径、对象、标准库和持久化故障定位证据。

学习目标

  • 能解释官方 18 章如何形成从当前源码、类型值、控制路径到对象、范围和持久记录的端到端证据链
  • 能复现并区分旧产物、输入/边界、浅复制/切片、算法前置条件和文件格式五类综合失败
  • 能设计并实现一个多态记录队列往返项目,以故障注入和独立重读完成全书验收

机制总览

C++ Primer Plus 6e:18 章总复习:机制路径

  1. 1

    为什么总复习要从失败反推最早证据

    单章练习一次只改变一个概念;综合项目的症状会跨层传播。文件重复记录可能是 ios::app ,也可能是 EOF 循环多执行一次;派生行为消失可能是 nonvirtual,也可能是按 Base value 切片;崩溃可能来自本次逻辑,也可能运行了旧 executable。总复习先寻找最早失效契约,而不是重读所有摘要。

  2. 2

    第一层:证明运行的是当前源码和完整模块

    Chapter 1–4 负责源代码、工具链、类型值和复合数据;Chapter 9 负责翻译单元、声明/定义与链接。综合验收从删除旧产物、完整警告编译和记录链接输入开始。若构建失败却还能启动旧 app,任何运行结果都失去来源证明。

  3. 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 时哪些对象已提交。

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 后不能读取未规定源状态。

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 会在哪一层被拒绝,再切换实验面板核对保护策略。如果故障直到崩溃或最终输出才被发现,说明更早的不变量、类型或状态检查尚未建立。

分步1 / 3

第一步:建立四层正常基线

从干净构建开始,固定输入和临时路径;创建两种派生记录,经 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. 问题 1:链接层定位。 头文件可见但 Queue::enqueue undefined reference,应收集什么?
  1. 问题 2:对象与解析分层。 文件含字段完整但 score=120,parser、Record 和 Queue 各负责什么?
  1. 问题 3:最终验收。 列出正常往返和四类故障的最小断言。

名词解释

名词解释

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

端到端证据链
从源码、构建、值、路径、对象到持久结果都可复现的证明链。
构建证据
证明源、目标、符号和可执行产物关系的命令与结果。
边界样例集
覆盖代表值、上下边界、边界外和格式失败的输入集合。
所有权图
标明 owner、borrower、复制/移动边和销毁者的生命周期结构。
算法范围契约
范围、迭代器、算法、输出和 callable 的联合前置条件。
文件往返
写出关闭后独立重读并重建等价对象的验证。

讨论

评论区加载中…