当前位置: 首页 > news >正文

[QT]信号与槽

一:信号与槽概述

在 Qt 框架中,GUI 程序的核心是响应用户交互,而用户与控件的每一次互动都被称为 "事件"。例如,点击按钮、勾选复选框等操作都会触发相应事件,每个事件发生时,控件都会发出一个 "信号"。

Qt 中的所有控件都具备接收信号的能力,一个控件甚至可以响应多种不同类型的信号。对于接收到的每个信号,控件都会执行对应的响应动作。这一机制与 Linux 中的 signal 机制有相似之处,都需要关注三个核心要素:信号的来源(哪个控件发出的信号)、信号的类型(具体是什么事件触发的)以及信号的处理方式(通过注册回调函数来响应信号)

在 Qt 中,对信号的响应动作被称为 "槽"(slot),其本质是一种可作为回调函数的特殊函数。Qt 提供了 connect 等函数来建立信号与槽的关联,一旦完成关联,当信号被触发时,对应的槽函数就会被自动调用。需要特别注意的是,必须先完成信号与槽的关联,再让信号被触发,这个顺序不可颠倒。

尽管信号与槽机制和 Linux 的 signal 机制有相似性,但它是 Qt 独有的消息传递方式,能够将原本独立的控件建立关联。例如,按钮和窗口本身是相互独立的控件,点击按钮原本不会对窗口产生任何影响,但通过信号与槽机制,就能实现 "点击按钮时窗口发生特定变化" 的效果。

具体来说,当用户对窗口或控件执行操作时,会引发特定事件,此时 Qt 对应的窗口类会发出相应信号,以此响应用户操作 —— 因此,信号的本质就是事件的体现。

而槽本质上是一个函数,它与普通的 C++ 函数并无本质区别:可以定义在类的 public、protected 或 private 区域,可以有任意参数,可以被重载,也可以被直接调用(但不能有默认参数)。它与普通函数的唯一区别在于:槽函数可以与信号建立关联,当关联的信号被发射时,槽函数会被自动执行。

二:信号与槽的使用

2.1:信号函数和槽函数定义

信号与槽机制的底层实现依赖于函数间的相互调用。每个信号都可以用特定的函数表示,称为信号函数;每个槽同样可以用函数表示,称为槽函数。例如,"按钮被按下" 这一信号函数可通过clicked()函数来体现,"窗口关闭" 这一槽功能函数则可通过close()函数实现。若要通过信号与槽机制达成 "点击按钮关闭窗口" 的效果,本质上就是clicked()函数调用close()函数的过程。

信号函数和槽函数通常隶属于某个类,与普通成员函数相比,它们有以下两个显著特点:

其一,信号函数需用signals关键字修饰,槽函数则需用public slotsprotected slotsprivate slots修饰。这些是 Qt 在 C++ 基础上扩展的专用关键字,用于明确标识信号函数与槽函数的身份。

其二信号函数仅需声明,无需定义(实现);而槽函数不仅需要声明,还必须进行定义(实现)。

2.2:信号与槽的连接

信号与槽的连接主要是通过connect函数来实现的,但是这个connect函数和linux套接字中的connect函数不同,下面给出Qt中conect函数的定义:

第一个参数(sender):QObject类型的指针,描述的是当前哪一个控件发出来的信号!

第二个参数(signal):char*类型的指针,信号的类型!

第三个参数(receiver):QObject类型的指针,描述的是当前哪一个对象(控件)接收信号负责处理!

第四个参数(member):char*类型的指针,怎样进行处理!

第五个参数:提供了一个默认参数,暂时不用考虑!

2.2.1:代码方式进行连接

下面我们通过代码方式使用connect函数来实现按钮按下关闭窗口的效果,具体如下所示:

上面虽然完成了按钮按下关闭窗口的效果,但是我们发现,实际中我们在填写connect函数的四个参数时,第二个参数和第四个参数我们填写的是信号函数的地址以及槽函数的地址,即函数指针,

可是在connect函数中定义中第二个参数和第四个参数都是const char*类型的指针,为什么会这样呢?函数指针怎么可能传参赋值给char*指针呢?

