1.信号和槽概述
信号槽是 Qt 框架引以为豪的机制之一。所谓信号槽,实际就是观察者模式(发布-订阅模式)。当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,将想要处理的信号和自己的一个函数(称为槽(slot))绑定来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调。这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发
1.1 信号的本质
信号是由于用户对窗口或控件进行了某些操作,导致窗口或控件产生了某个特定事件,这时候Qt对应的窗口类会发出某个信号,以此对用户的挑选做出反应:
因此根据上述的描述我们得到一个结论 – 信号的本质就是事件,比如:
- 按钮单击、双击
- 窗口刷新
- 鼠标移动、鼠标按下、鼠标释放
- 键盘输入
那么在Qt中信号是通过什么形式呈现给使用者的呢?
- 我们对于哪个窗口进行操作,哪个窗口就可以捕捉到这些被触发的事件
- 对于使用者来说触发了一个事件我们就可以得到Qt框架给我们发出的某个特定信号
- 信号的呈现形式就是函数,也就是说某个事件产生了,Qt框架就会调用某个对应的信号函数,通知使用者
在QT中信号的发出者是某个实例化的类对象,对象内部可以进行相关事件的检测
1.2 槽的本质
在Qt中槽函数是一类特殊的功能的函数,在编码过程中也可以作为类的普通成员函数来使用。之所以称之为槽函数是因为它们还有一个职责就是对Qt框架中产生的信号进行处理:
举个简单的例子:
女朋友说:“我肚子饿了!”,于是我带她去吃饭。
上边例子中相当于女朋友发出了一个信号, 我收到了信号并其将其处理掉了。
实例对象 | 角色 | 描述 |
女朋友 | 信号发出者 | 信号携带的信息:我饿了 |
我 | 信号接收者 | 处理女朋友发射的信号:带她去吃饭 |
在Qt中槽函数的所有者也是某个类的实例对象
1.3 信号和槽的关系
在Qt中信号和槽函数都是独立的个体,本身没有任何联系,但是由于某种特性需求我们可以将二者连接到一起,好比牛郎和织女想要相会必须要有喜鹊为他们搭桥一样。在Qt中我们需要使用QOjbect类中的connect函数进二者的关联。
连接信号和槽的connect()函数原型如下, 其中PointerToMemberFunction是一个指向函数地址的指针
QMetaObject::Connection QObject::connect(
const QObject *sender, PointerToMemberFunction signal,
const QObject *receiver, PointerToMemberFunction method,
Qt::ConnectionType type = Qt::AutoConnection);
参数:
- sender: 发出信号的对象
- signal: 属于sender对象, 信号是一个函数, 这个参数的类型是函数
指针, 信号函数地址
- receiver: 信号接收者
- method: 属于receiver对象, 当检测到sender发出了signal信号,
receiver对象调用method方法,信号发出之后的处理动作
// 参数 signal 和 method 都是函数地址, 因此简化之后的 connect() 如下:
connect(const QObject *sender, &QObject::signal, const QObject *receiver, &QObject::method);
- connect函数相对于做了信号处理动作的注册
- 调用conenct函数的sender对象的信号并没有产生, 因此receiver对象的method也不会被调用
- method槽函数本质是一个回调函数, 调用的时机是信号产生之后, 调用是Qt框架来执行的
- connect中的sender和recever两个指针必须被实例化了, 否则conenct不会成功
1.4 信号与槽在使用中的注意事项
(1)要使用的信号(发送者)和槽(接收者)的类必须要间接或直接继承QObject类,还必须要在类的定义中的第一行写上一个宏定义Q_OBJECT
(2)当信号与槽函数的参数数量相同时,它们的参数类型必须要一致(信号的参数不能少于槽函数的参数)
如果信号的参数与槽函数不一致时,有两种解决方法:
方法1:强制转换 static_cast<>(object);
方法2:QOverLoad<type>::of(object);
(3)如果信号和槽函数的参数不一致,允许的情况是,槽函数的参数可以比信号的参数少,但是,槽函数存在的那些参数的顺序也必须和信号前面几个类型一致
(4)信号和槽函数的参数必须是元对象类型,如果不是就要注册该类型,使用Q_DECLARE_METATYPE(Type)宏
(注册自定义类型的方法请看我写的这篇博客Qt中的基础数据类型_lune_one的博客-CSDN博客)
(5)发送对象和接收对象要对应(信号的发送者和接收者关联的对象不匹配时,就会解除关联,其中一个释放,也会解除关联)
(6)信号和槽函数的返回值都是void
(7)信号只需要声明,不需要实现;槽函数需要声明与需要实现
(8)一个信号可以关联多个槽,多个信号可以关联同一个槽
(9)一个信号可以关联一个信号
(10)槽可以被取消关联(disconnect)
1.5 信号与槽的优缺点
优点:(1)类型安全 (2)松散耦合 (3)灵活性
(1)类型安全:信号的参数类型和参数个数相同,接受该信号的槽的参数类型和参数个数相同。若信号和槽签名不一致,编译器会报错
(2)松散耦合:信号发送者和接收者是独立的。信号和槽之间实现了异步关系,信号和槽机制减弱了Qt对象的耦合度。激发信号的Qt对象无需知道是那个对象的那个信号槽接收它发出的信号,它只需在适当的时间发送适当的信号即可,而不需要关心是否被接受和那个对象接受了。Qt就保证了适当的槽得到了调用,即使关联的对象在运行时被删除。程序也不会奔溃
(3)灵活性:一个信号可以关联多个槽,多个信号可以关联同一个槽
缺点:(1) 效率低
信号与槽速度较慢。与回调函数相比,信号和槽机制运行速度比直接调用非虚函数慢 10倍
原因∶
1)需要定位接收信号的对象。
2)安全地遍历所有关联槽。
3)编组、解组传递参数。
4)多线程的时候,信号需要排队等待。
1.6 信号与槽关联函数connect的第五个参数 连接类型
connect(sender, signal, receiver, slot, connection type);
connect(信号发出者,信号,信号接收者,槽,连接类型(隐藏默认为自动连接))
(1)Qt::AutoConnect(自动关联)
默认值,连接类型会在信号发送时确定。如果接收者和发送者在同一个线程,则自动使用Qt::DirectConnection(直接连接)类型,如果接收者和发送者不在同一个线程,则自动使用Qt::QueuedConnection(队列连接)类型