教案对齐官方方案书:重写为 5 天 C++/Qt 教学设计,补齐 6 个 Qt 技术缺口示例
- docs/teaching/OUTLINE.md、slides/day1-5.html、index.html:替换此前自定义的 4 天方案,严格对齐 docs/TRAINING_PLAN_2026.md「C++方向」Day1-5 课堂内容 (Day6-10 为纯项目实作,不在教案范围) - docs/teaching/examples/:官方方案书要求但仓库 p03/(wiki 抓取内容) 未覆盖的 QSS/Model-View/JSON/QThread/QPropertyAnimation/QStateMachine 六个技术点, 各补一个最小可运行示例 + README 教学文档,均已离屏验证跑通;手工维护, 不属于 gen_part3.py 生成产物 - 根 CMakeLists.txt:接入 docs/teaching/examples 构建(BUILD_QT_PART 分支内), 不改动生成器管理的 p01/p03 CMakeLists - 任务卡改为目标/验收标准/基础知识参考三段式模板,custom.css 配套新增样式 - docs/teaching/PRETEST.md:10 题 10 分钟 C++ 摸底测验,验证教案假定的受众 基础是否成立,并与 OUTLINE.md §0 的分层预案挂钩
This commit is contained in:
+161
-195
@@ -2,7 +2,7 @@
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Day 2 · OOP 进阶</title>
|
||||
<title>Day 2 · Qt 开发环境/信号槽/基础控件/布局与 QSS</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">
|
||||
@@ -15,96 +15,89 @@
|
||||
|
||||
<section>
|
||||
<h1>Day 2</h1>
|
||||
<h3>OOP 进阶</h3>
|
||||
<p class="module-badge">Qt 方向实训 · 第 2 / 4 天 · 讲:练 ≈ 1:2.7</p>
|
||||
<h3>Qt 开发环境 / 信号槽 / 基础控件 / 布局与 QSS</h3>
|
||||
<p class="module-badge">C++/Qt 方向实训 · 第 2 / 5 天 · 讲:练 ≈ 1:2.7</p>
|
||||
<ul>
|
||||
<li>模块 A(3 课时):对象模型 / 友元 / 运算符重载</li>
|
||||
<li>模块 B(3 课时):继承与多态</li>
|
||||
<li>模块 A(3 课时):项目结构 / 第一个程序 / 信号槽</li>
|
||||
<li>模块 B(3 课时):常用控件 / 布局管理 / QSS 界面美化 → 计算器案例</li>
|
||||
</ul>
|
||||
<p><small>回顾:Day1 已建立类/构造析构/深浅拷贝的直觉——今天在这个基础上深挖机制</small></p>
|
||||
<div class="verify-box" style="font-size:0.5em;">
|
||||
<h4>开始前准备(一次性)</h4>
|
||||
<pre><code class="bash" data-trim>tools/install_qt_host.sh
|
||||
cmake -S . -B build -G Ninja -DBUILD_QT_PART=ON \
|
||||
-DCMAKE_PREFIX_PATH=$PWD/.qt514/5.14.2/gcc_64 -DCMAKE_BUILD_TYPE=Debug
|
||||
cmake --build build</code></pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===================== 模块 A:讲 ===================== -->
|
||||
<section>
|
||||
<section>
|
||||
<p class="module-badge">模块 A · 讲授 35min</p>
|
||||
<h2>对象模型 / 友元 / 运算符重载 <span class="tag tag-lecture">讲</span></h2>
|
||||
<h2>项目结构 / 信号槽 <span class="tag tag-lecture">讲</span></h2>
|
||||
<ul>
|
||||
<li>this 指针到底解决什么问题</li>
|
||||
<li>友元:突破封装的代价与取舍</li>
|
||||
<li>运算符重载:=、==、[]、()、++/-- 全景</li>
|
||||
<li>最小 Qt 程序结构</li>
|
||||
<li>控件创建与显示</li>
|
||||
<li>内置 / 自定义 / lambda 信号槽</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>this 指针:区分同名 + 隐式参数 <span class="tag tag-lecture">讲</span></h3>
|
||||
<h3>最小 Qt 程序:QApplication + exec() <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
class Person{
|
||||
class Widget : public QWidget {
|
||||
Q_OBJECT // 用到信号槽/属性系统的类都要有这个宏(触发 moc 代码生成)
|
||||
public:
|
||||
Person(string name, int age){
|
||||
this->name = name; // 形参名与成员名相同,靠 this-> 区分
|
||||
this->age = age;
|
||||
}
|
||||
// 成员函数隐含的第一个参数就是 this —— operator= 也是这样实现的
|
||||
Person PersonPlusPerson(Person& person){
|
||||
string newname = this->name + person.name;
|
||||
int newage = this->age + person.age;
|
||||
return Person(newname, newage);
|
||||
}
|
||||
Widget(QWidget* parent = nullptr) : QWidget(parent) {}
|
||||
};
|
||||
</code></pre>
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s04/this_pointer.cpp">p01/ch04/s04/this_pointer.cpp</a></small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>友元:全局函数 / 成员函数 / 整个类 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
class Building{
|
||||
friend void CleanBedRoom(Building& building); // 友元全局函数
|
||||
friend class MyFriend; // 友元类——MyFriend 的所有成员都能访问私有区
|
||||
public:
|
||||
Building();
|
||||
private:
|
||||
string mBedroom;
|
||||
};
|
||||
</code></pre>
|
||||
<p><small>友元是「单向」「不可传递」「不可继承」的特权授予——用得越多,封装边界越模糊,
|
||||
权衡取舍要讲清楚。出处:<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s05/friend_syntax.cpp">p01/ch04/s05/friend_syntax.cpp</a></small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>赋值运算符重载:与拷贝构造的分工 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
Person2& operator=(const Person2& person){
|
||||
// 关键:当前对象已存在,pName 可能已指向堆内存——必须先释放,否则内存泄漏
|
||||
if (this->pName != NULL){ delete[] this->pName; }
|
||||
this->pName = new char[strlen(person.pName) + 1];
|
||||
strcpy(this->pName, person.pName);
|
||||
return *this; // 返回引用才能支持 a = b = c 连续赋值
|
||||
}
|
||||
</code></pre>
|
||||
<p><small>“初始化用拷贝构造,已存在对象再赋值用 operator=”——这条分界线是本节强化重点。
|
||||
出处:<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s06/overload_assignment.cpp">p01/ch04/s06/overload_assignment.cpp</a></small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>前置/后置 ++ 的重载差异 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
Complex& operator++(){ // 前置++:改自身,返回引用
|
||||
mA++; mB++;
|
||||
return *this;
|
||||
}
|
||||
Complex operator++(int){ // 后置++:占位 int 参数只是为了和前置区分重载签名
|
||||
Complex temp = *this; // 必须先拷贝旧值
|
||||
mA++; mB++;
|
||||
return temp; // 返回值(而非引用)——所以后置++开销更大
|
||||
int main(int argc, char* argv[]) {
|
||||
QApplication app(argc, argv); // 整个程序唯一的 QApplication
|
||||
Widget w;
|
||||
w.show();
|
||||
return app.exec(); // 进入事件循环,程序在这里“活着”
|
||||
}
|
||||
</code></pre>
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s06/overload_increment_decrement.cpp">p01/ch04/s06/overload_increment_decrement.cpp</a></small></p>
|
||||
href="../../../p03/ch02/minimal_qt_app.cpp">p03/ch02/minimal_qt_app.cpp</a>(注意仓库版本额外注入了
|
||||
<code>QTimer::singleShot</code> 200ms 自动退出,便于离屏自动化验证,真实项目不需要这一行)</small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>控件创建:两种写法 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
// 第一种:先构造再设属性
|
||||
QPushButton* btn = new QPushButton;
|
||||
btn->setParent(this);
|
||||
btn->setText(QStringLiteral("德玛西亚"));
|
||||
btn->move(100, 100);
|
||||
|
||||
// 第二种:构造时指定文本与父亲
|
||||
QPushButton* btn2 = new QPushButton(QStringLiteral("孙悟空"), this);
|
||||
</code></pre>
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../../../p03/ch03/button_creation.cpp">p03/ch03/button_creation.cpp</a></small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>信号槽:内置 / 自定义 / 重载消歧义 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
class Teacher : public QObject {
|
||||
Q_OBJECT
|
||||
signals:
|
||||
void hungry(); // 无参信号
|
||||
void hungry(QString food); // 带参信号(重载)
|
||||
public slots:
|
||||
void classIsOver(){ emit hungry(); emit hungry(QString("汉堡")); }
|
||||
};
|
||||
// 连接重载信号时必须用 QOverload 显式指定版本
|
||||
connect(t, QOverload<>::of(&Teacher::hungry), s, QOverload<>::of(&Student::treat));
|
||||
connect(t, QOverload<QString>::of(&Teacher::hungry), s, QOverload<QString>::of(&Student::treat));
|
||||
</code></pre>
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../../../p03/ch04/custom_signal_slot.cpp">p03/ch04/custom_signal_slot.cpp</a>;
|
||||
lambda 槽 + 值捕获/引用捕获对比见
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch04/lambda_signal_slot.cpp">lambda_signal_slot.cpp</a></small></p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
@@ -112,32 +105,50 @@ Complex operator++(int){ // 后置++:占位 int 参数只是为了和前
|
||||
<section>
|
||||
<section>
|
||||
<p class="module-badge">模块 A · 练习 95min</p>
|
||||
<h2>动手:运算符重载 <span class="tag tag-practice">练</span></h2>
|
||||
<h2>动手:第一个 Qt 程序 <span class="tag tag-practice">练</span></h2>
|
||||
</section>
|
||||
<section>
|
||||
<div class="task-card">
|
||||
<h4>任务卡 1 · operator= + operator== <span class="tag tag-practice">练</span></h4>
|
||||
<p>给 Day1 写的类(或新写一个含指针成员的类)实现 <code>operator=</code>(注意先
|
||||
释放旧资源)与 <code>operator==</code>,参照
|
||||
<a class="filelink" target="_blank" href="../../../p01/ch04/s06/overload_assignment.cpp">overload_assignment.cpp</a>、
|
||||
<a class="filelink" target="_blank" href="../../../p01/ch04/s06/overload_equality.cpp">overload_equality.cpp</a>。</p>
|
||||
<h4>任务卡 1 · 跑通最小程序 <span class="tag tag-practice">练</span></h4>
|
||||
<dl class="task-spec">
|
||||
<dt>目标</dt>
|
||||
<dd>编译运行 <code>minimal_qt_app</code> 与 <code>button_creation</code></dd>
|
||||
<dt>验收标准</dt>
|
||||
<dd><code>QT_QPA_PLATFORM=offscreen tools/run_qt.sh minimal_qt_app</code> 与
|
||||
<code>button_creation</code> 均退出码为 0,无崩溃</dd>
|
||||
<dt>基础知识 · 参考</dt>
|
||||
<dd><a class="filelink" target="_blank" href="../../../p03/ch02/minimal_qt_app.cpp">p03/ch02/minimal_qt_app.cpp</a> ·
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch03/button_creation.cpp">p03/ch03/button_creation.cpp</a></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<div class="task-card">
|
||||
<h4>任务卡 2 · operator[] 边界检查 <span class="tag tag-practice">练</span></h4>
|
||||
<p>实现一个重载了 <code>operator[]</code> 的简单容器类(固定大小数组包装),
|
||||
越界访问要有明确行为(断言或抛异常),参照
|
||||
<a class="filelink" target="_blank" href="../../../p01/ch04/s06/overload_pointer_operator.cpp">overload_pointer_operator.cpp</a> 的思路。</p>
|
||||
<h4>任务卡 2 · 自定义信号槽改文本 <span class="tag tag-practice">练</span></h4>
|
||||
<dl class="task-spec">
|
||||
<dt>目标</dt>
|
||||
<dd>给一个按钮连接自定义信号槽:点击后改变一个 <code>QLabel</code> 的文本内容</dd>
|
||||
<dt>验收标准</dt>
|
||||
<dd>编译通过;离屏或有 X 环境下点击/模拟点击后 <code>QLabel</code> 文本确实改变;
|
||||
用 <code>connect</code> 语法而非直接函数调用</dd>
|
||||
<dt>基础知识 · 参考</dt>
|
||||
<dd><a class="filelink" target="_blank" href="../../../p03/ch04/custom_signal_slot.cpp">custom_signal_slot.cpp</a>(主参考)·
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch04/lambda_signal_slot.cpp">lambda_signal_slot.cpp</a>(lambda 槽写法对比)</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<div class="task-card">
|
||||
<h4>任务卡 3 · 选做:友元实现 operator<< <span class="tag tag-practice">练</span></h4>
|
||||
<p>参照 <a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s06/overload_with_friend.cpp">overload_with_friend.cpp</a>,
|
||||
为你的类实现 <code>friend ostream& operator<<</code>,讨论「为什么流输出运算符
|
||||
通常不能写成成员函数」(提示:左操作数是 ostream,不是本类对象)。</p>
|
||||
<h4>任务卡 3(选做)· 重载信号消歧义 <span class="tag tag-practice">练</span></h4>
|
||||
<dl class="task-spec">
|
||||
<dt>目标</dt>
|
||||
<dd>仿 <code>builtin_signal_slot.cpp</code>,给一个类定义两个同名不同参的信号,用
|
||||
<code>QOverload</code> 消歧义连接</dd>
|
||||
<dt>验收标准</dt>
|
||||
<dd>两个重载信号都能各自触发对应的槽</dd>
|
||||
<dt>基础知识 · 参考</dt>
|
||||
<dd><a class="filelink" target="_blank" href="../../../p03/ch04/builtin_signal_slot.cpp">p03/ch04/builtin_signal_slot.cpp</a></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
@@ -146,137 +157,91 @@ Complex operator++(int){ // 后置++:占位 int 参数只是为了和前
|
||||
<section>
|
||||
<section>
|
||||
<p class="module-badge">模块 B · 讲授 35min</p>
|
||||
<h2>继承与多态 <span class="tag tag-lecture">讲</span></h2>
|
||||
<ul>
|
||||
<li>继承中构造/析构的顺序</li>
|
||||
<li>同名成员遮蔽 vs 重写</li>
|
||||
<li>虚继承解决菱形问题</li>
|
||||
<li>upcast 裁切问题 → virtual 解决</li>
|
||||
<li>纯虚函数/抽象类、虚析构的意义</li>
|
||||
</ul>
|
||||
<h2>常用控件 / 布局管理 / QSS 美化 <span class="tag tag-lecture">讲</span></h2>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>构造先基类后派生类,析构相反 <span class="tag tag-lecture">讲</span></h3>
|
||||
<h3>常用控件:QLabel 显示图片 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
class A{ public: A(){cout<<"A构造\n";} ~A(){cout<<"A析构\n";} };
|
||||
class B : public A{ public: B(){cout<<"B构造\n";} ~B(){cout<<"B析构\n";} };
|
||||
class C : public B{ public: C(){cout<<"C构造\n";} ~C(){cout<<"C析构\n";} };
|
||||
|
||||
int main(){ C c; }
|
||||
// 输出顺序:A构造 → B构造 → C构造 → C析构 → B析构 → A析构
|
||||
QPixmap pixmap;
|
||||
pixmap.load(":/Image/boat.jpg"); // 从 Qt 资源系统(.qrc)加载
|
||||
QLabel* label = new QLabel(this);
|
||||
label->setPixmap(pixmap);
|
||||
</code></pre>
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s07/inheritance_ctor_dtor_order.cpp">p01/ch04/s07/inheritance_ctor_dtor_order.cpp</a></small></p>
|
||||
href="../../../p03/ch08/label_pixmap.cpp">p03/ch08/label_pixmap.cpp</a>;
|
||||
HTML 富文本标签见 <a class="filelink" target="_blank" href="../../../p03/ch08/label_text_html.cpp">label_text_html.cpp</a></small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>同名成员:默认遮蔽,需类名限定 <span class="tag tag-lecture">讲</span></h3>
|
||||
<h3>自定义控件与布局:QHBoxLayout <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
class Base{ public: Base():mParam(0){} int mParam; };
|
||||
class Derived : public Base{
|
||||
public:
|
||||
Derived():mParam(10){}
|
||||
void Print(){
|
||||
cout << Base::mParam << endl; // 必须类名限定才能访问被遮蔽的基类成员
|
||||
cout << mParam << endl; // 默认访问派生类自己的
|
||||
}
|
||||
int mParam;
|
||||
};
|
||||
QSpinBox* spin = new QSpinBox(this);
|
||||
QSlider* slider = new QSlider(Qt::Horizontal, this);
|
||||
QHBoxLayout* layout = new QHBoxLayout(this);
|
||||
layout->addWidget(spin);
|
||||
layout->addWidget(slider);
|
||||
// spin 变化 → slider 跟随,slider 变化 → spin 跟随
|
||||
connect(spin, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), slider, &QSlider::setValue);
|
||||
connect(slider, &QSlider::valueChanged, spin, &QSpinBox::setValue);
|
||||
</code></pre>
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s07/same_name_members.cpp">p01/ch04/s07/same_name_members.cpp</a></small></p>
|
||||
href="../../../p03/ch08/custom_widget.cpp">p03/ch08/custom_widget.cpp</a>——布局管理器负责排布,
|
||||
不用手算控件坐标。</small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>菱形继承 → 虚继承 <span class="tag tag-lecture">讲</span></h3>
|
||||
<h3>QSS 界面美化(教学补充) <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
class BigBase{ public: int mParam; };
|
||||
class Base1 : public BigBase{};
|
||||
class Base2 : public BigBase{};
|
||||
class Derived : public Base1, public Base2{};
|
||||
// derived.mParam 二义——两份 BigBase 子对象;须写 derived.Base1::mParam 消歧义
|
||||
// 虚继承(public virtual BigBase)能让两条路径共享同一份 BigBase 子对象
|
||||
okBtn->setObjectName("okButton");
|
||||
setStyleSheet(
|
||||
"QPushButton#okButton { background-color: #2f855a; border-radius: 6px; color: white; }"
|
||||
"QPushButton#okButton:hover { background-color: #276749; }"
|
||||
);
|
||||
</code></pre>
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s07/diamond_virtual_inheritance.cpp">diamond_virtual_inheritance.cpp</a>
|
||||
及 <a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s07/diamond_virtual_inheritance_2.cpp">_2.cpp</a>(虚继承版本对比)</small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>upcast 裁切问题:不加 virtual 就没有多态 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
// 问题版:Animal::speak_1 不是 virtual
|
||||
class Animal{ public: void speak_1(){ cout<<"动物在唱歌...\n"; } };
|
||||
class Dog : public Animal{ public: void speak_1(){ cout<<"小狗在唱歌...\n"; } };
|
||||
void DoBussiness(Animal& animal){ animal.speak_1(); }
|
||||
void test(){ Dog dog; DoBussiness(dog); } // 输出「动物在唱歌」——静态绑定,不是想要的结果
|
||||
|
||||
// 解决版:都加 virtual → 动态绑定,输出「小狗在唱歌」
|
||||
</code></pre>
|
||||
<p><small>问题版出处:<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s08/upcast_problem.cpp">upcast_problem.cpp</a>;解决版:
|
||||
<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s08/virtual_function_solution.cpp">virtual_function_solution.cpp</a></small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>纯虚函数、抽象类、虚析构的意义 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
class People{
|
||||
public:
|
||||
virtual void showName() = 0; // 纯虚函数 → People 是抽象类,不能实例化
|
||||
virtual ~People(){ cout<<"析构 People\n"; } // 虚析构:基类指针 delete 派生类对象时能正确调用派生类析构
|
||||
};
|
||||
class Worker : public People{
|
||||
public:
|
||||
virtual void showName(){ /* ... */ }
|
||||
~Worker(){ cout<<"析构 Worker\n"; delete[] pName; }
|
||||
private:
|
||||
char* pName;
|
||||
};
|
||||
People* p = new Worker;
|
||||
delete p; // 若 ~People() 不是 virtual,只会调用 ~People,Worker::pName 泄漏
|
||||
</code></pre>
|
||||
<p><small>强化点:<code class="filelink">ERRATA.md [58954469:5] abstract-instantiation</code>
|
||||
——wiki 原文一度把带纯虚析构的类直接实例化,这是编译错误。出处:
|
||||
<a class="filelink" target="_blank" href="../../../p01/ch04/s08/pure_virtual_dtor.cpp">pure_virtual_dtor.cpp</a>、
|
||||
<a class="filelink" target="_blank" href="../../../p01/ch04/s08/virtual_dtor_purpose.cpp">virtual_dtor_purpose.cpp</a></small></p>
|
||||
href="../examples/qss_styling/qt_qss_styling.cpp">docs/teaching/examples/qss_styling/qt_qss_styling.cpp</a>
|
||||
(<a class="filelink" target="_blank" href="../examples/qss_styling/README.md">配套 README</a>)——
|
||||
仓库 <code>p03/</code> 的 wiki 抓取内容没有 QSS,这是本轮补写的教学补充示例,已离屏验证跑通。</small></p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<!-- ===================== 模块 B:练 ===================== -->
|
||||
<!-- ===================== 模块 B:练(计算器案例) ===================== -->
|
||||
<section>
|
||||
<section>
|
||||
<p class="module-badge">模块 B · 练习 95min</p>
|
||||
<h2>动手:继承与多态 <span class="tag tag-practice">练</span></h2>
|
||||
<p class="module-badge">模块 B · 练习 95min(计算器案例,练习任务,非预建方案)</p>
|
||||
<h2>动手:做一个计算器 <span class="tag tag-practice">练</span></h2>
|
||||
</section>
|
||||
<section>
|
||||
<div class="task-card">
|
||||
<h4>任务卡 1 · 复现并修复 upcast 裁切 <span class="tag tag-practice">练</span></h4>
|
||||
<p>先跑 <a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s08/upcast_problem.cpp">upcast_problem.cpp</a> 观察输出,
|
||||
再手动加 <code>virtual</code> 关键字重新编译,对比两次输出差异,写清楚“静态绑定 vs 动态绑定”
|
||||
各自发生在什么时候。</p>
|
||||
<h4>任务卡 1 · 计算器雏形 <span class="tag tag-practice">练</span></h4>
|
||||
<dl class="task-spec">
|
||||
<dt>目标</dt>
|
||||
<dd>组合 <code>button_creation.cpp</code>(数字/运算符按钮)+ <code>custom_signal_slot.cpp</code>
|
||||
(点击信号槽)+ <code>custom_widget.cpp</code>(布局排布按钮),做出一个能加减乘除的最小计算器</dd>
|
||||
<dt>验收标准</dt>
|
||||
<dd>至少支持两个数 + 一种运算符的计算并在 <code>QLabel</code>/<code>QLineEdit</code> 上正确显示结果;
|
||||
界面不重叠、按钮可点击</dd>
|
||||
<dt>基础知识 · 参考</dt>
|
||||
<dd><a class="filelink" target="_blank" href="../../../p03/ch03/button_creation.cpp">button_creation.cpp</a> ·
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch04/custom_signal_slot.cpp">custom_signal_slot.cpp</a> ·
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch08/custom_widget.cpp">custom_widget.cpp</a></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<div class="task-card">
|
||||
<h4>任务卡 2 · 基类指针管理派生类对象 <span class="tag tag-practice">练</span></h4>
|
||||
<p>设计一个含虚析构的基类 + 2 个派生类(各自持有一个堆内存成员),用基类指针数组
|
||||
管理多个对象,逐个 <code>delete</code>,通过析构函数里的打印验证:(1) 调用的是派生类
|
||||
析构 (2) 没有内存泄漏(可用打印计数或 valgrind 验证)。</p>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<div class="task-card">
|
||||
<h4>任务卡 3 · 选做:普通继承 vs 虚继承对比 <span class="tag tag-practice">练</span></h4>
|
||||
<p>对照 <a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s07/diamond_virtual_inheritance.cpp">diamond_virtual_inheritance.cpp</a>
|
||||
与 <a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s07/diamond_virtual_inheritance_2.cpp">_2.cpp</a>,
|
||||
打印 <code>sizeof(Derived)</code> 并解释两种继承方式下对象体积差异的原因。</p>
|
||||
<h4>任务卡 2 · 用 QSS 美化计算器 <span class="tag tag-practice">练</span></h4>
|
||||
<dl class="task-spec">
|
||||
<dt>目标</dt>
|
||||
<dd>给任务卡 1 的计算器套一份 QSS 样式(数字键/运算键/等号键区分颜色)</dd>
|
||||
<dt>验收标准</dt>
|
||||
<dd>至少 2 类按钮有不同的 <code>background-color</code>;至少 1 个按钮有
|
||||
<code>:hover</code> 或 <code>:pressed</code> 效果</dd>
|
||||
<dt>基础知识 · 参考</dt>
|
||||
<dd><a class="filelink" target="_blank" href="../examples/qss_styling/qt_qss_styling.cpp">qt_qss_styling.cpp</a> ·
|
||||
<a class="filelink" target="_blank" href="../examples/qss_styling/README.md">README</a></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
@@ -285,15 +250,16 @@ delete p; // 若 ~People() 不是 virtual,只会调用 ~People,Worker::pNa
|
||||
<section>
|
||||
<h2>当日产出验证</h2>
|
||||
<div class="verify-box">
|
||||
<h4>验收标准</h4>
|
||||
<pre><code class="bash" data-trim>cmake --build build --target all_p01_ch04_s06
|
||||
cmake --build build --target all_p01_ch04_s07
|
||||
cmake --build build --target all_p01_ch04_s08</code></pre>
|
||||
<p>全绿;产出「基类指针管理派生类对象、正确释放」的可运行小程序。</p>
|
||||
<h4>现场演示</h4>
|
||||
<pre><code class="bash" data-trim>QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
|
||||
# 有 X 环境:去掉 QT_QPA_PLATFORM=offscreen 直接看窗口</code></pre>
|
||||
<p>现场演示计算器,至少完成一次正确运算 + QSS 美化。</p>
|
||||
</div>
|
||||
<p class="footer-note"><small>明日预告:Day 3 —— 泛型与标准库(模板 / 类型转换 /
|
||||
异常 / IO 流 / STL)</small></p>
|
||||
<p><a class="filelink" href="index.html">返回目录</a></p>
|
||||
<p class="footer-note"><small>明天 Day3:高级控件 Model/View / 事件机制 / 文件 IO 与 JSON ——
|
||||
大纲:<a class="filelink" href="../OUTLINE.md">docs/teaching/OUTLINE.md</a></small></p>
|
||||
<p><a class="filelink" href="index.html">返回目录</a> ·
|
||||
<a class="filelink" href="day1.html">← 上一天</a> ·
|
||||
<a class="filelink" href="day3.html">下一天 →</a></p>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user