b89819d5db
大纲按「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)供离线播放,代码链接可点开查看源码。
260 lines
12 KiB
HTML
260 lines
12 KiB
HTML
<!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<class NameType, class AgeType>
|
||
class Person{
|
||
public:
|
||
Person(NameType name, AgeType age){ mName = name; mAge = age; }
|
||
void showPerson(){ cout << mName << " " << mAge << endl; }
|
||
NameType mName; AgeType mAge;
|
||
};
|
||
|
||
// Person P1("德玛西亚", 18); // 非法——类模板不支持类型自动推导
|
||
Person<string, int> 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<Animal*>(dog01); // 子类→父类,合法(其实用不着 dynamic_cast)
|
||
|
||
// Dog* dog02 = dynamic_cast<Dog*>(animal01); // 父类→子类:运行期检查,失败返回 nullptr
|
||
// Animal* x = dynamic_cast<Animal*>(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<T1, T2></code> 或简易 <code>Stack<T></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 << mName << "对象被创建!\n"; }
|
||
~Person(){ cout << mName << "对象被析构!\n"; }
|
||
string mName;
|
||
};
|
||
void TestFunction(){
|
||
Person p1("aaa"), p2("bbb"), p3("ccc");
|
||
throw 10; // 抛出异常时,p3/p2/p1 按逆序自动析构——这就是「栈解旋」
|
||
}
|
||
int main(){ try{ TestFunction(); } catch(...){ cout<<"异常被捕获!\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 << e.what(); } // 若改成 throw out_of_range(...) 会走这里
|
||
catch (MyOutOfRange e){ cout << 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 > 5; } };
|
||
vector<int>::iterator it = find_if(v.begin(), v.end(), GreaterThenFive());
|
||
|
||
class MyCompare{ public: bool operator()(int a,int b){ return a > 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>
|