原因是上面那个const char*的参数写法是旧版本中Qt的connect的函数声明,且当时的写法和如今定义的也大不相同,当时给信号参数(第二个参数)传参时,需要搭配上SIGNAL宏,给槽参数(第四个参数)传参时,需要搭配上SLOT宏,示例中的connect如果按照当时的写法大概如下所示:

connect(my_button, SIGNAL(&QPushButton::clicked), this, SLOT(&My_Widget::close));

而自Qt5以来,人们开始对上述写法做出了简化,不再需要写SIGNAL和SLOT宏了,而是提供connect的重载函数,通过模版化,第二个参数和第四个参数变成了泛型参数,可以使我们传入任意类型的函数指针,大大提升了connect函数的适用性!具体定义如下所示:

我们可以看到,此时的connect函数使用了泛型模版,保证第二个参数和第四个参数可以接收任意类型的函数指针,同时我们也可以看到,Qt通过类型萃取器包装了第一个参数和第三个参数,使得此时的connect函数带有一定的参数检测能力,如果第二个参数的函数指针不是第一个参数的成员函数,或者第四个参数的函数指针不是第三个参数的成员函数,那么此时connect将报错!

2.2.2:ui方式进行连接

我们不仅可以通过手动编写代码的方式来进行信号和槽函数的连接,Qt还支持可视化ui界面的方式可以快速帮助我们完成信号和槽的连接,具体步骤如下图所示:

我们可以看到,上述操作并没有进行connect连接,哪怕Qt使用元编程技术,会把ui文件形成的xml代码内容生成出一个.h文件,但是当我们打开那个.h文件也并没有找到使用connect函数进行连接,这是为什么呢?原因就在于在Qt中,除了使用connect函数进行信号和槽的连接,还可以通过函数名字的方式自动连接!!!如同上图所示,我们并没有使用connect函数,但是我们自动生成了一个on_pushButton_clicked()这样一个槽函数(on是固定前缀,pushButton是控件名称(即ui文件中的objectname),clicked是信号类型),通过这个函数名字,即可完成信号与槽的连接!!!

2.3:自定义信号和槽

2.3.1:自定义槽函数

槽函数除了上面使用Qt内置的槽函数,如colse()槽函数,和使用UI界面自动生成on+objectname+信号类型的槽函数之外,我们还可以自己手动在类中添加成员函数,使其成为connect函数中第三个参数的成员函数,早期的 Qt 版本要求槽函数必须写到 "public slots" 下,但是现在⾼级版本的 Qt 允许写到类的 "public" 作⽤域中或者全局下; 自定义的槽函数返回值为 void,需要在类中声明,也需要实现;可以有参数,可以发⽣重载;然后通过connect函数连接,具体如下所示:

但如果想⽅便的编写自定义槽函数,⽐如在编写函数时连函数名都不想定义,则可以通过 Lambda表达式来达到这个⽬的。 Lambda表达式 是 C++11 增加的特性。C++11 中的 Lambda表达式 ⽤于定义并创建匿名的函数对象,以简化编程⼯作。至于Lambda的相关知识我在之前有总结过,如果不明白可以去看看这片博客:https://blog.csdn.net/KL4180/article/details/134628015?fromshare=blogdetail&sharetype=blogdetail&sharerId=134628015&sharerefer=PC&sharesource=KL4180&sharefrom=from_link

下面给出用lambda表达式来写自定义槽函数的例子:

这里注意一点即可,如果想使用my_button和this只需要传值捕捉即可,因为本身他们就是指针,如果这里再使用传引用捕捉,程序会崩溃!!!因为传值捕捉通过 “拷贝地址” 来保证:只要对象本身没被销毁,即使外部变量(指针)被修改,lambda 里拷贝的旧地址仍能访问到原来的有效对象

而传引用捕捉是 “直接绑死外部变量的引用”,一旦外部变量(或它指向的对象)生命周期结束,引用就会失效,触发崩溃。因此,在 Qt 中用 lambda 作槽函数时,传值捕捉([=])更安全,尤其是涉及对象指针时,能避免对象销毁导致的悬空引用问题。

2.3.2:自定义信号

