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

信号与槽的介绍

1.信号和槽概述

qt中谈到信号,涉及到三个要素:1.信号源:由哪个控件发出信号。2.信号的类型:用户进行不同的操作就可能触发不同的信号(点击按钮触发点击信号、在输入框中移到光标触发移到光标的信号等)。3.信号的处理方式:槽(slot),其实就是一个函数,qqt中使用connect这样的函数把一个信号和一个槽关联起来,后续只要信号触发了,qt就会自动的执行槽函数。所谓的槽函数,本质是上也是一种回调函数。qt中,一定是先关联信号和槽,然后再触发这个信号,顺序不能颠倒,否则信号就不知道如何处理了。

2. 信号和槽的使用

qt中要想使用信号和槽,离不开一个关键的函数叫connect,它是QObject提供的静态的成员函数。Qt中提供的这些类,本身是存在一定的继承关系的了,比如QPushButton、QLineEdit等类是继承自QWidget(控件)类的,QWidget再往上还有个父类是QObject:

QObject就是其它qt内置类的祖宗。由于connect函数是QObject这个类提供的,其它qt的类又是间接的继承自这个类,所以都可以使用到connect。

接下来看看connect的具体的使用方式,这是connect的函数原型:

这个里面有5个参数,最后一个是个默认参数(暂时不考虑)。第一个参数要传一个QObject指针的sender,其实它就是信号源,它就描述了当前信号是哪个控件发出来的。第二个参数,描述了信号的种类,发出的是哪一个信号。第三个和第四个参数描述的是信号如何处理,第三个说的是哪个对象(控件)负责处理,第四个说的是这个对象该怎么处理(要处理信号的对象提供的成员函数)。下面写代码来演示一下,我们的例子是界面上有个按钮,用户点击按钮关闭窗口:

当前有了窗口和关闭按钮,只不过现在点按钮并没有任何效果。继续完善,我们的所谓的信号,也是qt中的对象内部提供的一些成员函数:

click是一个slot函数,作用是在调用的时候相当于点击了一下按钮。clicked(过去分词),才是要触发的点击信号(图标上有锯代表槽函数,带信号的表示信号函数)。connect要求第一个参数和第二个参数是匹配的。button的类型如果是QPushButton*,此时第二个参数的信号必须是QPushButton内置的信号(它父类的信号),不能是一个其它的类,比如QLineEdit的信号。继续看,通过Widget来相应前面的点击信号:

close是QWidget内置的槽函数,Widget继承自QWidget,也就继承了父亲的槽函数。close槽函数功能是人家内部实现好的,不需要我们去关心,具体的功能是关闭当前的窗口/控件。后续针对button进行点击,Widget就会关闭。下面测试可以发现,点击后就关闭了。

写完了代码,现在还有两个问题:1.怎么知道的QPushButton有个clicked信号?怎么知道QWidget有个close槽?Qt里到底都提供了哪些内置的信号和槽可以让我们直接使用呢?多看文档:

在QPushButton中没有找到clicked信号。在翻阅文档的时候,如果在当前类中没有找到对应的线索,不妨看看这个类的父类:

可以看到QPushButton的父类是QAb,子类是QCo。abstract是抽象的意思,qt中会提供好几种按钮,这些按钮之间存在一些共性内容,就把这些共性的东西提取出来,放到了QAbstractButton类里面,按钮把共性继承再扩展自己的。点过去看看:

往下翻找:

看到了信号函数,它的子类就都可以使用。点开后也有描述:

2.connect第二个参数和第四个参数都是char*类型的,但我们填写的是函数指针:

那char*和函数指针是同一个东西吗?它们都不是一样的类型。我们点进去看看声明:

所以指向这个函数的指针是void(*)()类型的指针,和char*不是一个类型。那形参是char*,我们传的函数指针类型,c++中不允许用两个不同的指针类型相互赋值,这里为什么可以?点开文档发现也是char*:

其实这个函数声明是以前旧版本的Qt的connect函数的声明,以前版本中传参的写法和现在其实也是有区别的。给信号参数传参要搭配一个SIGNAL宏,给槽函数传参搭配一个SLOT宏,搭配宏可以把函数指针分别转为char*。以前版本这样写:

从Qt5开始对上述写法做了简化,不需要写这两个宏了,给connect提供了重载版本。重载版本中,第二个参数和第四个参数成了泛型参数,就允许传入任意类型的函数指针:

此时connect函数就带有了一定的参数检查功能,如果你传入的第一个参数和第二个参数不匹配,或者第三个参数和第四个参数不匹配(不匹配说的是2 4参数的函数指针不是1 3参数的成员函数),此时代码编译出错。

前面的例子中给的信号是qt内置的,槽也是qt内置的,但有时候内置的不一定能满足我们的要求,所以我们还要能自定义信号和槽。先来说说自定义槽,所谓的slot就是一个普通的成员函数,自定义一个槽函数操作过程和自定义一个普通的成员函数没有什么区别:

下面来测试一下:

再来说说第二种定义槽函数的方式,用qt designer,先拖动一个Button过去:

此时就能使用ui->objectName访问自动生成的控件:

按照第一种方式继续通过connect完成信号槽的连接是这样的:

但还有更容易的方式:

选择clicked点击:

发现qt creator生成了函数的声明和定义,我们编写就行了:

下面测试一下:

发现第二种方式没有connect,在qt中除了通过connect来连接信号槽之外,还可以通过函数名字的方式来自动连接。我们看看自动生成的槽函数的名字:

on是固定的前缀,pushButton是按钮的objectName,在后面是信号的名字。通过函数名可以了解到,把pushButton这个按钮它的clickde信号和当前函数建立连接。当函数名符合上述规则之后,qt就能自动的把信号的槽给建立上联系。比如故意把名字后面改为qt没有的信号click试一下:

此时点击按钮没有任何反应。通过提示也知道,qt中调用画红线的这个函数的时候,就会触发上述自动连接信号槽的规则,这正式在自动生成的ui_widget.h中调用的:

如果我们通过图形化的界面创建控件,还是推荐使用第二种快速的方式来连接信号槽;如果我们是通过代码的方式来创建控件,还是得收到connect,因为这种情况代码中没有调用connectSlotByName函数。

下面来说说怎么自定义信号函数,qt中也允许自定义信号。自定义函数槽非常关键,开发中大部分情况都是需要自定义槽函数的,槽函数就是用户触发某个操作之后要进行的业务逻辑。自定义信号比较少见,实际开发中很少会需要自定义信号,信号就对应到用户的某个操作,在GUI中用户能够进行哪些操作是可以穷举的,qt内置的信号基本上已经覆盖到了上述所有可能的用户操作。因此使用qt的内置信号,就足以应付大部分的开发场景了。咱们的Widget虽然还没有定义任何信号,由于继承自QWidget和QObject,这两类里面已经提供了一些信号了,可以直接使用。下面来看看自定义信号:1.所谓的qt的信号,本质上也就是一个函数。但是信号则是一类非常特殊的函数,程序员只要写出函数声明并且告诉qt这是一个信号即可。这个函数的定义,是qt在编译过程中自动生成的(自动生成的过程,程序员无法干预)。2.作为信号函数,这个函数的返回值必须是void。有没有参数都可以,甚至也可以支持重载。下面简单演示一下(signals是qt自己扩展出的关键字,qmake时会调用一些代码的分析/生成工具,扫描到类中包含signals这个关键字的时候就会自动的把下面的函数声明认为是信号,并且给这些信号函数自动的生成函数定义):

此时就会把signals下面的函数声明当信号并给信号自动生成对应的函数定义。下面再把信号和槽关联起来:

下面测试一下:

没反应,因为此时信号和槽建立连接不代表信号发出来了。那这个自定义的信号如何触发呢?Qt内置的信号都不需要手动通过代码来触发,用户在GUI进行某些操作就会自动触发对应信号(发射信号的代码已经内置到qt框架中了)。我们自定义的要自己emit(发射)+定义的信号:

发送的信号的操作可以在任何合适的代码中,不一定在构造函数里。比如这样(ui中创建按钮和信号):

此时是这样一个过程:

其实现在qt5中emit什么都没有做,真正的操作都包含在mySignal内部生成的函数定义了,不加emit信号也是可以的发出去的(建议加):

