深入理解QT之信号和槽

1.信号和槽的基本概念

1.1信号

说到信号,相信很多人都会想到Linux中的信号,两者虽不是一样的东西,但是也有异曲同工之妙。

在QT中,由用户与界面的一些交互操作我们叫做事件,某个事件完成以后就会发出信号。比如用户点击按钮,按钮控件就会发出按钮被点击的信号;用户关闭窗口,窗口就会发出关闭窗口的信号。

1.2槽

在QT中每个控件都能收到信号,当一个控件收到一个信号以后就要去做某些事情,这个事情就称为槽。比如用户点击关闭窗口时,窗口收到了这个信号就会去执行关闭窗口这个操作。

本质上,**槽(slot)**在代码中就是某个函数。所以控件在接受到某些指令以后就要去执行某些槽函数,这是提前设置好的。

槽函数与一般的函数一致都可以有任何的参数,定义在任意的地方,也可以进行重载。槽函数与一般函数不一致的地方是,当某个信号发射时,该槽函数会被自动调用。

2.信号和槽的使用

2.1连接信号和槽

在QT中可以使用connect将一个信号和槽进行关联起来。connect是QObject的一个静态成员函数,QT中大多数控件都直接会间接继承于QObject,QObject相当于一个内置的父类。

connect函数的声明:

这段代码使用了 C++ 的 模板(Template) 技术。虽然看起来很复杂(包含 typename, QtPrivate, FunctionPointer 等),但它的目的是为了实现 编译期的类型安全检查

它的逻辑可以翻译为:

"当 sender(发送者)发出 signal(信号)时,请自动调用 receiver(接收者)的 slot(槽函数)。"

这里使用了typename模板的方式可以传入不同类型的对象和函数指针。

QT中封装了一个类型萃取器FunctionPointer来保证传入的对象和函数指针相对应。

拆解其中的参数:connect(sender, signal, receiver, slot, type)

1)sender发送者

类型:const typename QtPrivate::FunctionPointer<Func1>::Object *

解释:发出信号的对象

说明:在这里使用了复杂的类型萃取器的一个写法,是为了将sendersignal 进行匹配,如果signal 不是sender的成员函数(父类继承的也可以),那么编译就会报错。

这句话的意思就是:如果你要连接一个属于 Parent 类的信号,你传入的对象 sender 必须是 Parent 或者 Parent 的子类。

2)signal信号

类型:Func1

解释:发出的信号

说明:signal必须与sender的类型匹配

3)receiver接受者

类型:const typename QtPrivate::FunctionPointer<Func2>::Object *

解释:接受信号并操作槽函数的指针

说明:同sender一样,slot必须与receiver类型匹配

4)slot槽函数

类型:Fun2

解释:槽函数,信号发出以后要执行的函数

5)type(连接类型)

类型:Qt::ConnectionType

默认值:Qt::AutoConnection

解释:决定信号和槽如何交互(同步还是异步)。

AutoConnection(默认):如果发送者和接收者在同一线程,直接调用(同步);如果不同线程,放入事件队列(异步)

DirectConnection:像普通函数调用一样立即执行(主要用于单线程)。

QueuedConnection:将执行任务放入接收者所在线程的事件循环中(主要用于多线程)

使用示例:

QObject::connect(

ui->myButton, // sender: 按钮对象

&QPushButton::clicked, // signal: 按钮被点击的信号 (函数地址)

this, // receiver: 当前窗口对象

&MyWindow::close // slot: 执行关闭操作 (函数地址)

);

也可以使用Lambda表达式:

QObject::connect(ui->myButton, &QPushButton::clicked, this, =() {

// 如果 this 被销毁了,Qt 会自动断开这个连接,Lambda 永远不会被执行

qDebug() << "按钮被点击了,这里是 Lambda 表达式!";

this->doSomething();

});

2.2connect使用