在Qt中不仅能自定义槽函数,我们还可以自定义信号,但是和自定义槽函数不同,自定义槽函数非常关键,在现实开发中,大部分情况下我们都需要自定义槽函数,进行对应的业务逻辑。而自定义信号比较少见,因为在GUI操作中,用户的操作是可以穷举的,比如点击,按下,勾选等,而Qt内置的信号基本上能够覆盖用户所有可能的必要操作了,虽说如此,我们还是有必要掌握自定义信号的方式方法。

所谓Qt中的信号,本质上就是一个函数,在Qt5以及更高版本中,槽函数已经和普通的成员函数没有差别了,但是信号则是一类非常特殊的函数。我们只需要写出函数声明,并告诉Qt这只是一个信号即可,至于这个函数定义,Qt会在编译时自动生成,无需我们操作干预!同时⾃定义信号函数也有书写规范要求,首先自定义信号函数必须写到 "signals"关键字下;其次返回值为 void,只需要声明,不需要实现; 最后自定义信号可以有参数,也可以重载。

下面我们尝试自己自定义一个信号,并通过connect函数连接,然后改变窗口标题,具体如下:

这里需要注意,由于我们是自定义信号,那么connect函数的第一个参数就需要写自定义信号所在

类,同时我们也发现,似乎我们的自定义信号并没有发挥作用,窗口的标题没有变化,槽函数并没有执行,这是因为是什么呢?因为connect函数只是将信号和槽进行连接,而并不是连接+触发信号,之所以之前使用connect函数就能触发,是因为哪些都是Qt的内置信号,不需要我们通过手动的方式进行触发,用户在对GUI界面进行操作时,自动触发对应信号,发射信号的代码已经内置到Qt框架内了,

所以,为了解决自定义信号的问题,Qt又提供了一个关键字“emit”,使用时只需要在其后面加上自定义信号调用即可,具体如下所示:

如此,就可以解决自定义信号发送信号的问题,emit关键字不仅仅可以在构造函数使用,也可以在其他的地方调用,不一定非要在构造函数中。

其实,即使不使用emit关键字也可以发送自定义信号,只需要调用一下即可,具体如下所示:

加不加emit关键字都行,因为在Qt5之后emit基本上啥都没有干,真正的发射操作都包含在my_siganl()内部生成的函数定义中了,所以,写不写emit,即使不写emit,自定义信号也能发射!

之所以我上面还要在介绍一下emit关键字,是因为加上emit关键字会使得代码的可读性会更高,这样能更明显的标识出,这里是在发射自定义信号!

2.4:带参数的信号和槽

Qt 的信号与槽机制还支持带参数的使用方式。需要注意的是,信号函数的参数列表必须与所连接的槽函数的参数列表保持一致。参数类型必须一致参数个数可以不一致但是不一致的情况下,必须是信号函数的参数个数>槽函数的参数个数! 这样一来,当信号被触发并调用到对应的槽函数时,信号函数中的实际参数就能顺利传递到槽函数的形式参数中。下面给出一个具体例子:

上述是自定义信号函数参数和自定义槽函数参数相同的情况,下面再给出自定义信号函数参数大于自定义槽函数参数的情况,具体如下所示:

我们可以看到,当信号参数大于槽函数参数时,程序照样运行,且槽函数会按照参数顺序,取前N个参数,至少确保槽函数每一个参数都能拿到值!但是反过来,槽函数参数比信号参数多,那么程序将出错,而之所以要设计成信号参数>=槽函数参数,就是因为一个槽要绑定不止一个信号,如果搞成参数一对一,那么就表示信号绑定到槽的要求变高了,所以为了程序能够更加灵活,为了让更多的信号绑定到槽上,信号参数>=槽函数参数这种规则是必要的!

三:信号与槽断开连接

disconnect和connect的用法差不多,但是disconnect用的比较少,一般把信号和槽连接之后就不必关心了,在极少数情况下我们使用disconnect往往是把信号绑定到另一个槽函数上。

如上所示,我们先把第一个按键和槽函数一绑定,然后在第二个按键的槽函数中,将第一个按键和槽函数一解绑,再把第一个按键和槽函数二绑定到一起!

四:信号与槽存在的意义

