Files
scuc-qt-course/docs/teaching/slides/day3.html
T
张宗平 b89819d5db 新增 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)供离线播放,代码链接可点开查看源码。
2026-07-01 16:51:12 +08:00

260 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>