首先在widget的构造函数定义一个QPushButton的按钮,调整一些相关属性,然后通过connect将button的点击信号和窗口的关闭槽函数进行连接。

其中我们在输入clicked信号和close的时候会有一些语法提示,带有小齿轮的说明是槽函数,像波一样向外发射的就是信号。可以看到clicked是信号,而click是槽函数。

clicked相当于是执行了函数click以后会发出的一个信号,click在前,clicked在后,不要搞混了。

编译运行以后就出现了一个窗口中有一个CLOSW WIDOW的按钮,点击这个按钮,buttom就会发送出clicked信号,widget窗口在接受到信号以后就会执行槽函数close关闭窗口了。

2.3信号与槽函数

在QT中,大多数控件都会有自己的信号和槽函数,这些信号和槽函数不一定是属于控件本身的,更多是从父类继承来的,我们可以翻阅QT文档来查看。

'

QPushButton的信号和槽都继承于QAbstractButton( 抽象按钮**)**,这里是为了复用代码,让其他的按钮控件也能够拥有相同的信号与槽功能。

文档中提到的以下控件都是继承于QAbstractButton的,体现了控件之间的共性,控件还可以根据自己的特性单独写某些信号或者槽函数。

需要注意的是QAbstractButton又是继承于QWidget的,而QWidget又是继承于QObject。所以connect的时候才能顺利传参,即sender与signal是匹配的。

3.自定义信号和槽

3.1自定义槽函数

1)除了使用QT提供的槽函数,我们也可以自定义槽函数。这里在Widget类中定义了一个handleclick函数。

编译运行点击按钮发出信号,widget窗口执行槽函数

2)除此之外,还可以通过以下这种方法连接信号和槽:

首先点击widget.ui文件,转到设计页面,然后创建一个按钮,右击此按钮,点击转到槽

然后点击clicked信号

'

接着系统会自动在widget.cpp中生成一个函数,其中的定义由我们自己填写

然后运行编译点击按钮一样可以实现信号和槽的连接效果

此处我们并没有对这个PushButton按钮的clicked信号和on_pushButton_clicked()槽函数进行连接,但是在按下按钮的时候也执行了这个函数。

我们会发现这个系统自动生成的槽函数的名字有点特别,它是由on + 当前控件名 + 信号组成的,然后通过调用函数**QMetaObject::connectSlotsByName(Widget)**来实现自动连接信号和槽的功能。

所以当我们使用图形化创建按钮的时候可以直接用系统生成的槽函数。

3.2自定义信号

在QT中支持程序员自定义信号,信号本质上也是函数,它是一类特殊的函数。

1)这类自定义的信号只需要在QT中声明,无需定义,QT系统会在编译的时候自动生成定义,程序员无法进行干预。

2)该函数的返回值只能是void,可以有参数也可以没有参数,参数数量不限制。

3)在声明自定义信号的前面需要加上QT中扩展的关键字signals,编译的时候如果扫描到了这个关键字,QT就会知道这个是一个自定义信号,并自动生成一些代码。

在类中自定义一个信号:

连接信号和槽,这里还需要手动发送信号,可以使用关键字emit。

QT中内置的信号已经内置在框架中了,用户在GUI中的操作就会触发某些信号,所以无需写代码进行触发。

还可以通过其他控件发送信号去执行槽函数来发送我们自定义的信号:

1)创建一个PushButton

2)在系统生成的槽函数中来发送自定义的信号

执行流程:

所以,发送信号可以在代码中的任意地方,自定义信号可以通过其他信号来发送。

实际上,emit在QT5以上的版本中已经没有实质性的操作了,但是为了可读性我们一般在发送信号的时候都会带上,表面这是在发送信号。

3.3带参数的信号和槽

在QT中信号和槽可以带参数,信号的参数可以大于或等于槽的参数个数,但是要求它们的类型是一致的。

传参是为了更好的复用那些代码整体逻辑一致,只不过数据不同的情况。

1)信号与槽参数的个数一致时:

