Day 2

Qt 概述 / 创建项目 / 第一个程序 / 信号槽 → 计算器

C++/Qt 方向实训 · 第 2 / 4 个讲授日

  • 上午(08:30–11:00 讲 / 11:00–12:00 练):Qt 概述 · 创建 Qt 项目 · 第一个 Qt 小程序
  • 下午(13:30–16:00 讲 / 16:00–17:00 作业):信号槽机制 → 项目案例:简易计算器

开始前准备(一次性)

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

Qt 概述 / 创建项目 / 第一个程序

Qt 概述

核心概念

  • Qt = 跨平台 C++ 应用框架:一份代码,Windows/Linux/macOS 原生编译;模块化组织—— 本课程用 Core(对象/容器/IO)、GuiWidgets(桌面控件)。
  • 版本纪律:本实训统一用隔离安装的 Qt 5.14.2.qt514/),不碰系统 Qt——顶层 CMakeLists 有版本断言,连错版本直接 FATAL_ERROR。这是工程上 "锁依赖版本"的真实示范。

出处:仓库根 CMakeLists.txt(版本断言一节)

创建 Qt 项目(CMake 方式)

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 类生成信号槽所需的代码,下一页就见到。

出处:p03/CMakeLists.txtp03/ch02/CMakeLists.txt

第一个 Qt 小程序

核心概念

  • QApplication 是唯一的事件循环拥有者,app.exec() 阻塞进入 事件循环分发系统事件;Q_OBJECT 宏触发 moc 生成信号槽/属性系统所需的元对象代码。

常见的坑

  • 一个进程只能有一个 QApplication;忘记 exec() 窗口一闪而过;
  • 用了信号槽却忘 Q_OBJECT:报 undefined reference—— 链接错误(昨天那页!),报错完全不提"缺宏",知道这一条能省一小时。

出处:p03/ch02/minimal_qt_app.cpp

控件创建与对象树

核心概念

  • QObject 对象树 = Qt 的所有权答案(接昨天"谁负责 delete"):创建控件把 parent 传进去,parent 析构自动 delete 所有子对象——整棵树只需要管好根。

常见的坑

  • 忘记传 parent:对象没人管,真实内存泄漏;
  • move() 手动定位的控件一旦套上布局会被布局接管覆盖。

出处:p03/ch03/button_creation.cpp

★ 栈对象与父子所有权打架


QPushButton btn("我建在栈上");   // 先声明 → 后析构
QWidget window;                  // 后声明 → 先析构
btn.setParent(&window);
// 离开作用域:window 先析构 → 父子树 delete &btn(栈地址!)→ 崩溃
    

核心概念

  • 父子树会 delete 子对象 ⇒ 交给父子树管的对象必须建在堆上。一个对象只能有 一个所有者:给了 parent,就不能再由栈/智能指针/手动 delete 管。

常见的坑

  • 声明顺序反过来时这段代码"恰好不崩"——不要赌析构顺序的运气,规则只有一条。 现场演示:tools/run_qt.sh qt_pitfall_stack_widget --crash(真崩给你看)。

复现示例:examples/pitfall_stack_widget/README

上午 · 课堂练习 11:00–12:00(工程师指导、答疑)

动手:跑通最小程序

任务卡 1 · 跑通并改造最小程序

目标
编译运行 minimal_qt_appbutton_creation;把窗口标题和 按钮文字改成自己的学号+姓名(中文——如果乱码了,记住现象,下午有一页专讲)
验收标准
QT_QPA_PLATFORM=offscreen tools/run_qt.sh minimal_qt_app 退出码 0; 有 X 环境下能看到自己改的中文标题正常显示
基础知识 · 参考
p03/ch02/minimal_qt_app.cpp · p03/ch03/button_creation.cpp

下午 · 讲授 13:30–16:00

信号槽机制 → 计算器案例

信号槽:内置 / 自定义 / lambda

核心概念

  • 观察者模式的 Qt 实现:发送者只 emit,不知道谁在听——对象间彻底解耦;
  • 新语法 &Class::signal 编译期类型检查;旧字符串语法 SIGNAL()/SLOT() 只在运行时检查;连接方式 Direct/Queued/Auto(Day5 跨线程时回收)。

常见的坑

  • 旧语法写错不报编译错误,只是运行时静默连接失败;
  • 重载信号不加 QOverload 消歧义直接编译报错。

出处:p03/ch04/builtin_signal_slot.cpp · custom_signal_slot.cpp · lambda_signal_slot.cpp

★ lambda 槽的生命周期与第五参数


connect(sender, &Sender::sig, [obj]{ obj->f(); });        // ✗ obj 销毁后仍触发
connect(sender, &Sender::sig, obj, [obj]{ obj->f(); });   // ✓ 第五参数=context
    

核心概念

  • 第五参数(context object)决定连接跟谁的生命周期挂钩:传了 context,对象析构时 Qt 自动断开连接。规则:lambda 捕获了某个 QObject,就把它作为第五参数传进去

常见的坑

  • 不传 context:接收方销毁后 lambda 仍被调用,捕获的指针就是悬空指针——UB 可能崩、 也可能"打印出看似正常的旧值",后者更危险(开发机正常、演示现场炸)。

复现示例:examples/pitfall_lambda_capture/README--dangle 看真实悬空访问)

★ 重复 connect:点一次按钮弹两次框

核心概念

  • connect 每调一次新增一条连接,Qt 不去重——同一对信号槽连两次, 槽就被调两次。修复:connect 收拢到构造函数只做一次(首选);或 Qt::UniqueConnection(只对成员函数指针有效,lambda 防不了)。

常见的坑

  • connect 写在会被多次执行的路径上(每次打开对话框都跑一遍的"初始化"函数)—— 现象是"点一次弹两次",第一反应查按钮,根源在连接次数。

复现示例:examples/pitfall_double_connect/README

★ QString 与中文编码

核心概念

  • QString 内部是 UTF-16,和 std::string(字节串)是两种东西, 互转必须指明编码:toUtf8()/fromUtf8();中文字面量建议 QStringLiteral("中文") + 源码文件保存为 UTF-8。

常见的坑

  • 源码不是 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)+ 点击信号槽(刚学的三种写法) + QGridLayout 脚手架排键盘 + QLabel/QLineEdit 显示结果。
  • 项目 = 已学知识点的组装——这是 Day5 综合案例和 Day6–10 项目的预演。

下午 · 课堂作业 16:00–17:00(工程师答疑)

作业:完成计算器

任务卡 2 · 简易计算器

目标
组合按钮 + 信号槽 + QGridLayout 脚手架,做出能加减乘除的最小计算器
验收标准
至少支持两个数 + 一种运算符的计算并正确显示结果;界面不重叠、按钮可点击; connect 全部用新语法且写在构造函数里(今天的坑卡现学现用)
基础知识 · 参考
p03/ch03/button_creation.cpp · p03/ch04/custom_signal_slot.cpp · p03/ch08/custom_widget.cpp(布局参考)

当日产出验证

验收

现场演示计算器完成至少一次正确运算(QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target> 或有 X 环境直接运行);中文界面文字无乱码。

返回目录 · ← 上一天 · 下一天 →