信号和槽 (Signals and Slots)
Qt 框架中的信号和槽机制是其核心特性之一,用于对象间通信,特别适用于 GUI 事件处理。它允许对象发送和接收消息,从而实现松散耦合的设计。
信号和槽的基本概念
- 信号 (Signal):信号是对象状态发生变化时发出的通知。信号不需要实现,只需在类定义中声明即可。
- 槽 (Slot):槽是一个函数,可以在信号发出时被调用。槽是普通的成员函数,只不过可以与信号关联。
信号和槽的连接
使用 QObject::connect
函数来连接信号和槽。信号和槽可以是一个类的成员,也可以跨类连接。
信号和槽的特点
- 类型安全:连接信号和槽时,Qt 会在编译时检查参数类型是否匹配。
- 松散耦合:对象之间可以通过信号和槽进行通信,而不需要彼此知道具体的实现,从而实现松散耦合。
- 多对多连接:一个信号可以连接到多个槽,一个槽也可以连接到多个信号。信号还可以连接到其他信号,从而形成信号链。
高级特性
1. Lambda 表达式
从 Qt 5 开始,信号和槽可以使用 C++11 的 lambda 表达式:
2. 断开连接
可以使用 QObject::disconnect
函数断开信号和槽的连接:
connect
函数
在 Qt 框架中,QObject::connect
函数用于将信号和槽连接起来,使得当一个对象的信号被发射时,与之连接的槽会被调用。connect
函数有多个重载版本,以适应不同的使用场景。以下是最常见的形式及其参数详解:
常见的 connect
函数形式
- 四参数版本:
- 五参数版本:
- 新语法(使用函数指针或 lambda 表达式):
参数详解
四参数版本
- sender:发射信号的对象。
- signal:信号的字符指针,格式为
"signalName(type1, type2, ...)"
。 - receiver:接收信号的对象。
- method:槽的字符指针,格式为
"slotName(type1, type2, ...)"
。
五参数版本
- sender:发射信号的对象。
- signal:信号的字符指针,格式为
"signalName(type1, type2, ...)"
。 - receiver:接收信号的对象。
- method:槽的字符指针,格式为
"slotName(type1, type2, ...)"
。 - type:连接类型,默认是
Qt::AutoConnection
,可以是以下类型之一:
-
Qt::AutoConnection
:根据线程自动选择合适的连接方式。 -
Qt::DirectConnection
:直接调用槽函数(在发射信号的线程中)。 -
Qt::QueuedConnection
:将槽函数调用放入接收者的事件队列中(在接收信号的对象所属的线程中)。 -
Qt::BlockingQueuedConnection
:类似Qt::QueuedConnection
,但会阻塞直到槽函数完成(仅用于多线程)。 -
Qt::UniqueConnection
:确保信号和槽只连接一次,如果已经连接则不再连接。
新语法
- sender:发射信号的对象。
- signal:信号的成员函数指针,例如
&Sender::signalName
。 - receiver:接收信号的对象。
- method:槽的成员函数指针,例如
&Receiver::slotName
,或 lambda 表达式。
槽(Slots)
在 Qt 框架中,槽函数是一个特殊的成员函数,它可以与信号关联,当信号被发出时,槽函数会被执行。槽函数本质上是普通的成员函数,但通过 Qt 的元对象系统 (Meta-Object System) 可以与信号机制结合使用。
槽函数的定义
槽函数的定义方式与普通成员函数基本相同,不同之处在于它们需要在类声明中使用 slots
关键字标识。以下是一个简单的例子:
槽函数的特点
- 成员函数:槽函数是类的成员函数,可以访问类的成员变量和其他成员函数。
- 参数和返回值:槽函数可以有参数和返回值,但通常不会有返回值,因为信号和槽机制主要用于事件驱动编程。
- 公开或私有:槽函数可以是
public
、protected
或private
的,这取决于你希望如何控制对槽的访问。
槽函数的调用
槽函数可以被任何对象直接调用,就像调用普通成员函数一样,但更常见的是通过信号触发调用。这种机制允许对象之间进行松散耦合的通信。
直接调用
通过信号调用
槽函数的灵活性
槽函数比信号更加灵活,因为它们可以有返回值和参数,可以直接调用,还可以与多个信号关联。以下是一些示例:
槽函数带返回值
尽管槽函数通常没有返回值,但在某些情况下它们可以有返回值:
一个槽函数连接多个信号
同一个槽函数可以连接到多个不同的信号,从而处理不同的事件:
槽函数与信号的连接方式
连接信号和槽的 connect
函数有多种形式,使用新语法可以利用函数指针和 lambda 表达式:
使用函数指针连接
使用 lambda 表达式
定义自定义槽
自定义槽函数与普通的成员函数非常相似,但它们需要在类声明中使用 slots
关键字标识。槽函数可以是 public
、protected
或 private
的。
自定义槽的特点
- 成员函数:槽函数是类的成员函数,可以访问类的成员变量和其他成员函数。
- 参数和返回值:槽函数可以有参数和返回值,但通常不会有返回值,因为信号和槽机制主要用于事件驱动编程。
- 公开或私有:槽函数可以是 public、protected 或 private 的,这取决于你希望如何控制对槽的访问。
简单自定义槽
头文件 (myobject.h)
实现文件 (myobject.cpp)
连接信号到自定义槽
在你的应用程序中,创建对象并将信号连接到自定义槽。
头文件 (sender.h)
主程序 (main.cpp)
信号(Signals)
信号是 Qt 对象在特定事件发生时发出的。它们可以由 Qt 框架中的类定义,也可以由用户自定义的类定义。信号是事件驱动的,即它们只在特定事件发生时发出,并且不包含返回类型(即它们是单向的)。信号的发出类似于广播,没有特定的接收者,只要有对象对这个信号感兴趣并进行了连接,就可以接收到这个信号。
自定义信号
自定义信号是用户根据需要在自己的类中定义的信号。它们在类声明中通过 signals
关键字声明,不需要在类实现文件中定义,因为信号本身没有实现,只是一个声明。信号的发射是通过 emit
关键字来完成的。
自定义信号的示例
头文件 (sender.h)
实现文件 (sender.cpp)
使用自定义信号
要使用自定义信号,你需要将信号连接到一个槽函数,当信号被发射时,槽函数将被调用。
头文件 (receiver.h)
实现文件 (receiver.cpp)
主程序 (main.cpp)
信号和槽的连接方式
Qt 提供了多种连接信号和槽的方法,主要有以下几种:
使用旧的字符串语法
这种方式适用于 Qt 4 及之前的版本,但在 Qt 5 及之后推荐使用新语法。
使用函数指针的新语法(推荐)
这种方式在编译时进行类型检查,确保信号和槽的参数类型匹配,更加安全可靠。
使用 lambda 表达式
lambda 表达式非常灵活,适用于处理简单的信号响应。
信号和槽的特点
- 类型安全:连接信号和槽时,Qt 会在编译时检查参数类型是否匹配。
- 松散耦合:对象之间可以通过信号和槽进行通信,而不需要彼此知道具体的实现,从而实现松散耦合。
- 多对多连接:一个信号可以连接到多个槽,一个槽也可以连接到多个信号。信号还可以连接到其他信号,从而形成信号链。
- 线程安全:Qt 的信号和槽机制支持多线程编程,可以在不同线程间传递信号。
高级示例:带返回值的自定义信号(使用新特性)
尽管信号通常没有返回值,但可以使用 Qt 的新特性(如 Qt 6 中引入的特性)实现带返回值的信号和槽。
头文件 (advancedreceiver.h)
实现文件 (advancedreceiver.cpp)
主程序 (main.cpp)