这里通过两个按钮发送了两个带有不同参数的自定义信号来改变窗口的标题:

修改1:

修改2:

2)信号与槽的参数的个数不一致时:

为了使代码更加灵活,允许信号多于槽函数的参数个数,也就意味着更多的信号可以使用同一个槽函数,这里需要保证类型一致,否则编译会报错。

当信号参数个数大于槽函数时,槽会从左向右依次接受它自己的N个参数。

有1个参数的信号发射调用槽函数:

有2个参数的信号发射调用槽函数:

4.信号和槽存在的意义

在现代的许多的GUI开发系统中,其实并没有信号与槽这个概念,而直接是点击某个按钮就触发某种事件,是一种一对一的关系。但是在QT中不同的是,将信号与槽进行解耦合了,希望能实现一对一,一对多,多对多的关系。

上面的案例我们已经说过了很多信号与槽一对一的案例,下面来讲解一下其他两种情形:

1)一个信号对多个槽函数,也就是说触发一个信号会执行多个函数

这里将button的clicked信号和下面两个槽函数连接,触发clicked信号以后,两个槽函数都被调用了

2)一个槽函数对应多个信号

我们在上面测试信号与槽的参数的个数不一致时,将同一个槽函数连接了多个信号,使槽函数更加灵活。

3)多个信号对应多个槽函数

既然信号与槽函数都可以支持一对一,一对多,那么多对多肯定也是支持的

5.信号与槽的断开连接

可以使用disconnect来断开一个信号与一个槽函数的连接,这里先将button的clicked信号与handleclick进行连接,触发以后在handleclick内部断开连接,并建立与handleclick2的连接。

当我们第二次点击按钮的时候就会执行handleclick2了。

6.信号与槽的优缺点优点:

**优点松散耦合:**信号发送者不需要知道发出的信号被哪个对象的槽函数接收,槽函数也不需要知道哪些信号关联了⾃⼰,Qt的信号槽机制保证了信号与槽函数的调⽤。⽀持信号槽机制的类或者⽗类必须继承于 QObject类。

**缺点效率较低:**与回调函数相⽐,信号和槽稍微慢⼀些,因为它们提供了更⾼的灵活性,尽管在实际应⽤程序中差别不⼤。

通过信号调⽤的槽函数⽐直接调⽤的速度慢约10倍(这是定位信号的接收对象所需的开销;遍历所有关联;编组/解组传递的参数;多线程时,信号可能需要排队),这种调⽤速度对性能要求不是⾮常⾼的场景是可以忽略的,是可以满⾜绝⼤部分场景。

7.结语

至此,我们已经全面剖析了 Qt 中最核心、最迷人的机制------信号与槽(Signals & Slots)

从最初的概念理解,到 connect 函数的灵活运用,再到自定义信号与槽以及多对多关系的实战,我们可以清晰地感受到 Qt 设计哲学的精髓:"解耦"

  1. 机制的核心:它打破了传统回调函数的强依赖,让对象之间的通信变得像"广播与收音机"一样松散且自由。发送者只需负责"呐喊"(emit),而无需关心谁在"倾听"。

  2. 灵活的代价 :虽然我们提到通过元对象系统(MOC)实现的信号槽在性能上略低于直接的函数回调,但在现代 GUI 开发中,这种微小的性能损耗换来的是代码极高的可维护性扩展性,这无疑是一笔非常划算的交易。

  3. 进阶之路:掌握了信号与槽,就等于拿到了通往 Qt 高级开发的钥匙。无论是跨线程通信,还是复杂的组件交互,都离不开这一机制。

相关推荐
用户805533698032 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner2 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz7 天前
QML Hello World 入门示例
qt
xcyxiner10 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner11 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner11 天前
DicomViewer (添加模型类)3
qt
xcyxiner12 天前
DicomViewer (目录调整) 2
qt
xcyxiner12 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
LDR00614 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术14 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript