Qt 信号与槽 [ 2 ]
信号与槽的连接方式
一对一的连接方式
主要有两种形式,分别是:
- 一个信号连接一个槽;
- 一个信号连接一个信号。
(1)一个信号连接一个槽
示例:
在"widget.h"中声明信号和槽以及信号发射函数;
#ifndef WIDGET_H #define WIDGET_H #include <QWidget> class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); void EmitSignal(); // 信号发射函数 signals: void MySignal(); // 信号函数声明 public slots: void MySlot(); // 槽函数声明 }; #endif // WIDGET_H在"widget.cpp"中实现槽函数,信号发射函数以及连接信号和槽;
#include "widget.h" #include <QDebug> Widget::Widget(QWidget *parent) : QWidget(parent) { // MySignal信号对应一个槽函数:MySlot connect(this, &Widget::MySignal, this, &Widget::MySlot); EmitSignal(); } // 槽函数的实现 void Widget::MySlot() { qDebug() << "好好学习,天天向上!"; } // 信号发射函数的实现 void Widget::EmitSignal() { emit MySignal(); }会遇到的问题:耦合度偏高,发送方和接收方逻辑强绑定,如果后续要加额外处理,只能改原有业务代码,不能灵活扩展。一旦链路变长,后期维护时调用关系不直观,不好梳理流程。
适用场景:最标准、最稳妥的基础用法。适合单一职责的绑定:一个触发动作只对应一个处理逻辑,比如按钮点击只绑定一次业务函数、单个控件状态变化只更新一个界面模块。不需要扩展、不需要广播通知的场景首选。
(2)一个信号连接另一个信号
示例:在上述示例的基础上,在"widget.cpp"文件中添加如下代码:
#include "widget.h" #include <QDebug> #include <QPushButton> Widget::Widget(QWidget *parent) : QWidget(parent) { QPushButton *btn = new QPushButton("按钮", this); resize(800, 600); connect(this, &Widget::MySignal, this, &Widget::MySlot); // 信号与信号的连接 connect(btn, &QPushButton::clicked, this, &Widget::MySignal); EmitSignal(); } void Widget::MySlot() { qDebug() << "好好学习,天天向上!"; } void Widget::EmitSignal() { emit MySignal(); }会遇到的问题:中间多了一层转发,调试链路变长,断点跟进去看不到直接业务槽,排查逻辑流转麻烦。没有实际业务处理,只是透传,滥用会造成信号链路嵌套过深,代码可读性变差;且无法在转发过程中做数据加工,只能原参数透传。
适用场景:用于模块解耦、层级中转。比如内层控件信号不想暴露给外层窗口,通过中间类信号转发;或者封装组件时,把内部原生信号重新对外暴露成自定义信号,隔离内部实现,外层不用依赖内层控件头文件。
一对多
一个信号连接多个槽
示例:
(1)在"widget.h"头文件中声明一个信号和三个槽;
#ifndef WIDGET_H #define WIDGET_H #include <QWidget> class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); void EmitSignal(); // 信号发射函数 signals: void MySignal(); // 信号函数声明 public slots: void MySlot_1(); // 槽函数1声明 void MySlot_2(); // 槽函数2声明 void MySlot_3(); // 槽函数3声明 }; #endif // WIDGET_H(2)在"widget.cpp"文件中实现槽函数以及连接信号和槽;
#include "widget.h" #include <QDebug> #include <QPushButton> Widget::Widget(QWidget *parent) : QWidget(parent) { QPushButton *btn = new QPushButton("按钮", this); btn->move(100,100); resize(800,600); connect(this,&Widget::MySignal,this,&Widget::MySlot_1); // MySignal信号 连接 槽1 connect(this,&Widget::MySignal,this,&Widget::MySlot_2); // MySignal信号 连接 槽2 connect(this,&Widget::MySignal,this,&Widget::MySlot_3); // MySignal信号 连接 槽3 EmitSignal(); // 发射信号 } void Widget::MySlot_1() { qDebug() << "MySlot_1"; } void Widget::MySlot_2() { qDebug() << "MySlot_2"; } void Widget::MySlot_3() { qDebug() << "MySlot_3"; } void Widget::EmitSignal() { emit MySignal(); }会遇到的问题:槽执行顺序不确定,Qt 不保证连接顺序,一旦槽之间有变量依赖,必出偶现 bug。同步直连模式下,某个槽卡顿、崩溃会阻塞后面所有槽执行;还容易出现重复连接,导致同个槽被执行多次。整体无法精准控制分发流程。
适用场景:只适合无依赖的通知广播场景。比如数据模型更新后,同时通知列表刷新、状态栏更新、日志记录;主题切换信号同时更新所有子控件样式。特点:各个槽互相独立、谁先执行都不影响业务。
多对一
多个信号连接一个槽函数
示例:
(1)在"widget.h"头文件中声明两个信号以及一个槽;
#ifndef WIDGET_H #define WIDGET_H #include <QWidget> class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); void EmitSignal(); // 信号发射函数 signals: void MySignal_1(); // 信号函数1声明 void MySignal_2(); // 信号函数2声明 public slots: void MySlot(); // 槽函数声明 }; #endif // WIDGET_H(2)在"widget.cpp"文件中实现槽函数以及连接信号和槽;
#include "widget.h" #include <QDebug> #include <QPushButton> Widget::Widget(QWidget *parent) : QWidget(parent) { QPushButton *btn = new QPushButton("按钮", this); btn->move(100, 100); resize(800, 600); // 三个不同信号连接同一个槽 connect(this, &Widget::MySignal_1, this, &Widget::MySlot); // MySignal_1信号 连接 槽 connect(this, &Widget::MySignal_2, this, &Widget::MySlot); // MySignal_2信号 连接 槽 connect(btn, &QPushButton::clicked, this, &Widget::MySlot); // 按钮信号 连接 槽 EmitSignal(); // 发射信号 } // 槽函数的实现 void Widget::MySlot() { qDebug() << "MySlot_1"; } // 发射信号 void Widget::EmitSignal() { emit MySignal_1(); // 发射信号1 emit MySignal_2(); // 发射信号2 }会遇到的问题:槽函数分不清到底是哪个信号触发,必须依赖sender()判断发送者,代码侵入且不够优雅;多个信号参数不一致时,容易出现参数不匹配、隐式转换报错。逻辑全部挤在一个槽里,分支变多后臃肿难维护。
适用场景:适合同类行为统一处理。比如多个功能相似的按钮共用同一个点击处理槽、多个状态变更信号统一走同一个告警提示、多个设备节点信号统一汇总到一个数据接收函数,用来简化重复代码、统一入口处理。
信号与槽的断开
使用disconnect即可完成断开。disconnect的用法和connect基本一致。
示例:
#include "widget.h" #include <QDebug> #include <QString> #include <QPushButton> Widget::Widget(QWidget *parent) : QWidget(parent) { QPushButton *btn = new QPushButton("按钮", this); btn->move(100,100); resize(800,600); // 信号与槽的连接 connect(btn, &QPushButton::clicked, this, &Widget::close); // 断开信号与槽的连接 disconnect(btn, &QPushButton::clicked, this, &Widget::close); }Qt4 版本信号与槽的连接
Qt4 中的connect用法和 Qt5 相比是更复杂的,需要搭配SIGNAL和SLOT宏来完成。而且缺少必要的函数类型的检查,使代码更容易出错。
示例:(1)在"widget.h"头文件中声明信号和槽:
#ifndef WIDGET_H #define WIDGET_H #include <QWidget> class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); void EmitSignal(); // 信号发射函数 signals: void MySignal(); // 信号函数声明 public slots: void MySlot(); // 槽函数声明 }; #endif // WIDGET_H(2)在"widget.cpp"文件中实现槽函数以及连接信号与槽:
#include "widget.h" #include <QDebug> Widget::Widget(QWidget *parent) : QWidget(parent) { // Qt4版本的信号与槽的连接 connect(this, SIGNAL(MySignal()), this, SLOT(MySlot())); EmitSignal(); // 发射信号 } // 槽函数的实现 void Widget::MySlot() { qDebug() << "MySlot()"; } // 信号发射函数的实现 void Widget::EmitSignal() { emit MySignal(); }Qt4 版本信号与槽连接的优缺点:
- 优点:参数直观;
- 缺点:参数类型不做检测;
示例:
#include "widget.h" #include <QDebug> Widget::Widget(QWidget *parent) : QWidget(parent) { // Qt4版本的信号与槽的连接 connect(this, SIGNAL(MySignal()), this, SLOT(MySlot(QString))); EmitSignal(); // 发射信号 } void Widget::MySlot() { qDebug() << "MySlot()"; } void Widget::EmitSignal() { emit MySignal(); }原则上,槽函数的参数类型要和信号函数参数一一对应。但是对于 Qt4 版本来说,此处构建不会有任何错误,并且可以运行。
使用 Lambda 表达式定义槽函数
Qt5 在 Qt4 的基础上提高了信号与槽的灵活性,允许使用任意函数作为槽函数。但如果想更方便的编写槽函数,比如在编写函数时连函数名都不想定义,则可以通过 Lambda 表达式来达到这个目的。
Lambda 表达式是 C++11 增加的特性。C++11 中的 Lambda 表达式用于定义并创建匿名的函数对象,以简化编程工作。
Lambda 表达式的语法格式如下:
[capture] (params) opt -> ret { Function body };说明:
| 组成部分 | 说明 |
|---|---|
capture | 捕获列表 |
params | 参数表 |
opt | 函数选项 |
ret | 返回值类型 |
Function body | 函数体 |
1、局部变量引入方式[ ]
[]:标识一个 Lambda 表达式的开始,不可省略。
| 符号 | 说明 |
|---|---|
[] | 局部变量捕获列表。Lambda 表达式不能访问外部函数体的任何局部变量 |
[a] | 在函数体内部使用值传递的方式访问变量a |
[&b] | 在函数体内部使用引用传递的方式访问变量b |
[=] | 函数外的所有局部变量都通过值传递的方式使用,函数体内使用的是副本 |
[&] | 以引用的方式使用 Lambda 表达式外部的所有变量 |
[=, &foo] | foo使用引用方式,其余是值传递的方式 |
[&, foo] | foo使用值传递方式,其余是引用传递 |
[this] | 在函数内部可以使用类的成员函数和成员变量,=和&形式也会默认引入 |
说明:
由于使用引用方式捕获对象会有局部变量释放了而 Lambda 函数还没有被调用的情况。也就是说:通过“引用”捕获了一个局部变量,但 Lambda 被延迟(比如放到另一个线程或一个回调列表里)执行了;等到 Lambda 真正执行时,那个局部变量早就被销毁了,导致引用“悬空”。如果执行Lambda时,引用传递捕获进来的局部变量的值不可预知。所以绝大多数场合使用的形式为[=](){}。
早期版本的 Qt,若要使用 Lambda 表达式,要在.pro文件中添加:CONFIG += C++11。因为 Lambda 表达式是 C++11 标准提出的。Qt5 以上的版本无需手动添加,在新建项目时会自动添加。
CONFIG += C++11示例 1:Lambda 表达式的使用
#include "widget.h" #include <QPushButton> Widget::Widget(QWidget *parent) : QWidget(parent) { QPushButton *btn = new QPushButton("按钮", this); // 创建按钮 resize(800, 600); // 调整窗口大小 [=](){ btn->setText("测试按钮"); }(); // Lambda 表达式的调用 } Widget::~Widget() { }示例 2:以[=]方式传递,外部的所有变量在 Lambda 表达式中都可以使用
#include "widget.h" #include <QPushButton> Widget::Widget(QWidget *parent) : QWidget(parent) { resize(800, 600); // 调整窗口大小 QPushButton *btn1 = new QPushButton("按钮1", this); // 创建按钮 QPushButton *btn2 = new QPushButton("按钮2", this); // 创建按钮 btn2->move(100, 0); [=](){ btn1->setText("测试按钮1"); btn2->setText("测试按钮2"); }(); // 以 `=` 方式传递,外部所有的变量在 Lambda 表达式中都可访问 } Widget::~Widget() { }示例 3:以[btn]方式传递,在 Lambda 表达式中只能使用传递进来的btn
#include "widget.h" #include <QPushButton> Widget::Widget(QWidget *parent) : QWidget(parent) { QPushButton *btn = new QPushButton("按钮", this); // 创建按钮 resize(800, 600); // 调整窗口大小 [btn](){ btn->setText("测试按钮"); }(); // 在 Lambda 表达式中只传递 `btn` 这个变量 } Widget::~Widget() { }2、函数参数()
(params)表示 Lambda 函数对象接收的参数,类似于函数定义中的小括号,表示函数接收的参数类型和个数。参数可以通过按值(如(int a,int b))和按引用(如(int &a,int &b))两种方式进行传递。函数参数部分可以省略,省略后相当于无参的函数。
示例:
#include "widget.h" #include <QDebug> Widget::Widget(QWidget *parent) : QWidget(parent) { auto f = [](int x, int y){ return x + y; }; int result = f(10, 20); qDebug() << "result = " << result; } Widget::~Widget() { }3、选项opt
opt部分是可选项,最常用的是mutable声明,这部分可以省略。Lambda 表达式外部的局部变量通过值传递进来时,其默认是const,所以不能修改这个局部变量的拷贝,加上mutable就可以修改。
示例:
#include "widget.h" #include <QDebug> #include <QPushButton> Widget::Widget(QWidget *parent) : QWidget(parent) { resize(800, 600); QPushButton *btn1 = new QPushButton("btn1", this); QPushButton *btn2 = new QPushButton("btn2", this); btn2->move(300, 100); int m = 10; // 定义局部变量 connect(btn1, &QPushButton::clicked, this, [m]() mutable { m = 20; qDebug() << m; }); // 没有 mutable 声明,局部变量 `m` 的拷贝是 const,不可修改。`[=]()` {qDebug() << m;}; connect(btn2, &QPushButton::clicked, this, [=](){ qDebug() << m; }); } Widget::~Widget() { }4、Lambda 表达式的返回值类型->
可以指定 Lambda 表达式的返回值类型;如果不指定返回值类型,则编译器会根据代码实现为函数推导一个返回类型;如果没有返回值,则可忽略此部分。
示例 1:
#include "widget.h" #include <QDebug> Widget::Widget(QWidget *parent) : QWidget(parent) { int ret = []()->int{ return 123; }(); qDebug() << "ret = " << ret; } Widget::~Widget() { }示例 2:
#include "widget.h" #include <QDebug> Widget::Widget(QWidget *parent) : QWidget(parent) { // 指定返回值类型 auto f1 = []()->int { return 1; }; int result1 = f1(); qDebug() << "result1 = " << result1; // 不指定返回值类型 auto f2 = []() { return 1; }; int result2 = f2(); qDebug() << "result2 = " << result2; } Widget::~Widget() { }5、Lambda 表达式的函数体{}
Lambda 表达式的函数体部分与普通函数一致。用{}标识函数的实现,不能省略,但函数体可以为空。
示例:
#include "widget.h" Widget::Widget(QWidget *parent) : QWidget(parent) { [](){}; // Lambda 表达式中函数主体为空 } Widget::~Widget() { }6、槽函数使用 Lambda 表达式来实现
示例 1:点击按钮关闭窗口;
#include "widget.h" #include <QPushButton> Widget::Widget(QWidget *parent) : QWidget(parent) { resize(800, 600); QPushButton *btn = new QPushButton("关闭", this); connect(btn, &QPushButton::clicked, this, [=](){ this->close(); }); } Widget::~Widget() { }示例:当connect函数第三个参数为this时,第四个参数使用 Lambda 表达式时,可以省略掉this:
#include "widget.h" #include <QPushButton> Widget::Widget(QWidget *parent) : QWidget(parent) { resize(800, 600); QPushButton *btn = new QPushButton("按钮", this); connect(btn, &QPushButton::clicked, [=](){ this->close(); }); } Widget::~Widget() { }信号与槽的优缺点
优点:
信号发送者不需要知道发出的信号被哪个对象的槽函数接收,槽函数也不需要知道哪些信号关联了自己。Qt 的信号槽机制保证了信号与槽函数的调用。支持信号槽机制的类或者父类必须继承于QObject。
缺点:
- 与普通函数调用相比,信号和槽稍微慢一些,因为它们提供了更高的灵活性。尽管在实际应用程序中差别不大。
- 所有关联信号 / 槽的函数参数要多一些,信号的发送可能需要排队。这种调用对响应性能要求非常高的场景是可以忽略的,是可以满足绝大部分场景。
💡 一个客户端程序中,最慢的环节往往是 “人” 的反应时间,对局部程序效率上来说,是感知不到的。
假设调用基于回调的方式是 10us,使用信号槽的方式是 100us,对于使用程序的人来说,是感知不到差别的。
信号与槽的原理
前置核心结论先点明
- C++原生没有信号与槽语法,这是 Qt 基于元对象系统自研的事件通信机制;
- 必须继承 QObject + 加 Q_OBJECT 宏,否则完全用不了自定义信号槽;
- 整个流程靠MOC 工具 → 生成元数据 → connect 建映射表 → emit 查表回调完整跑通。
为什么必须写 Q_OBJECT 宏?
普通C++类 带Q_OBJECT的Qt类 ↓ ↓ 只有成员变量/普通函数 继承QObject + Q_OBJECT ↓ 被MOC工具扫描识别 ↓ 自动生成 moc_xxx.cpp 底层代码 ↓ 生成元数据表、信号函数体、信号槽映射逻辑普通 C++ 类,只能有构造、析构、普通成员函数、成员变量,没有信号、槽、元信息这些概念,编译器只按标准 C++ 编译,不做额外处理。
当一个类公有继承 QObject,并且在类里面第一行写上Q_OBJECT宏时,相当于给 Qt 的MOC 元对象编译器打了一个标记:这个类需要额外生成 Qt 专属底层代码。
Q_OBJECT 宏的本质:内部定义了一堆元对象相关的虚函数、常量、元数据入口,强制触发 MOC 的解析流程。
如果不写 Q_OBJECT:MOC 直接忽略这个类,不会生成任何元数据表、信号实现代码,connect 找不到信号标识,编译报错、或者连接无效,信号发了槽也不会触发。
只要用到:自定义信号、自定义槽、信号槽连接、Qt 动态属性、反射机制,三件套缺一不可:
- ① 类继承
public QObject - ② 类内部写
Q_OBJECT宏 - ③ 工程正常走 Qt 编译流程(让 MOC 参与编译)
MOC 工具是什么?原理作用?
你写的 .h 头文件(含signals/slots/Q_OBJECT) ↓ MOC工具(Qt自带,编译时自动运行) ↓ 扫描解析:提取信号名、槽函数名、参数类型 ↓ 自动生成 moc_头文件名.cpp 源文件 ↓ 并入项目一起编译链接 ↓ 底层具备信号调用、类型匹配、查表分发能力MOC 全称Meta-Object Compiler 元对象编译器,是 Qt 自带的预编译工具,不属于标准 C++ 编译器,是 Qt 额外加的一步编译流程。
编译项目时,Qt 不会直接把你的.h 交给 C++ 编译器,而是先过一遍 MOC:专门扫描带 Q_OBJECT 的头文件。
MOC 会做三件核心大事:
- 自动生成信号函数的实现体:你只需要声明
void MySignal(QString);,不用写函数实现,MOC 帮你把信号的底层调用代码写好; - 生成元数据表:表格里记录当前类有哪些信号、哪些槽、每个函数的参数类型、函数唯一标识;
- 生成内部回调、类型匹配、信号槽映射的底层逻辑代码。
生成的moc_xxx.cpp会和你自己写的代码合并编译,最终程序运行时,靠这份 MOC 生成的代码支撑信号槽工作。
没有 MOC → 没有元数据表 → 没有信号底层实现 → 信号槽机制彻底瘫痪。
┌─────────────────────────────────────────────────────────────────────┐ │ 你的源代码文件 │ │ │ │ 👉 widget.h (里面包含 Q_OBJECT 宏、signals、slots) │ │ 👉 widget.cpp │ └───────────────────────────┬─────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 【第一步:MOC 预编译】 │ │ MOC = Meta-Object Compiler 元对象编译器 (Qt专属工具) │ ├─────────────────────────────────────────────────────────────────────┤ │ 1. 扫描所有 #include <QObject> 且带 Q_OBJECT 宏的 .h 文件 │ │ 2. 读取 signals: 下的所有信号声明 │ │ 3. 读取 slots: 下的所有槽声明 │ └───────────────────────────┬─────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ MOC 自动生成 3 种核心代码 │ ├─────────────────────────────────────────────────────────────────────┤ │ ① 生成【信号函数的实现体】 │ │ → 你只写声明:void MySignal(); │ │ → MOC 自动写出函数体代码(底层发射、调度逻辑) │ │ │ │ ② 生成【元对象数据表】 │ │ → 记录:本类有哪些信号、哪些槽、参数类型、函数ID │ │ │ │ ③ 生成【信号槽底层映射代码】 │ │ → connect 怎么找到槽? │ │ → 参数类型怎么匹配? │ │ → 信号触发后怎么调用槽?全部靠它! │ └───────────────────────────┬─────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 输出文件: moc_widget.cpp (自动生成) │ └───────────────────────────┬─────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 【第二步:标准 C++ 编译】 │ │ 你的代码 + MOC 生成的代码 一起编译 │ ├─────────────────────────────────────────────────────────────────────┤ │ widget.cpp + moc_widget.cpp → 合成目标文件 .o .obj │ └───────────────────────────┬─────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 【第三步:链接】 │ │ 生成可执行程序 exe / app │ └───────────────────────────┬─────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 程序运行时:信号槽正常工作 │ │ │ │ emit MySignal() → 靠 MOC 生成的底层代码 调用槽函数 │ │ │ │ ✅ 没有 MOC → 没有信号实现 → 没有元数据 → 信号槽完全不能用! │ └─────────────────────────────────────────────────────────────────────┘connect 函数底层做了什么?
发送者对象(Teacher) | | 信号:MySignal(QString) ▼ connect函数 | | 内部创建一张【信号槽映射注册表】 ▼ 记录条目: 发送者地址 + 信号标识 → 接收者地址 + 槽函数标识 | ▼ 接收者对象(Student) | | 槽函数:StartStudy(QString) ▼ 等待信号触发调用connect(发送者, 信号, 接收者, 槽函数)这句代码,不是立刻执行函数,而是做注册绑定。
Qt 内部维护一个全局信号槽映射表,connect 的核心工作就是:往这张表里插入一条记录。
记录里存的关键信息:
- 是谁发信号(发送者对象地址);
- 是哪个信号(信号唯一标识 / 函数地址);
- 给谁发(接收者对象地址);
- 要执行哪个槽函数(槽函数标识 / 地址)。
Qt4 旧版靠字符串名字匹配(SIGNAL/SLOT 宏转字符串),Qt5 新版靠成员函数指针编译期类型校验,但底层都是往映射表注册条目。
connect 只是建立订阅关系,此时槽函数完全不会执行,只是静静躺在注册表中等待信号触发。
emit 发射信号时,底层完整运行流程?
代码执行到:emit tch->MySignal("Hello"); ↓ emit 只是空宏,无实际代码,等价于直接调用MOC生成的信号函数 ↓ 进入MOC自动生成的MySignal底层函数 ↓ Qt查内部【信号槽映射表】 ↓ 匹配:当前发送者 + 当前信号 对应的所有接收者 ↓ 遍历所有绑定好的槽函数 ↓ 把信号的参数原封不动传给槽函数 ↓ 自动执行接收者的槽函数代码emit本质就是一个空宏,编译后会被直接删掉,没有任何额外指令;emit 信号(参数)等价于直接调用 MOC 生成的信号函数。
信号函数是 MOC 自动帮你实现的,你不用自己写函数体,它的内部逻辑就是:触发查表分发。
程序会拿着当前发送者对象地址和当前信号的标识,去 Qt 全局信号槽映射表里检索。
找到所有和这个发送者、这个信号绑定的所有接收者 + 槽函数。
按连接类型(自动连接、队列连接、直连等),决定立刻同步调用,还是放进事件队列异步调用。
把 emit 传进去的参数,原样拷贝传递给槽函数的形参,然后执行槽函数内部业务逻辑。
整个过程:发送者完全不知道接收者是谁,只负责发信号;接收者也不用主动轮询,被动等待回调,实现了模块完全解耦。
全链路
1. 编码阶段 写类:继承QObject + Q_OBJECT + 声明signals信号、slots槽函数 ↓ 2. 编译预解析阶段 MOC扫描头文件 → 生成moc_xxx.cpp → 生成元数据表+信号实现 ↓ 3. 程序构造阶段 new 对象 → 调用connect → 往Qt注册表注册信号槽映射关系 ↓ 4. 运行触发阶段 代码执行emit → 调用MOC信号函数 → 查表匹配 → 自动执行槽函数总结
Q_OBJECT 是入场券,让 MOC 识别并为类生成元对象底层代码,没有它就没有信号槽的底层支撑;
MOC 是幕后工人,帮你补全信号实现、生成元数据、搭建信号槽匹配的底层能力;
connect 是登记注册,把信号和槽的对应关系存入 Qt 内部映射表,建立发布订阅关系;
emit 是触发广播,通过 MOC 生成的信号函数查表,自动回调所有绑定的槽函数;
整套机制是 Qt 用QObject + MOC 元对象系统,在标准 C++ 之外扩展出的观察者模式通信机制,实现对象间低耦合通信。
