简介:《数据结构与算法分析C++语言描述》第四版配套答案与源码包,面向正在啃经典教材的C++学习者与算法自学者,提供书中练习题的参考解答与可运行的实现代码,覆盖数组、链表、栈、队列、树、图等数据结构,以及排序、查找、最短路径、最小生成树等算法的C++示例与效率分析,帮读者把抽象原理落到具体代码。压缩包共100个文件,其中63个cpp源文件、22个头文件、12个docx习题答案文档,另有少量html与txt说明,整体仅4.65MB,轻量且覆盖教材各章核心示例。该资源在CSDN已有3703人学习下载,是许多读者对照教材验证算法逻辑的常用素材。通过cpp与h可直接观察SuffixArray、RadixSort、KdTree、MaxSumTest等典型实现,docx则整理解题思路,便于自测与查漏补缺;尤其适合需要结合源码理解复杂度、备战笔试面试的进阶学习者。
1. 参考答案不是标准答案:C++ 版数据结构与算法分析(第四版)该怎么“对答案”
搜索“数据结构与算法分析C++语言描述第四版参考答案”的人,通常不是想找现成作业答案,而是卡在了某一章的练习上。这本书题量跨度大:前三章的链表、栈、队列多敲几遍还能过,到 AVL 双旋转、伸展树摊还分析、不相交集路径压缩,C++ 模板和递归的复杂度同时压上来,参考答案就成了“校验点”——你写完一版拿不准对错,需要一个更严谨、边界处理更完整的实现来核对思路。
适合翻开参考答案的人,是自己先动手写过、有编译器、能跑测试的人;不适合想复制粘贴的人,因为这份答案经常只给核心函数,没有 main 和读入,抄了也编译不过。这篇笔记拆成三块:答案里有什么、怎么对照检验、最容易翻车的几个坑。
2. 先看清第四版的答案盘:章节权重、题型构成与 C++11 底色
2.1 十二章节的知识权重与三类练习形态
第四版的中文译本把内容编成十二章:前三章是引论、算法分析和列表/栈/队列,第四到第六章是树、哈希和优先队列,第七到第九章是排序、不相交集和图算法,最后几章是算法设计技术、摊还分析和高级数据结构。参考答案的密度并不均匀,树、排序、图三章的题量最大,改造型练习也最多;模板、摊还分析两章则多是短推导题,答案常常只有几行。
对照答案之前,先判断手里这道题属于哪一类,能省大量时间。第四版练习大致是三种形态:
**推导题。**典型问题是给你一个递归式或者一段双层循环,让你证明复杂度、分析最坏情形,或者解释某个势函数的含义。参考答案的形态是文字推导加结论,基本没有完整代码。这类题看答案很容易“看懂”,但合上答案还是不会推,所以正确姿势是先拿草稿纸自己推到卡住,再翻答案,并且把推导缺口写在题旁边。只看几眼就翻篇,等于白做。
**实现题。**典型问题是“实现二叉堆的插入和删除最小元素”“给链表类写迭代器”这一类。参考答案会给你成员函数级别的核心代码,但通常省略拷贝构造、拷贝赋值和析构这些“体力活”。它提供的是一种把例题方法落实到具体代码的形态,不是工程级代码。对照时重点看分支处理、边界条件和数据结构不变量,而不是变量命名。
**改造题。**让你把已经学过的结构扩展成新用途,比如“把二叉查找树改造成支持找第 k 小元素的版本”。参考答案往往只给出新增成员变量和增量代码,你得自己把改动嵌回原类。这也是答案最容易让人误判的地方——你看到一个函数以为看懂了,但往原类里一放,构造函数和析构全得跟着改。
我整理了一张第四版常见题型与答案形态的对应表,不同印次题号会变,但题型比例相对稳定:
| 章节主题 | 题型 | 答案形态 | 最容易翻车的点 |
|---|---|---|---|
| 模板与递归 | 推导题 | 短函数加推导 | 模板匹配顺序 |
| 算法分析 | 推导题 | 递推展开 | 循环边界与初值 |
| 列表/栈/队列 | 实现题 | 类成员函数 | 指针悬垂 |
| 树 | 实现题+改造题 | 指针实现 | 删除分支 |
| 哈希 | 实现题 | 探查函数 | 惰性删除标记 |
| 优先队列 | 实现题 | 数组上滤下滤 | 堆序性质破坏 |
| 排序 | 实现题+推导题 | 完整算法函数 | 递归深度和稳定性 |
| 不相交集 | 实现题 | find/union 短代码 | 路径压缩与秩 |
| 图算法 | 实现题+改造题 | 邻接表算法 | 优先队列定制 |
| 算法设计 | 改造题 | 部分代码 | 状态定义 |
| 摊还分析 | 推导题 | 势函数证明 | 势能初值选择 |
很多人拿这本书和严蔚敏的《数据结构(C语言版)》对比,也常去翻王道系列的复习笔记。严蔚敏那本更偏 C 语言指针操作,王道笔记偏考点浓缩,而第四版的参考答案的价值在“完整类的实现细节”,尤其是模板和异常处理层面。教材答案不会帮你背考点,但能把你从“看懂伪代码”推到“写出能编译的类”。
2.2 C++11 特性在答案里的比重:移动语义、智能指针、initializer_list
第四版出版在 C++11 标准之后,参考答案和第三版相比,最大的区别是auto和移动语义出现频率明显增加。你用新编译器编译答案时,经常能见到auto it = begin()这种迭代器声明,以及移动构造函数配合noexcept的写法。这是这版答案的“时代特征”,也是初学阶段最容易忽略的部分。
一个典型的参考答案风格移动构造长这样:
// 参考答案风格的 Vector 类:拷贝构造、移动构造 template <typename Object> class Vector { public: explicit Vector(int initSize = 0) : theSize{initSize}, theCapacity{initSize + 2} { objects = new Object[theCapacity]; } // 拷贝构造:深拷贝,数据独立 Vector(const Vector& rhs) : theSize{rhs.theSize}, theCapacity{rhs.theCapacity}, objects{nullptr} { objects = new Object[theCapacity]; std::copy(rhs.objects, rhs.objects + theSize, objects); } // 移动构造:偷走指针并置空源对象,避免深拷贝 Vector(Vector&& rhs) noexcept : theSize{rhs.theSize}, theCapacity{rhs.theCapacity}, objects{rhs.objects} { rhs.objects = nullptr; rhs.theSize = 0; rhs.theCapacity = 0; } // 赋值运算符、析构函数略 };注意构造函数初始化列表里用theSize{initSize}而不是theSize = initSize,这是 C++11 以来更严谨的写法,答案里大量出现。移动构造的关键在偷指针和置空源对象:把rhs.objects直接搬过来,再把rhs.objects设成nullptr,避免两个对象指向同一块内存后重复 delete。noexcept修饰同样重要,标准库的vector在扩容时会优先检测移动构造是否可能抛异常,决定用移动还是拷贝来转移旧元素。
但一个容易误导人的点是:答案里智能指针并不常见。教材风格偏向后学“机制”,树和链表大多仍用裸new/delete。你在答案里看到 delete,应该理解为“教科书写法”,而不是“推荐工程做法”。真正上工程时,std::unique_ptr或std::shared_ptr往往比裸指针更合适,但考试和面试里裸指针仍是讨论的主线。
initializer_list在答案里的出现频率比我预想得低,多数容器练习的构造函数还是“传入初始容量”的形态。不过第 7 章排序练习里经常会用{4, 2, 8, 1}这种花括号列表给 vector 赋初值,这是 C++11 之后很方便的测试写法。如果编译器提示找不到initializer_list,一般是标准库头文件没有引入,加上<initializer_list>即可。
2.3 用参考答案搭一条“练习-对照-修正”闭环
参考答案是一堆散装代码,你要做的不是把它通读一遍,而是把它变成一条能反复使用的验证闭环。我常用的流程是四步。
第一步,给每章建一个独立目录,里面放两个文件:exercise_xx.cpp和exercise_xx_test.cpp。前者放你的解答,后者放一组调用你实现的测试代码。第二步,先不看答案,把 exercise 文件写到能通过自测的程度。第三步,翻开答案,找出与你的实现结构不同的地方,尤其是函数签名、递归出口、边界分支。第四步,合上答案,一周后凭记忆重写一遍,只保留差异点作为提醒。
这套流程最关键的是第二步。很多人的问题在于写完一个能通过的自测就以为自己“会了”,其实只覆盖了一条正常路径。二叉树的删除至少要覆盖无孩子、单孩子、双孩子三种情况,哈希表要覆盖删除后的查找路径,堆要覆盖上滤到根和下滤到叶的两条路径。测试用例写全了,对照答案才真正有价值。
用一个小例子说明。假设你要验证自己写的二分查找,可以这样写测试:
// exercise_02_test.cpp:验证 vector 里的二分查找 #include <vector> #include <cassert> int myBinarySearch(const std::vector<int>& a, int target); int main() { std::vector<int> v{1, 3, 5, 7, 9}; assert(myBinarySearch(v, 1) == 0); // 首元素 assert(myBinarySearch(v, 9) == 4); // 末元素 assert(myBinarySearch(v, 4) == -1); // 中间不存在 assert(myBinarySearch(v, 0) == -1); // 比最小还小 assert(myBinarySearch(v, 10) == -1); // 比最大还大 assert(myBinarySearch({}, 1) == -1); // 空数组 return 0; }这几个例子里最容易漏的是空数组和两端边界。你自己写第一版时多半会漏掉一两个,翻答案时稍微用心一点,就能看到参考答案对边界条件的处理——通常是把first == last作为循环出口。用 assert 写测试的好处是失败时能告诉你具体哪一行挂了,配合调试器能快速定位。再往后想做得更严,可以引入 Google Test 这类框架,但对教材练习来说 assert 已经够用。我建议把每个练习的测试文件都留在目录里,复习时先跑测试再决定要不要重新看答案,比从头到尾翻书高效得多。
提示:参考答案基于 C++11 编写,你的编译器如果默认按 C++98 处理,
auto、移动构造、花括号初始化都会报错。先确认编译命令行带了-std=c++11或更高版本,再开始对照代码。
3. 把参考答案当脚手架:一套能在每一章复用的对照流程
3.1 四步对照法:先自写、再测试、后读答案、最后重构
参考答案最容易把人带进“我读懂了”的幻觉。读到一段二叉树删除,每一行都看得懂,合上书写一个不同形态的二叉堆插入却手感全无。问题出在答案被当成阅读材料,而不是校验工具。
我用的对照流程分成四步,整本书每一章都可以套。
第一步,把自己写的解答放在一个新文件里,答案不看也不碰。这一步不需要多完善,能表达主要思路就行,但函数签名需要具体到能编译。第二步,给这个文件写一组断言或者一个很小的 test 文件,把边界情况都跑一遍。跑不过就先改自己的代码,不要急着翻答案。第三步,翻开答案,只检索你刚写的那道题,不看相邻题的答案。对答案的时候带着三个问题:函数签名和我的差在哪、循环退出条件是什么、递归或者迭代的终止分支怎么写。第四步,合上答案,把刚才的差异点记在一个笔记文件里,然后在一到两天后重新写一遍这道题,写完之后再跑一遍同样的测试。
为什么要把“重写”放到一两天后?因为刚看完答案时的短期记忆还在,立刻重写等于默写。隔开的这段时间让大脑做一轮筛选,留下来的才是真正理解和记住的部分。四个步骤里最容易被跳过的也是这一步,很多人卡在“看完答案就以为自己会了”的循环里,本质上是在反复阅读不是练习。
3.2 一个全场通用的例子:以二叉查找树 remove 为例
二叉树删除是教材里区分“看懂”和“会写”的分水岭。我第一次实现时写的代码,运行到“删除根节点”就直接翻车:
// 一个典型的错误版本:节点指针按值传递 void remove(int x, Node* t) { if (t == nullptr) return; if (x < t->element) remove(x, t->left); else if (t->element < x) remove(x, t->right); else if (t->left && t->right) { t->element = findMin(t->right)->element; remove(t->element, t->right); } else { Node* old = t; t = (t->left) ? t->left : t->right; // 这里只改了形参 delete old; } }问题很清楚:t是按值传递的指针,删除单孩子节点时局部变量t被重新指向孩子的子节点,但调用者持有的指针仍是原来的t。删除根节点时,整个 BST 的root_成员不会被更新,下一次查找直接访问已释放内存。
参考答案的标准解法是让指针以引用方式传入:
// 参考答案风格:remove 的第二个参数使用引用 template <typename Comparable> void BinarySearchTree<Comparable>::remove(const Comparable& x, node_ptr& t) const { if (t == nullptr) return; if (x < t->element) remove(x, t->left); else if (t->element < x) remove(x, t->right); else if (t->left != nullptr && t->right != nullptr) { t->element = findMin(t->right)->element; remove(t->element, t->right); } else { node_ptr oldNode = t; t = (t->left != nullptr) ? t->left : t->right; delete oldNode; } }差别在函数体第一行:参数类型是node_ptr& t,按引用传递,所有递归层修改的都是同一个指针变量。当t指向的节点需要被删除时,delete oldNode之前先把父节点的孩子指针更新为孩子节点的子树,父节点一侧不会留下悬垂指针。这是二叉查找树删除题目的标准解,也是参考答案想让你在对照中抓住的关键改动:删除不是“释放节点”,而是“修改链接再释放”。
3.3 用测试集把“参考答案”变成“可跑代码”
即使参考答案写法正确,也经常不能直接编译:它可能省略了头文件、前向声明以及析构。你需要自己补齐测试骨架,把答案塞进一个可运行的程序里。
常见做法是给每个练习建两个文件:.cpp存你的解答,_test.cpp存断言。以二叉查找树为例,测试集至少覆盖四种删除场景:
// bst_test.cpp:覆盖叶子、单孩子、双孩子、删除根 #include "bst.h" #include <cassert> int main() { BST<int> t; for (int x : {5, 3, 7, 2, 4, 6, 8}) t.insert(x); t.remove(2); // 叶子节点 assert(!t.contains(2)); t.remove(7); // 单右孩子的中间节点 assert(!t.contains(7)); assert(t.contains(8)); // 子树没有丢节点 t.remove(5); // 删除根:左右子树都有 assert(!t.contains(5)); assert(t.contains(3) && t.contains(4)); assert(t.contains(6) && t.contains(8)); t.remove(6); // 单左孩子场景补齐 assert(!t.contains(6)); return 0; }跑法很直接:g++ bst.cpp bst_test.cpp -std=c++17 -fsanitize=address -g && ./a.out。-fsanitize=address能在指针用错时立刻报错,而不是等程序随机崩溃。这一组断言在删除节点同时检查子树完整性,比只检查返回值要严格。你用自己的实现跑,跑不过就回去改;改完再看答案,答案会帮你找出设计上的差别,比如哪里可以省掉一个分支,或者哪里必须保留递归。
把答案变成可跑代码的另一个收益是:你可以给同一道题写两个实现,一个是你自己的,一个是参考答案的风格,然后让两者在随机生成的一万条数据上互相验证结果是否一致。这种差分测试比单纯看答案有用得多,尤其是需要维护二叉性质、堆序性质、散列表负载因子的章节。
4. 从“对答案”到“上工程”:参考答案的边界在哪
4.1 容器选择:答案常选 vector,工程里要换成 deque 和 map
教材答案倾向于用最简单的容器表达逻辑。比如图的邻接表,答案最常见形态是vector<vector<int>>;练习要求实现 Dijkstra,参考答案会用一个vector或者自己写的堆。这个选择没有错,但它假设数据规模小、插入删除不频繁,运行一次就退出。
工程场景不一样。频繁在头部插入删除,答案的vector.insert(begin, x)是 O(n),换成deque是常数时间;节点间频繁按键查询,答案的map够用但unordered_map更接近常数级。常见做法是:数据量小于一万用答案方案直接跑;超过十万开始考虑容器差异;超过百万,默认无视答案的容器建议,优先测deque、unordered_map和内存池。
这里有一个容易走偏的误区:换容器不是炫技,而是改复杂度形态。同为 O(n log n) 的算法,容器换成std::map还是std::unordered_map,常数因子能差 3 到 5 倍,这就是现实中时间限制能否通过的差别。刷题时最好每次写完答案再补一行注释,写出这个数据结构在什么场景下会被同类替代。
4.2 复杂度分析 vs 实测:为什么归并排序参考答案比 std::sort 慢近一倍
第 7 章排序练习聚集了全书最多的“看起来都能过”的代码。归并排序的复杂度是 O(n log n),但它在现代 CPU 上的表现和标准库差距比很多人想象的大。参考答案的归并排序通常长这样:
// 标准自顶向下归并排序:递归分半,再线性合并 void mergeSort(vector<int>& a, vector<int>& tmp, int left, int right) { if (left >= right) return; int mid = (left + right) / 2; mergeSort(a, tmp, left, mid); mergeSort(a, tmp, mid + 1, right); merge(a, tmp, left, mid, right); }数学上没问题。实测你在 1000 万元素上跑它,再跑std::sort,前者可能要 4 到 6 秒,后者 2 秒左右。差距来自三个细节:递归调用开销、合并时临时数组的反复拷贝、缓存不命中。标准库的排序实现会对底层小数组用插入排序打底,减少递归到单元素的深度;还会利用部分有序序列的特性减少比较次数,而参考答案通常不会涉及这些优化。
这提醒我们一件事:大 O 只是复杂度上界,常数因子在工程里可能更显著。看参考答案验证“算法对不对”,但不能把答案的运行效率当作工程立足点。第 7 章里的快速排序、堆排序、希尔排序也一样,可以先自己写成参考答案的样子确保逻辑正确,再对照标准库思考差距在哪。
提示:做排序类练习时,把标准库
std::sort的接口和手写排序的实现分开记忆。考试会要你手写归并或快速排序,工程优先用现成设施,两者目标不同。
4.3 把答案迁到现代 C++:const、noexcept、RAII 的三个改造点
参考答案大多是教学性代码,往工程或面试代码迁移时有三处最值得改。
第一是 const 成员函数。答案里查找、遍历这类不修改状态的函数经常漏掉 const 修饰,比如contains(node_ptr t)本应该是 const。明明只读查询却不肯加 const,会导致你无法在 const 引用对象上调用查询方法。改动很小,收益很大。
第二是移动构造函数和移动赋值运算符加noexcept。前面第 2.2 节里Vector(Vector&& rhs) noexcept如果不带 noexcept,标准库 vector 扩容时为了强异常保证会退回拷贝而不是移动,性能损失在元素类型重时非常明显。参考答案写不写 noexcept 取决于版本,但你自己重构时应习惯补上。
第三是 RAII 取代裸指针。树、链表、图三章答案大量使用new和delete,上工程前可以把资源所有权换成std::unique_ptr:
// 节点指针换用 unique_ptr 后的二叉查找树删除骨架 struct Node { int element; std::unique_ptr<Node> left; std::unique_ptr<Node> right; }; void remove(int x, std::unique_ptr<Node>& t) { if (!t) return; if (x < t->element) remove(x, t->left); else if (t->element < x) remove(x, t->right); else if (t->left && t->right) { t->element = findMin(t->right)->element; remove(t->element, t->right); } else { t = std::move(t->left ? t->left : t->right); } }这段代码的亮点在最后一行的统一处理:std::move把左子树或右子树的所有权转移给父节点,删的时候unique_ptr析构自动释放节点,悬垂和泄漏同时消失。这个改造要付出递归参数全部改引用、delete全部拿掉的代价,但在项目里值得。改造时保持一个习惯:一次只改一个点,每改一次跑一遍第 3.3 节那组断言。const 和 noexcept 是纯标注性修改,不会改变运行结果;unique_ptr 改造则会影响拷贝语义,需要额外测试拷贝树的行为。
5. 参考答案避坑指南:五条血泪经验从现象到解决
5.1 现象:delete 节点后访问成员,程序随机崩溃
现象:实现树或链表的删除逻辑后,跑一个稍大的测试程序,结果在某个节点上时好时坏,一会儿能跑过去,一会儿在assert(!t.contains(x))处崩掉。原因:删除节点后,某个指针仍然指向已释放的内存。常见于按值传递指针、删除后没有把父节点指针置空,以及两个节点共享同一块内存的浅拷贝。解决:先把指针参数改成引用传递,务必让父节点持有的被删节点指针更新为子树节点。排查时开启 AddressSanitizer,用g++ -fsanitize=address -g编译,报错会直接指出哪一行访问了已释放内存。这是能把小时级问题压缩到分钟级的调试技术。
5.2 现象:递归删除在十万数据量时直接栈溢出
现象:写二叉查找树析构函数用递归删除所有节点,小规模测试没有任何问题,生成十万个随机键插入后退出程序,segfault,栈回溯显示递归深度达到数万层。原因:树的形状极端退化时,递归深度等于节点数。平衡时深度是 log n,插入顺序如果已经有序,深度就和数量一样大。参考答案的递归删除不是错误,但在极端数据上会顶爆默认栈空间。解决:析构函数改用迭代后序遍历。做法是把节点压进栈,按“左、右、自己”的顺序弹出并释放,循环而非递归。遍历树的其他操作同理,递归只适合深度可控的场景。想在内存受限环境跑大树,递归的替代方案一定要提前设计。
5.3 现象:参考答案的迭代器删除代码一跑就崩
现象:仿照答案写出for (auto it = v.begin(); it != v.end(); ++it) { if (*it % 2 == 0) v.erase(it); },程序崩溃或者漏删元素。原因:vector::erase会让指向被删除元素及之后所有元素的迭代器失效,循环里继续用it访问是未定义行为。答案为了可读性省略这层提醒,读者如果不了解容器语义就直接照抄。解决:删除用it = v.erase(it)更新迭代器,或者用std::remove_if配v.erase两段式删除。循环中需要删除元素的场景,优先考虑std::list,它的 erase 只使当前迭代器失效。这个坑对vector、deque和string都成立,对list、map、set则宽松得多。
5.4 现象:把参考答案的快速排序搬到工程场景速度反而变慢
现象:自己实现的快速排序参考答案在 10 万元素上比std::sort慢一半还多,n 越大差距越明显。原因:参考答案的快排会用取中位数的三数中值法选 pivot,但仍然递归到单元素;没有处理小数组时切换插入排序;在完全逆序数据上递归深度接近 n,导致栈压力和缓存命中都变差。标准库会针对小数组改用插入排序收尾,并在不同数据分布下调整策略。解决:把递归出口从left >= right改成if (right - left < 16) { insertionSort; return; },小数组用插入排序收尾,实测速度立刻大幅提高。工程上能用std::sort就不自己写快排,考试和面试才要求完整手写。
5.5 现象:参考答案编译不过,报错说不认识 auto 和 initializer_list
现象:参考答案代码片段到了本机的 Dev-C++ 或者老版本 GCC 上,编译器报error: 'auto' not permitted或找不到initializer_list。原因:Dev-C++ 的默认编译标准是 C++98,而第四版基于 C++11 编写,auto、移动构造、花括号初始化列表都是 C++11 以后引入的语法。解决:确认编译命令行带了-std=c++11或更高版本。Dev-C++ 在编译器选项里手动加上这个参数,或者换成 VS Code 配置 g++ 环境,这类能自由指定标准的方式。还有一部分“答案”,尤其网上流传的老版本,可能是第三版遗留的代码,第三版当时还是 C++98 思路,看到模板里用了typename和iterator的大量写法是正常现象,不代表你环境坏了。教材有多个印次,网上流传的答案版本复杂,对不上题号时先核对题干文字,不要只看数字。
6. 把参考答案改造成自己的算法自测验证平台
最后分享一个我坚持到现在的习惯:把每章练习题目录变成一个可复用的测试沙箱。
每个章节建一个文件夹,里面统一放两个文件:exercise_xx.cpp存实现,exercise_xx_test.cpp存断言。别写 main 混在一起,分开以后每道题都能单独编译。配合-fsanitize=address,undefined -g和-Wall -Wextra编译,所有隐性的未定义行为都会在测试运行时暴露。跑不过,先用测试定位再翻答案;跑过了,再看答案的边界处理差异,忽略掉变量命名的小差别。
| 章节目录 | 内容 | 统一验证命令 |
|---|---|---|
| ch03_list/ | 链表、栈、队列练习 | g++ -std=c++17 -fsanitize=address,undefined -g *.cpp && ./a.out |
| ch04_tree/ | 二叉树、AVL 练习 | 同上 |
| ch07_sort/ | 排序练习 | 同上,可另加-O2对比性能 |
我建议固定三个动作。一,每个练习只保留一份“我的版本”和一个answers_notes.txt,里面只记录差异点,比如“删除时参数要引用传递”“堆上滤注意空堆”,不抄代码。二,每月挑一道以前的实现题,只凭差异笔记重写,跑同一套测试。能过说明以前吃透了;不能过,把翻车点补进笔记。三,把参考答案当成回归测试的一部分,每周留一个两小时的窗口,翻开答案集中研究自己最容易错的题型,而不是从头到尾通读。
我自己刷这本书从早期版本到第四版,最大的改变是把参考答案当成一个“评审者”,审查我的接口设计、边界和所有权,而不是一个“代写者”。答案的价值不在文字本身,在对照之后你改写了哪些代码。前面那五条坑,每一条我都至少花过一两个晚上调试,希望你不用再走一遍。希望帮到你。
本文还有配套的精品资源,点击获取