C++/Qt 方向实训 · 第 2 / 4 个讲授日
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
上午 · 讲授 08:30–11:00
Core(对象/容器/IO)、Gui、Widgets(桌面控件)。.qt514/),不碰系统
Qt——顶层 CMakeLists 有版本断言,连错版本直接 FATAL_ERROR。这是工程上
"锁依赖版本"的真实示范。出处:仓库根 CMakeLists.txt(版本断言一节)
find_package(Qt5 5.14 REQUIRED COMPONENTS Core Gui Widgets)
add_executable(minimal_qt_app minimal_qt_app.cpp)
set_target_properties(minimal_qt_app PROPERTIES AUTOMOC ON)
target_link_libraries(minimal_qt_app PRIVATE Qt5::Core Qt5::Gui Qt5::Widgets)
find_package 概念今天落地:CMake 按 CMAKE_PREFIX_PATH
找到 Qt;AUTOMOC 自动跑 moc(元对象编译器)——它为 Q_OBJECT
类生成信号槽所需的代码,下一页就见到。QApplication 是唯一的事件循环拥有者,app.exec() 阻塞进入
事件循环分发系统事件;Q_OBJECT 宏触发 moc 生成信号槽/属性系统所需的元对象代码。QApplication;忘记 exec() 窗口一闪而过;Q_OBJECT:报 undefined reference——
链接错误(昨天那页!),报错完全不提"缺宏",知道这一条能省一小时。move() 手动定位的控件一旦套上布局会被布局接管覆盖。
QPushButton btn("我建在栈上"); // 先声明 → 后析构
QWidget window; // 后声明 → 先析构
btn.setParent(&window);
// 离开作用域:window 先析构 → 父子树 delete &btn(栈地址!)→ 崩溃
tools/run_qt.sh qt_pitfall_stack_widget --crash(真崩给你看)。上午 · 课堂练习 11:00–12:00(工程师指导、答疑)
minimal_qt_app 与 button_creation;把窗口标题和
按钮文字改成自己的学号+姓名(中文——如果乱码了,记住现象,下午有一页专讲)QT_QPA_PLATFORM=offscreen tools/run_qt.sh minimal_qt_app 退出码 0;
有 X 环境下能看到自己改的中文标题正常显示下午 · 讲授 13:30–16:00
emit,不知道谁在听——对象间彻底解耦;&Class::signal 编译期类型检查;旧字符串语法
SIGNAL()/SLOT() 只在运行时检查;连接方式 Direct/Queued/Auto(Day5 跨线程时回收)。QOverload 消歧义直接编译报错。出处:p03/ch04/builtin_signal_slot.cpp · custom_signal_slot.cpp · lambda_signal_slot.cpp
connect(sender, &Sender::sig, [obj]{ obj->f(); }); // ✗ obj 销毁后仍触发
connect(sender, &Sender::sig, obj, [obj]{ obj->f(); }); // ✓ 第五参数=context
复现示例:examples/pitfall_lambda_capture/
(README,
--dangle 看真实悬空访问)
connect 每调一次新增一条连接,Qt 不去重——同一对信号槽连两次,
槽就被调两次。修复:connect 收拢到构造函数只做一次(首选);或
Qt::UniqueConnection(只对成员函数指针有效,lambda 防不了)。QString 内部是 UTF-16,和 std::string(字节串)是两种东西,
互转必须指明编码:toUtf8()/fromUtf8();中文字面量建议
QStringLiteral("中文") + 源码文件保存为 UTF-8。QString(char*) 隐式转换中文字面量 → 界面乱码。
上午改学号姓名乱码的同学:根源就在这页。Day4 读写文件时编码问题还会再来一次。
auto* grid = new QGridLayout(window); // 布局原理明天下午讲透
grid->addWidget(btn7, 0, 0); grid->addWidget(btn8, 0, 1); // 今天先会用:
grid->addWidget(btn9, 0, 2); grid->addWidget(btnDiv, 0, 3); // 行、列坐标摆键盘
button_creation)+ 点击信号槽(刚学的两种写法:
lambda+context 批量绑定数字/运算符键,成员函数指针绑定等号/清空/退格)
+ QGridLayout 脚手架排键盘 + QLineEdit 显示结果。教师完整参考实现(现场先跑成品,再拆代码):
examples/calculator/qt_calculator.cpp
(README,
QT_QPA_PLATFORM=offscreen tools/run_qt.sh qt_calculator 自测 5 组用例)。
课上不要直接展示源码给学生抄——讲思路,学生自己实现(任务卡 2)。
.ui 表单(纯 XML),
uic 编译期翻译成 ui_xxx.h,setupUi(this)
一次建好控件树——业务逻辑(onDigit/onOperator……)
与代码布局版一字不差,换的只是"界面从哪来"。setupUi() 内部会 connectSlotsByName(this):类里若有
on_<objectName>_<signal>() 槽会被自动连接——这是
信号槽之外、Designer 工作流特有的第三种"连接方式"。objectName 从 btnClear 改成
btnReset:on_btnClear_clicked() 不报编译错误、不报运行时
错误,只是从此再也不会被调用——静默失败坑点家族的第三个成员(前两个:旧
SIGNAL()/SLOT() 字符串语法、重复 connect)。
本参考实现故意改用显式 connect() 规避这个坑。对照实现:examples/calculator_designer/qt_calculator_designer.cpp + calculator_designer.ui (README)—— 快速原型/界面调整用 Designer 更直观;控件数量随数据动态变化(如 Day5 表格行) 必须手写代码搭建,两条路线按场景选,不是谁取代谁。
下午 · 课堂作业 16:00–17:00(工程师答疑)
现场演示计算器完成至少一次正确运算(QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
或有 X 环境直接运行);中文界面文字无乱码。
钩子 · 信号槽的幕后:打开 build/ 里真实的
moc_*.cpp,看看 moc 到底生成了什么代码。
明天 Day3:QMainWindow / QDialog / 布局管理器 / 常用控件——计算器要升级成"正经软件"。