目录
- [第二章 信号与槽](#第二章 信号与槽)
-
- [1. 信号和槽概述](#1. 信号和槽概述)
- [2. 信号和槽的使用](#2. 信号和槽的使用)
-
- [2.1 连接信号和槽](#2.1 连接信号和槽)
- [2.2 查看内置信号和槽](#2.2 查看内置信号和槽)
- [2.3 通过 Qt Creator 生成信号槽代码](#2.3 通过 Qt Creator 生成信号槽代码)
- [3. 自定义信号和槽](#3. 自定义信号和槽)
-
- [3.1 基本语法](#3.1 基本语法)
- [3.2 带参数的信号和槽](#3.2 带参数的信号和槽)
- [4. 信号与槽的连接方式](#4. 信号与槽的连接方式)
-
- [4.1 一对一](#4.1 一对一)
- [4.2 一对多](#4.2 一对多)
- [4.3 多对一](#4.3 多对一)
- [5. 信号和槽的其他说明](#5. 信号和槽的其他说明)
-
- [5.1 信号与槽的断开](#5.1 信号与槽的断开)
- [5.2 Qt4 版本信号与槽的连接](#5.2 Qt4 版本信号与槽的连接)
- [5.3 使用 Lambda 表达式定义槽函数](#5.3 使用 Lambda 表达式定义槽函数)
- [5.4 信号与槽的优缺点](#5.4 信号与槽的优缺点)
第二章 信号与槽
1. 信号和槽概述
在 Qt 中,用户和控件的每次交互都可以看作一个事件。例如点击按钮、关闭窗口、输入内容等,都会触发对应的事件。
事件发生后,Qt 会发出对应的信号;收到信号后执行的响应函数,就是槽函数。信号和槽可以把两个相互独立的对象关联起来,例如把"按钮点击"这个信号和"窗口关闭"这个槽函数连接起来。

信号的本质: 信号可以理解为事件发生后的通知。常见事件包括按钮单击、按钮双击、窗口刷新、鼠标移动、鼠标按下、鼠标释放、键盘输入等。
在代码层面,信号的呈现形式就是函数。某个事件发生后,Qt 框架会触发对应的信号函数。信号的发送者通常是某个已经实例化的 Qt 对象。
槽的本质: 槽(Slot)就是响应信号的函数。槽函数本质上仍然是 C++ 函数,可以定义在 public、protected 或 private 区域,也可以有参数、重载或被直接调用。
说明: 信号和槽机制底层可以理解为函数之间的调用关系。例如按钮的点击信号可以用 clicked() 表示,窗口关闭槽函数可以用 close() 表示。连接之后,点击按钮就相当于触发 clicked(),再由它调用 close()。
信号函数通常写在 signals 下,只需要声明,不需要手动实现;槽函数可以写在 public slots、protected slots、private slots 或普通成员函数区域,需要自己实现。
Qt 会在编译前自动生成信号相关代码,这类机制可以理解为元编程(Meta Programming)。
2. 信号和槽的使用
2.1 连接信号和槽
在 Qt 中,QObject 类提供了一个静态成员函数 connect(),该函数专门用来关联指定的信号函数和槽函数。
关于 QObject: QObject 是 Qt 内置的基础父类,Qt 中很多类都直接或间接继承自 QObject。
connect() 函数原型:
cpp
connect(const QObject *sender,
const char *signal,
const QObject *receiver,
const char *method,
Qt::ConnectionType type = Qt::AutoConnection);
参数说明:
sender: 信号的发送者。
signal: 发送的信号,也就是信号函数。
receiver: 信号的接收者。
method: 接收信号后执行的槽函数。
type: 连接方式,默认值为 Qt::AutoConnection,通常不需要手动设置。
代码示例: 在窗口中设置一个按钮,当点击"按钮"时关闭"窗口"。

2.2 查看内置信号和槽
系统自带的信号和槽通常通过 Qt 帮助文档查询。例如要查看按钮的信号,可以在帮助文档中搜索 QPushButton,先在 Contents 中查找 signals。如果当前类中没有找到,就继续到父类中查找,例如 QPushButton 的父类 QAbstractButton。

这里的 clicked() 就是要找的信号。槽函数的寻找方式和信号一样,只不过它的关键字是 slot。
2.3 通过 Qt Creator 生成信号槽代码
Qt Creator 可以快速生成信号槽相关代码。
代码示例: 在窗口中设置一个按钮,当点击按钮时关闭窗口。
1、新建项目,如下图为新建完成之后所包含的所有文件。注意:创建项目时需要生成 UI 设计文件。

2、双击 widget.ui 文件,进入 UI 设计界面;

3、在 UI 设计窗口中拖入一个"按钮",并且修改"按钮"的名称及字体大小等;

4、可视化生成槽函数;

当单击"转到槽..."之后,出现如下界面:对于按钮来说,当点击时发送的信号是:clicked(),所以此处选择:clicked()

对于普通按钮来说,使用 clicked 信号即可;clicked(bool) 主要用于带状态的按钮,例如复选按钮。
5、自动生成槽函数原型框架。
(1)在 widget.h 头文件中自动添加槽函数声明;

自动生成槽函数的命名规则: on_XXX_SSS。
XXX 表示控件的 objectName 属性,SSS 表示对应的信号。例如 on_pushButton_clicked() 中,pushButton 是对象名,clicked 是对应的信号。
按照这种命名风格定义的槽函数,会被 Qt 自动连接到对应信号。
实际开发中,除非是 IDE 自动生成代码,否则更推荐显式调用 connect()。这样能更清晰地描述信号和槽的连接关系,也能减少因为命名或拼写问题导致连接失效的情况。
(2)在 widget.cpp 中自动生成槽函数定义。

6、在槽函数定义中添加要实现的功能,实现关闭窗口的效果。

3. 自定义信号和槽
3.1 基本语法
Qt 允许自定义信号函数和槽函数,但书写方式需要遵守固定规则。
自定义信号函数:
1、自定义信号函数必须写在 signals 下。
2、返回值为 void,只需要声明,不需要实现。
3、可以带参数,也可以重载。
自定义槽函数:
1、早期 Qt 版本要求槽函数写在 public slots 下;较新的 Qt 版本也允许写在普通 public 作用域中,或者写成普通全局函数。
2、返回值通常为 void,需要声明,也需要实现。
3、可以带参数,也可以重载。
发送信号: 使用 emit 关键字发送信号。emit 本质上是一个空宏,可写可不写,主要用于提升代码可读性。
示例1:自定义信号和槽。
1、在 widget.h 中声明自定义信号和槽;

2、在 widget.cpp 中实现槽函数,并连接信号和槽。注意图中 ① 和 ② 的顺序不能颠倒:需要先建立连接,再发送信号;如果先发送信号,此时槽函数还没有关联,就不会触发响应。

示例2:自定义 Teacher 信号与 Student 槽函数。
1、在源文件中新建两个类:一个是 Teacher 类,一个是 Student 类。首先选中项目名称,鼠标右键选择 "add new..."。

点击"add new..."之后,出现如下界面:

选择 "choose" 后进入类配置界面。注意:在 Qt 中新建类时,需要选择新类的父类。
如果新类不是窗口或控件,也没有更合适的业务父类,可以选择 QObject 作为基类。这样新类对象可以配合 Qt 的对象树机制,便于对象释放。

选择"下一步",进入如下界面:

按照同样的方式添加 Student 类。添加完成后,项目目录新增文件如下:

在 teacher.h 中声明信号函数:

在 student.h 中声明槽函数:

在 widget.h 中实例化 Teacher 对象和 Student 对象;

在 student.cpp 中实现槽函数:

在 widget.cpp 中连接自定义信号和槽;

运行结果如下:

示例3:点击按钮触发自定义信号;

运行结果如下:

3.2 带参数的信号和槽
Qt 的信号和槽支持参数,也支持重载。通常要求信号函数的参数列表和槽函数的参数列表保持匹配。
当信号触发时,信号函数中的实参会传递给槽函数的形参,这样就可以通过信号向槽传递数据。
示例1:重载信号槽。
(1)在 widget.h 中声明重载的信号函数和槽函数;

(2)在 widget.cpp 中实现重载槽函数,并连接信号和槽。注意:定义函数指针时,需要指明函数指针的作用域。

(3)执行结果如下图所示:

示例2:信号槽参数列表匹配规则。
1、在 widget.h 中声明信号和槽函数;

2、在 widget.cpp 中实现槽函数,并连接信号和槽;

信号的参数个数可以多于槽函数的参数个数,但槽函数的参数个数不能多于信号参数个数。实际开发中,最好让两者参数列表保持一致。
示例3:参数不完全一致时的连接。
1、在 widget.h 中声明信号和槽函数;

2、在 widget.cpp 中实现槽函数,并连接信号和槽;

4. 信号与槽的连接方式
4.1 一对一
主要有两种形式,分别是:一个信号连接一个槽和一个信号连接一个信号。
(1)一个信号连接一个槽

示例:一对一连接。
1、在 widget.h 中声明信号、槽以及信号发射函数;

2、在 widget.cpp 中实现槽函数、信号发射函数,并连接信号和槽;

(2)一个信号连接另一个信号

示例:在上述示例基础上,在 widget.cpp 中添加如下代码:

4.2 一对多
一个信号连接多个槽

示例:一个信号连接多个槽。
(1)在 widget.h 中声明一个信号和三个槽;

(2)在 widget.cpp 中实现槽函数,并连接信号和槽;

4.3 多对一
多个信号连接一个槽函数

示例:多个信号连接一个槽。
(1)在 widget.h 中声明两个信号以及一个槽;

(2)在 widget.cpp 中实现槽函数,并连接信号和槽;

5. 信号和槽的其他说明
5.1 信号与槽的断开
使用 disconnect() 可以断开信号和槽的连接,基本用法和 connect() 类似。
示例:

5.2 Qt4 版本信号与槽的连接
Qt4 中的 connect() 写法比 Qt5 更复杂,需要配合 SIGNAL 和 SLOT 宏使用,而且缺少函数类型检查,代码更容易出错。
示例:Qt4 写法。
(1)在 widget.h 中声明信号和槽;

(2)在 widget.cpp 中实现槽函数,并连接信号与槽;

Qt4 写法的特点:
优点: 参数直观。
缺点: 参数类型不做检测,编译期不容易发现错误。
示例:

5.3 使用 Lambda 表达式定义槽函数
Qt5 提高了信号与槽的灵活性,允许使用任意函数作为槽函数。需要临时定义简单槽函数时,可以使用 Lambda 表达式来简化代码。
Lambda 表达式是 C++11 增加的特性,用于定义匿名函数对象,可以简化槽函数的编写。
Lambda 表达式的基本语法格式如下:
cpp
[capture](params) opt -> ret {
Function body;
};
| 组成部分 | 说明 |
|---|---|
capture |
捕获列表,用于指定外部变量的捕获方式。 |
params |
参数列表,类似普通函数的参数。 |
opt |
函数选项,常见的是 mutable。 |
ret |
返回值类型,可以显式指定,也可以由编译器推导。 |
Function body |
函数体,即 Lambda 表达式具体执行的代码。 |
1、局部变量引入方式 []
[] 是 Lambda 表达式的捕获列表,用来声明外部变量的捕获方式。
| 捕获方式 | 说明 |
|---|---|
[] |
不捕获任何外部局部变量。 |
[a] |
以值传递方式捕获变量 a。 |
[&b] |
以引用传递方式捕获变量 b。 |
[=] |
以值传递方式捕获外部所有局部变量,函数体中使用的是副本。 |
[&] |
以引用方式捕获外部所有局部变量。 |
[=, &foo] |
foo 使用引用捕获,其余变量使用值捕获。 |
[&, foo] |
foo 使用值捕获,其余变量使用引用捕获。 |
[this] |
捕获当前对象指针,可以在函数体中访问类的成员函数和成员变量。 |
说明: 使用引用捕获时要注意变量生命周期。如果 Lambda 执行时,被引用捕获的局部变量已经释放,就会产生不可预期的结果。实际开发中更常见的写法是 [=](){}。
早期 Qt 版本如果要使用 Lambda 表达式,需要在 .pro 文件中添加:
qmake
CONFIG += c++11
Qt 5 及以上版本通常会在新建项目时自动添加相关配置。

示例1:Lambda 表达式的使用

示例2:以 [=] 方式传递,外部的所有变量在 Lambda 表达式中都可以使用

示例3:以 [a] 方式传递,在 Lambda 表达式中只能使用传递进来的 a

2、函数参数()
(params)表示 Lambda 函数对象接收的参数,类似于函数定义中的小括号表示函数接收的参数类型和个数。参数可以通过按值(如:(int a,int b))和按引用(如:(int&a,int&b))两种方式进行传递。函数参数部分可以省略,省略后相当于无参的函数。
示例:

3、选项 Opt
Opt 部分是可选项,最常用的是 mutable 声明。Lambda 表达式通过值捕获外部局部变量时,默认不能修改这个副本;加上 mutable 后就可以修改。

4、Lambda 表达式的返回值类型 ->
可以显式指定 Lambda 表达式的返回值类型;如果不指定,编译器会根据函数体自动推导;如果没有返回值,可以省略这一部分。
示例1:

示例2:

5、Lambda 表达式的函数体 {}
Lambda 表达式的函数体和普通函数体类似,用 {} 表示具体实现。函数体不能省略,但可以为空。
示例:

6、使用 Lambda 表达式实现槽函数
示例1:点击按钮关闭窗口;

示例2:当 connect() 的第三个参数为 this 时,第四个参数使用 Lambda 表达式时,可以省略 this;

5.4 信号与槽的优缺点
优点: 松散耦合。
信号发送者不需要知道信号会被哪个对象的槽函数接收,槽函数也不需要关心自己关联了哪些信号。Qt 的信号槽机制会负责完成调用。需要注意的是,支持信号槽机制的类或父类必须继承自 QObject。
缺点: 效率略低。
与直接回调相比,信号和槽会稍慢一些,因为它提供了更高的灵活性。额外开销主要来自查找接收对象、遍历连接关系、处理参数以及跨线程排队等过程。不过在大多数 GUI 开发场景中,这点开销通常可以忽略。
对于 GUI 程序来说,用户交互本身通常远慢于函数调用开销。即使信号槽比直接回调慢一些,在大多数界面开发场景中也不会成为性能瓶颈。