3.带参数的信号和槽

我们前面写的信号和槽都是不带参数的,信号函数和槽函数其实也是可以带参数的。当信号带有参数的时候,槽的参数必须和信号参数一致。此时发射信号的时候就可以给信号函数传递实参,与之对应的这个参数就会被传递到对应的槽函数中,此时就可以起到让信号给槽传参的效果了。下面加个参数看看:

这里的参数必须一致。一致主要是要求类型,个数如果不一致也可以,不一致的时候要求信号的参数的个数必须要比槽的参数的个数要更多。进一步完善实现代码:

此时信号的实参会被传到槽函数的实参里面。下面测试一下:

传参可以起到复用代码的效果,有多个逻辑,逻辑上整体一致,但是涉及到的数据不同,就可以通过函数参数来复用代码并且在不同场景中传入不同的参数即可。比如多加一个按钮给第二个按钮也设置槽函数:

继续完善:

现在信号是同一个信号,槽也是同一个槽,但是可以通过参数达成不同的效果:

qt中很多内置的信号也只带有参数的,这些参数不是我们自己传递的。

下面再看看参数个数不一致的情况:

现在信号的参数多,槽的参数少。继续补齐:

此时虽然槽函数只有一个参数,但不影响程序的正常运行。测试发现正确,所以信号函数的参数个数超过了槽函数的参数个数,此时是可以正常使用的。下面反过来试一试:

下面再次去测试一下:

有很多报错,所以信号函数的参数个数少于槽函数的参数个数,此时代码无法编译通过。直观的思考,应该是要求信号的参数个数和槽的参数个数严格一致,此处为啥允许信号的参数比槽的参数多呢?一个槽函数有可能会绑定多个信号,如果我们严格要求参数个数一致,就意味着信号绑定到槽的要求就变高了。换而言之当下这样的规则,就允许信号和槽之间的绑定更灵活了,更多的信号可以绑定到这个槽函数上了。个数不一致,槽函数就会按照参数顺序拿到信号的前N个参数,至少需要确保槽函数的每个参数都是有值的。要求信号给槽的参数可以有富裕,但不能少。

再看一下参数类型的影响:

下面测试一下:

同样编译报错。因此带有参数的信号,要求信号的参数和槽的参数一致,类型和个数满足要求(信号的参数个数要多于槽的参数个数)。

再介绍一个和信号槽与关系的部分,在widget.h中针对Widge类进行声明,里面有个这样的宏:

Qt中如果要让某个类能够使用信号槽(可以在类中定义信号和槽),则必须要在类最开始的地方写下Q_OBJECT宏,这是Qt中的硬性规定。如果不加这个宏,这个类在编译的时候也会出错。

4.信号与槽的断开

我们可以使用disconnect来断开信号槽的连接,disconnect使用的方式和connect是非常类似的。disconnect用的比较少,大部分情况下把信号和槽连上后就不必管了,主动断开往往是把信号重新绑定到另一个槽函数上。下面演示一下,先拖动一个按钮:

继续完善:

此时点击按钮窗口标题发生改变:

然后再用另一个按钮切换槽函数:

然后来完善:

下面演示:

此时点第一个按钮标题改变,再点第二个按钮后再点第一个,标题又改变。如果没有disconnect,就会构成一个信号绑定两个槽函数,触发信号的时候两个槽函数都会执行。

5.使用lambda表达式定义槽函数

定义槽函数的时候,也是可以使用lambda表达式的。很多编程语言都支持这个语法糖,它本质就是一个匿名函数,主要应用在回调函数的场景中。下面结合代码看一下效果:

下面运行一下:

说明槽函数可以顺利执行了。现在我想点击按钮的时候把按钮的位置移动一下怎么办呢:

发现此时用的时候说button按钮没有定义。因为lambda表达式是一个回调函数,这个函数无法直接获取到上层作用域中的变量的。当前button定义是在9~20这个作用域,而lambda是自成一个体系,获取不到外面的变量。lambda为了解决上述问题,引入“变量捕获”语法,通过变量捕获获取到外层作用域中的变量,在方括号中下手:

此时相当于让lambda表达式能够把外层作用域中的button变量名捕获到lambda表达式里面,这时候就可以在lambda表达式里面使用了:

类似的比如想在lambda表达式里面对this进行操作,就可以捕获一个this,比如移动一下窗口:

此时运行可以看到按钮和窗口位置都会发生移动。如果当前lambda里面想使用更多的外层变量咋办?可以写作[=],这个写法的含义就是把上层作用域中所有的变量名都给捕获进来:

后续如果我们对应的槽函数比较简单,而且是一次性使用的,就会经常写作这种lambda的形式。另外也要确认捕获到lambda内部的变量是有意义的,回调函数执行时机是不确定的(用户啥时候点击按钮不知道的),无论何时用户点击了按钮,捕获到的变量都能正确使用(不要用的时候已经被释放了)。由于此处button是new出来的变量,生命周期跟随整个窗口(挂到对象树上,窗口关闭才会释放),这个东西就可以在后面随时使用了。类似的this指向的对象widget,这个变量是在main函数结束销毁,main函数结束说明进程结束了,只要进程不结束widget就可用,this也就可用了。lambda除了可以按照值的方式来捕获变量[=]还可以按照引用的方式来捕获[&],但很少这样写,捕获到的变量一般就是各种控件的指针。指针变量按照值传递或者引用来传递都无所谓,如果按引用,还得更关注这个引用的变量本身的生命周期。lambda语法是c++11中引用的,对于Qt5及其更高版本,默认就是按照c++11来编译的。如果使用Qt4或者更老的版本,就需要手动在.pro文件中加上c++11的编译选项:

http://www.cnnetsun.cn/news/3570987.html

相关文章:

  • 栈的应用(括号匹配)
  • 新手部署 OpenClaw 2.7.9 避坑全攻略,网关离线、安全拦截处理办法(含安装包)
  • 算力、先验知识与自主进化:以《苦涩的教训》审视 LLM 边界及下一代智能范式转向
  • 如何快速搭建个人漫画图书馆:哔咔漫画下载器终极完整解决方案
  • RS485相关知识
  • 后备命令处理_add-fallback-commands
  • SolidWorks快捷键全攻略:从S键到自定义,解锁高效设计
  • 一文读懂汽车CAN总线 —— 从原理到故障诊断
  • 精准授时破局时序难题,NTP 授时服务器筑牢各行业时间基准
  • UE4样条曲线高效铺路:5分钟实现地形自适应道路生成
  • pythonlist案例(解包)
  • 【Bug已解决】DPOTrainer does not work for multimodal Gemma 4 解决方案
  • 嵌入式USB接收端点寄存器配置详解:从FIFO、DMA到双缓冲实战
  • Python自动化视频混剪工具开发:从素材搜索到合成全流程
  • 数据迁移一致性保障:三阶段验证与双通道审计实践
  • 国际集运物流模块开发|Taocarts 反向代购系统物流核算与轨迹同步技术方案
  • 2026亚太EMBA含金量中立测评|民营企业家择校参考
  • Django网络安全学习系统:计算机毕设实战指南与部署教程
  • 回溯算法精解:从排列组合问题掌握决策树与剪枝核心思想
  • MCP Server:AI Agent安全高效调用业务API的标准化方案
  • 小程序毕设项目: 基于 Node.js 的实验课堂日志填报、审核与统计平台 智慧实验室教学信息记录管理系统(源码+文档,讲解、调试运行,定制等)
  • OpenHarmony 6.1(API23)端侧 AI Native C++ 交叉编译工具链配置与验证手册
  • Windows下Rust与C/C++混合开发环境配置:WinLibs GCC 15与CMake 4实践指南
  • 从零实现C++多线程HTTP服务器:深入理解网络编程与并发模型
  • 如何快速配置Arnis:高级用户的完整Minecraft城市生成指南
  • STM32F103程序下载全攻略:串口与SWD详解
  • Cocos Creator Shader实战指南:从基础到高级特效实现
  • WPS AI模板市场私域运营秘术:如何用1个模板撬动2000+精准用户并沉淀至企微?
  • Android功耗系列专题理论之三:cpu 功耗问题分析方法
  • PHP 8.5容器化实战:从基础镜像到生产部署