新增 4 天教案大纲 + reveal.js 交互幻灯片(docs/teaching/)

大纲按「3天C++强化(含OOP)+1天Qt桌面」组织,讲:练≈1:2.7;每个知识点直接
链接仓库真实代码文件与 ERRATA/VERSION_NOTES 里记录的具体坑。幻灯片
vendor 了 reveal.js 5.2.1(核心+highlight插件),零 CDN 依赖,配 serve.py
本地起服务(仅绑定 127.0.0.1)供离线播放,代码链接可点开查看源码。
This commit is contained in:
张宗平
2026-07-01 16:51:12 +08:00
parent 8e44203929
commit b89819d5db
13 changed files with 2093 additions and 0 deletions
+259
View File
@@ -0,0 +1,259 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>Day 3 · 泛型与标准库</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="reveal/reveal.css">
<link rel="stylesheet" href="reveal/theme.css">
<link rel="stylesheet" href="reveal/plugin/highlight/monokai.css">
<link rel="stylesheet" href="custom.css">
</head>
<body>
<div class="reveal">
<div class="slides">
<section>
<h1>Day 3</h1>
<h3>泛型与标准库</h3>
<p class="module-badge">Qt 方向实训 · 第 3 / 4 天 · 讲:练 ≈ 1:2.7</p>
<ul>
<li>模块 A(3 课时):模板 + 类型转换</li>
<li>模块 B(3 课时):异常 + IO 流 + STL</li>
</ul>
</section>
<!-- ===================== 模块 A:讲 ===================== -->
<section>
<section>
<p class="module-badge">模块 A · 讲授 35min</p>
<h2>模板 + 类型转换 <span class="tag tag-lecture"></span></h2>
<ul>
<li>函数模板 vs 类模板</li>
<li>模板的局限性</li>
<li>四种显式类型转换(static / dynamic / const_cast</li>
</ul>
</section>
<section>
<h3>类模板:类型不能自动推导 <span class="tag tag-lecture"></span></h3>
<pre><code class="cpp" data-trim data-noescape>
template&lt;class NameType, class AgeType&gt;
class Person{
public:
Person(NameType name, AgeType age){ mName = name; mAge = age; }
void showPerson(){ cout &lt;&lt; mName &lt;&lt; " " &lt;&lt; mAge &lt;&lt; endl; }
NameType mName; AgeType mAge;
};
// Person P1("德玛西亚", 18); // 非法——类模板不支持类型自动推导
Person&lt;string, int&gt; P1("德玛西亚", 18); // 必须显式写出模板实参
</code></pre>
<p><small>与函数模板最大的区别:函数模板能从实参推导类型,类模板不能——这是常见误用点。
出处:<a class="filelink" target="_blank"
href="../../../p01/ch05/class_template_basics.cpp">p01/ch05/class_template_basics.cpp</a></small></p>
</section>
<section>
<h3>dynamic_cast:运行期检查的向下转型 <span class="tag tag-lecture"></span></h3>
<pre><code class="cpp" data-trim data-noescape>
class Animal { public: virtual void ShowName() = 0; }; // 必须有虚函数(多态类型)
class Dog : public Animal { virtual void ShowName(){ /*...*/ } };
class Other { public: void PrintSomething(){} }; // 与 Animal 无继承关系
Dog* dog01 = new Dog;
Animal* animal02 = dynamic_cast&lt;Animal*&gt;(dog01); // 子类→父类,合法(其实用不着 dynamic_cast
// Dog* dog02 = dynamic_cast&lt;Dog*&gt;(animal01); // 父类→子类:运行期检查,失败返回 nullptr
// Animal* x = dynamic_cast&lt;Animal*&gt;(other); // 无继承关系:编译期报错
</code></pre>
<p><small>dynamic_cast 依赖 RTTI,只对多态类型(含虚函数)生效——这是与 static_cast
最本质的区别。出处:<a class="filelink" target="_blank"
href="../../../p01/ch06/dynamic_cast_demo.cpp">p01/ch06/dynamic_cast_demo.cpp</a></small></p>
</section>
</section>
<!-- ===================== 模块 A:练 ===================== -->
<section>
<section>
<p class="module-badge">模块 A · 练习 95min</p>
<h2>动手:模板与类型转换 <span class="tag tag-practice"></span></h2>
</section>
<section>
<div class="task-card">
<h4>任务卡 1 · 写一个通用容器模板 <span class="tag tag-practice"></span></h4>
<p>仿照 <a class="filelink" target="_blank"
href="../../../p01/ch05/class_template_basics.cpp">class_template_basics.cpp</a>
写一个 <code>Pair&lt;T1, T2&gt;</code> 或简易 <code>Stack&lt;T&gt;</code>push/pop/top
栈空时的行为要明确定义)。</p>
</div>
</section>
<section>
<div class="task-card">
<h4>任务卡 2 · dynamic_cast 安全向下转型 <span class="tag tag-practice"></span></h4>
<p>用 Day2 写的多态类层次(基类指针数组),对每个元素做
<code>dynamic_cast</code> 判断具体类型,转型失败(返回 nullptr)的分支要写出来,
不能假设转型一定成功。参照 <a class="filelink" target="_blank"
href="../../../p01/ch06/dynamic_cast_demo.cpp">dynamic_cast_demo.cpp</a></p>
</div>
</section>
<section>
<div class="task-card">
<h4>任务卡 3 · 选做:类模板的派生关系 <span class="tag tag-practice"></span></h4>
<p>阅读 <a class="filelink" target="_blank"
href="../../../p01/ch05/class_template_derives_class.cpp">class_template_derives_class.cpp</a>
<a class="filelink" target="_blank"
href="../../../p01/ch05/class_template_derives_template.cpp">class_template_derives_template.cpp</a>
写一句话总结「普通类继承类模板」与「类模板继承类模板」在写法上的区别。</p>
</div>
</section>
</section>
<!-- ===================== 模块 B:讲 ===================== -->
<section>
<section>
<p class="module-badge">模块 B · 讲授 35min(三块内容合并速览)</p>
<h2>异常 + IO 流 + STL <span class="tag tag-lecture"></span></h2>
<ul>
<li>异常:栈解旋、自定义异常类</li>
<li>文件 IO:文本 vs 二进制</li>
<li>STL:容器 + 迭代器 + 算法 + 函数对象</li>
</ul>
</section>
<section>
<h3>栈解旋(stack unwinding <span class="tag tag-lecture"></span></h3>
<pre><code class="cpp" data-trim data-noescape>
class Person{
public:
Person(string name){ mName = name; cout &lt;&lt; mName &lt;&lt; "对象被创建!\n"; }
~Person(){ cout &lt;&lt; mName &lt;&lt; "对象被析构!\n"; }
string mName;
};
void TestFunction(){
Person p1("aaa"), p2("bbb"), p3("ccc");
throw 10; // 抛出异常时,p3/p2/p1 按逆序自动析构——这就是「栈解旋」
}
int main(){ try{ TestFunction(); } catch(...){ cout&lt;&lt;"异常被捕获!\n"; } }
</code></pre>
<p><small>出处:<a class="filelink" target="_blank"
href="../../../p01/ch07/stack_unwinding.cpp">p01/ch07/stack_unwinding.cpp</a></small></p>
</section>
<section>
<h3>自定义异常类:继承 std::exception <span class="tag tag-lecture"></span></h3>
<pre><code class="cpp" data-trim data-noescape>
class MyOutOfRange : public exception{
public:
MyOutOfRange(const string errorInfo){ m_Error = errorInfo; }
virtual const char* what() const noexcept { return m_Error.c_str(); }
string m_Error;
};
// 多个 catch 分支各自匹配不同的异常类型(都直接继承 exception,彼此是平级、不是父子)
// Person(151) 这里实际只会抛 MyOutOfRange,前两个 catch 分支不会命中——
// 写多个 catch 分支是为了同一段代码能同时应对「标准库异常」和「自定义异常」两种来源
try{ Person p(151); }
catch (out_of_range& e){ cout &lt;&lt; e.what(); } // 若改成 throw out_of_range(...) 会走这里
catch (MyOutOfRange e){ cout &lt;&lt; e.what(); } // 本例实际命中的分支
</code></pre>
<p><small>强化点:catch 分支的匹配顺序只在「类型存在继承关系」时才重要(子类 catch 必须写在
父类前面,否则父类分支会先吞掉子类异常);这里三个类型互不继承,顺序其实不影响本例结果。
出处:<a class="filelink" target="_blank"
href="../../../p01/ch07/custom_exception_class.cpp">p01/ch07/custom_exception_class.cpp</a></small></p>
</section>
<section>
<h3>二进制文件读写 <span class="tag tag-lecture"></span></h3>
<pre><code class="cpp" data-trim data-noescape>
ofstream osm("person.txt", ios::out | ios::binary);
osm.write((const char*)&p1, sizeof(Person)); // 直接写内存布局,不做任何格式转换
osm.close();
ifstream ism("person.txt", ios::in | ios::binary);
Person p3;
ism.read((char*)&p3, sizeof(Person)); // 按同样的 sizeof 读回——结构必须一致
</code></pre>
<p><small>二进制 IO 要求读写两端的结构体内存布局完全一致(含 padding),跨平台/跨编译器
不保证兼容。出处:<a class="filelink" target="_blank"
href="../../../p01/ch08/binary_file_io.cpp">p01/ch08/binary_file_io.cpp</a></small></p>
</section>
<section>
<h3>STL:容器 + 迭代器 + 算法 + 函数对象 <span class="tag tag-lecture"></span></h3>
<pre><code class="cpp" data-trim data-noescape>
// 谓词(函数对象)驱动算法
class GreaterThenFive{ public: bool operator()(int num){ return num &gt; 5; } };
vector&lt;int&gt;::iterator it = find_if(v.begin(), v.end(), GreaterThenFive());
class MyCompare{ public: bool operator()(int a,int b){ return a &gt; b; } };
sort(v.begin(), v.end(), MyCompare()); // 用函数对象改变算法策略——从小到大 → 从大到小
</code></pre>
<p><small>STL 的核心思想:容器负责存储,算法负责操作,迭代器是两者之间的胶水,
函数对象(谓词)负责定制算法行为。出处:
<a class="filelink" target="_blank" href="../../../p01/ch09/s03/vector_iterator.cpp">p01/ch09/s03/vector_iterator.cpp</a>
<a class="filelink" target="_blank" href="../../../p01/ch09/s04/predicate.cpp">p01/ch09/s04/predicate.cpp</a></small></p>
</section>
</section>
<!-- ===================== 模块 B:练 ===================== -->
<section>
<section>
<p class="module-badge">模块 B · 练习 95min(任选其二,第三个留参考)</p>
<h2>动手:异常 / STL / 文件 IO <span class="tag tag-practice"></span></h2>
</section>
<section>
<div class="task-card">
<h4>任务卡 1 · 给类加异常处理 <span class="tag tag-practice"></span></h4>
<p>给 Day1/Day2 写的类加一个自定义异常类(继承 <code>std::exception</code>),
非法参数(如负数年龄)抛出异常,调用方 <code>catch</code> 后打印信息、程序不崩溃。
参照 <a class="filelink" target="_blank"
href="../../../p01/ch07/custom_exception_class.cpp">custom_exception_class.cpp</a></p>
</div>
</section>
<section>
<div class="task-card">
<h4>任务卡 2 · vector + 算法解决实际问题 <span class="tag tag-practice"></span></h4>
<p><code>vector</code> 存一组「学生成绩」,用 <code>sort</code> +
自定义谓词按成绩排序,再用 <code>find_if</code> 找出所有不及格的学生并打印。
参照 <a class="filelink" target="_blank"
href="../../../p01/ch09/s04/predicate.cpp">predicate.cpp</a></p>
</div>
</section>
<section>
<div class="task-card">
<h4>任务卡 3 · 结构体数组的二进制读写 <span class="tag tag-practice"></span></h4>
<p>参照 <a class="filelink" target="_blank"
href="../../../p01/ch08/binary_file_io.cpp">binary_file_io.cpp</a>,把一个结构体数组
(如 5 个学生记录)写入二进制文件,再读出并逐条打印校验,确认与写入前一致。</p>
</div>
</section>
</section>
<!-- ===================== 收尾 ===================== -->
<section>
<h2>当日产出验证</h2>
<div class="verify-box">
<h4>验收标准</h4>
<pre><code class="bash" data-trim>cmake --build build --target all_p01_ch05
cmake --build build --target all_p01_ch07
cmake --build build --target all_p01_ch09_s04</code></pre>
<p>全绿;至少 1 个模板类 + 1 个用 STL 容器/算法解决实际小问题的程序。</p>
</div>
<p class="footer-note"><small>明日预告:Day 4 —— Qt 桌面全景(1 天快速建立骨架认知
+ 动手做一个可跑的小工具)</small></p>
<p><a class="filelink" href="index.html">返回目录</a></p>
</section>
</div>
</div>
<script src="reveal/reveal.js"></script>
<script src="reveal/plugin/highlight/highlight.js"></script>
<script>
Reveal.initialize({
hash: true, controls: true, progress: true, center: true, slideNumber: true,
plugins: [ RevealHighlight ]
});
</script>
</body>
</html>