Qt中的信号与槽的意义就在于解耦合,即把用户触发的控件和处理对应用户操作逻辑的解耦合,以及多对多的效果,即一个信号可以connect多个槽函数,而一个槽函数也可以被多个信号connect,

其多对多更是Qt最想达到的效果!下面给出多对对的示例:

综上,Qt引入信号槽机制最本质的目的就是为了让信号和槽之间按照多对多的方式来进行关联!

而其他的GUI框架往往不具备Qt这样的特性,但在实际GUI开发中,“多对多”是伪需求,实际中很少碰到,大部分情况下,“一对一”即够用了。

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

综上,这是我对QT中信号与槽机制的一些简单且浅薄的认识,如有错误之处,请各位大佬指正!


文章转载自:

http://DlMTQO9N.rjqtq.cn
http://3k67yoC3.rjqtq.cn
http://4qoNc7CY.rjqtq.cn
http://8bYYFzcE.rjqtq.cn
http://MjzwvbKW.rjqtq.cn
http://oVoQaiNz.rjqtq.cn
http://L5ipnMsl.rjqtq.cn
http://L4AY4S25.rjqtq.cn
http://ZCRa142J.rjqtq.cn
http://4XaepgYo.rjqtq.cn
http://sYtX794W.rjqtq.cn
http://OQhrM8eg.rjqtq.cn
http://QDh6ceGL.rjqtq.cn
http://9vyIe9ja.rjqtq.cn
http://APDSuIIs.rjqtq.cn
http://RpfW9z84.rjqtq.cn
http://WeGSZ6BI.rjqtq.cn
http://fYPmcENm.rjqtq.cn
http://3tEez1Ag.rjqtq.cn
http://1Wj5JTNR.rjqtq.cn
http://NpaJYmpo.rjqtq.cn
http://8HJmUokk.rjqtq.cn
http://wdLj3qdP.rjqtq.cn
http://Gjmx0hZj.rjqtq.cn
http://HSuXmxyR.rjqtq.cn
http://VtPAwxTO.rjqtq.cn
http://0hUi7D0T.rjqtq.cn
http://GzRqEDMU.rjqtq.cn
http://DiikyL0R.rjqtq.cn
http://Kh6NiKSw.rjqtq.cn
http://www.dtcms.com/a/383112.html

相关文章:

  • 高精度运算:大数计算全攻略
  • LeetCode 3302.字典序最小的合法序列
  • 深入解析3x3矩阵:列优先与行优先约定的全面指南
  • Codeforces 1049 Div2(ABCD)
  • 【开题答辩全过程】以 “居逸”民宿预订微信小程序为例,包含答辩的问题和答案
  • AWS IAM 模块全面优化:实现完整生命周期管理与性能提升
  • RK3568 PWM驱动基础知识
  • 贪心算法应用:钢铁连铸优化问题详解
  • 9. LangChain4j + 整合 Spring Boot
  • 返利app的消息队列架构:基于RabbitMQ的异步通信与解耦实践
  • React Native架构革命:从Bridge到JSI性能飞跃
  • Qt---描述网络请求QNetworkRequest
  • XLua教程之Lua调用C#
  • 第七章:AI进阶之------条件语句(if-elif-else)(一)
  • 从希格斯玻色子到QPU:C++在高能物理与量子计算领域的跨界征程与深度融合
  • 二、vue3后台项目系列——安装相关依赖、项目常用辅助开发工具
  • Knockout.js 备忘录模块详解
  • VS2022下载+海康SDK环境配置实现实时预览
  • 前端基础 —— C / JavaScript基础语法
  • 手搓一个 DELL EMC Unity存储系统健康检查清单
  • 字节M3-Agent:如何实现一个支持多模态长期记忆与推理的Agent
  • TCL华星计划投建第8.6代印刷OLED产线
  • Qt学习:moc生成的元对象信息
  • Java—JDBC 和数据库连接池
  • 软件工程实践四:MyBatis-Plus 教程(连接、分页、查询)
  • 用 Go 快速上手 Protocol Buffers
  • Java Stream 流学习笔记
  • Linux线程id与简易封装线程实现
  • 公链分析报告 - Secret Network
  • JavaScript 简单链表题目试析