04-信号与槽:Qt 的“事件连线”¶
目录页:见 Qt 教程目录
目标¶
- 用一句话解释“信号与槽”在解决什么问题
- 会写最常见的
connect:按钮点击 → 执行一段逻辑 - 知道新手最容易踩的坑:对象生命周期、重复连接、线程误用
先决知识(可选)¶
- 会创建并运行一个 Widgets 工程(见 02 章)
1. 事件怎么从 A 传到 B:别把代码写成一团¶
你很快会遇到这种需求: - 用户点了按钮(A) - 你要更新标签、列表、或者触发网络请求(B)
如果你把所有逻辑都写进按钮类、窗口类里,很快就会变成“到处互相引用”的一团乱。
2. 信号与槽是什么:把“发生了什么”和“怎么处理”拆开¶
- 专业名称:Signals & Slots(信号与槽)
- 类比:插排的插头和插座。信号像“插头抛出一个事件”,槽像“插座接住后做事”
- 作用:解耦:发消息的人不需要知道谁来处理;处理的人也不需要知道消息从哪来
- 规则/坑点:连接(connect)依赖对象存在;对象销毁后连接会自动失效,但你要避免把 lambda 里捕获到的东西用成悬空
核心句子: - 信号:某件事发生了 - 槽:发生后要做什么 - connect:把它们连起来
3. 最常见 connect 写法(Qt5/Qt6 都通用)¶
这是新手最推荐的写法:函数指针 + lambda。
例子:按钮点击让计数 +1,并更新标签。
#include <QApplication>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QWidget w;
w.setWindowTitle("Signal/Slot Demo");
auto *layout = new QVBoxLayout(&w);
auto *label = new QLabel("count=0");
auto *btn = new QPushButton("Add");
layout->addWidget(label);
layout->addWidget(btn);
int count = 0;
QObject::connect(btn, &QPushButton::clicked, &w, [&] {
count += 1;
label->setText(QString("count=%1").arg(count));
});
w.show();
return app.exec();
}
你应该把这段理解成:
- btn 发出 clicked 信号
- lambda 是“要做的事”(槽)
- &w 是连接的“归属对象”(context object):当 w 销毁时,连接自动断开
4. 连接的三种常见形式:你会在项目里都见到¶
4.1 信号 → 成员函数(槽函数)¶
当你的逻辑比较多,建议写成成员函数:
- 作用:代码更清晰、可测试、可复用
- 坑点:槽函数签名要匹配信号参数
4.2 信号 → lambda(轻量逻辑首选)¶
适合: - 逻辑很短 - 只影响当前窗口里的几个控件
坑点:
- lambda 捕获引用(&)时要确保被引用的变量生命周期足够长
4.3 自动连接(初学者不建议优先用)¶
Qt Designer 有一套“按命名规则自动连接”的机制,但新手容易被“魔法”搞懵。 结论: - 入门阶段先把 connect 写明白,再考虑自动连接
5. 线程相关的一个关键结论:UI 只能在主线程改¶
- 结论:任何 QWidget 的操作都应该发生在主线程
- 正确姿势:子线程做耗时工作,完成后用信号把结果发回主线程更新 UI(见 09 章)
常见坑¶
-
错误:重复 connect 同一个信号到同一个槽(例如初始化函数被调用多次)
结果:点一次按钮触发多次逻辑
正确:保证只连接一次;或者在重新连接前先断开(或用更清晰的生命周期管理) -
错误:lambda 捕获了已经销毁的对象指针
结果:随机崩溃
正确:用 context object(例子里的&w),并尽量避免捕获裸指针;需要的话用QPointer做保护 -
错误:在子线程里直接改 UI
结果:不稳定、崩溃
正确:子线程发信号回主线程,由主线程更新 UI(见 09 章)
小练习¶
练习 1:做一个“+ / -”计数器¶
- 题目:两个按钮 Add/Sub,标签显示当前 count
- 输入/输出:点击 Add 增加,点击 Sub 减少
- 约束:必须用
connect+ lambda - 提示:两次 connect,改同一个
count - 目标:把 connect 写熟
练习 2:把逻辑移到成员函数¶
- 题目:把计数器逻辑从 lambda 改成窗口类的成员函数
- 输入/输出:行为不变
- 约束:槽函数必须是成员函数
- 提示:窗口类里存
count和控件指针 - 目标:练“结构化写法”
小结¶
- 信号与槽让“发生了什么”和“怎么处理”解耦
- 新手最推荐:函数指针 + lambda,并提供 context object
- UI 只能在主线程改;耗时任务用信号把结果带回来
下一篇:见 05-Widget入门-窗口按钮布局