【C++ Qt】信号和槽(内配思维导图 图文并茂 通俗易懂)


每日激励:“不设限和自我肯定的心态:I can do all things。 — Stephen Curry”

绪论​:
本章是Qt中的第三章,也是我们理解Qt中必备的点 信号槽,它本质由信号和槽两个来实现,其中将细致的讲述如何自定义信号、槽,以及通过connect函数进行将信号和槽连接起来的实现用户通过控件和程序进行交互的操作。
————————
早关注不迷路,话不多说安全带系好,发车啦(建议电脑观看)。

在这里插入图片描述

信号和槽概述

首先了解信号是什么,它由三个部分组成:

  1. 信号源:谁发的信号
  2. 信号的类型:那种类别的信号
  3. 信号的处理方式:注册信号处理函数,在信号被触发的时候会自动调用执行。

同样的Qt中谈到信号也涉及到三点:

  1. 信号源:由那个控件发出的信号
  2. 信号的类型:用户进行不同的操作,就可能触发不同的信号
    1. 点击按钮
    2. 输入框汇总移动光标…

GUI程序就是要让用户进行操作,就是要和用户进行交互的,这个过程就需要关注,用户当前的操作具体是什么样的操作,也就是通过用户的操作触发的信号(信号)从而知道用户做了什么,根据这个信号进行对应的处理(槽)

  1. 槽(slot)–> 函数
    1. Qt 中可以使用 connect 这样的函数,把一个信号和一个槽关联起来
    2. 后续只要信号触发了,Qt就会自动的执行槽函数(本质就是 回调函数(函数适配器、比较器))

注意:

  1. 其中一定是 先把信号的处理方式准备好,再触发信号~
  2. Qt 中,一定是先关联 信号 和 槽,然后再触发信号(顺序不能乱)

connect

connect是QObject 提供的静态成员函数
并且Qt中提供的这些类,本身是存在一定的继承关系的(如下图)
在这里插入图片描述
其中:QObject 就是其他 Qt 内置类的“祖宗”(Qt4才引入继承机制…)、所以因为connect是QObject中的函数,所有许多控件都能继承使用

connect的具体使用

connect (const QObject *sender,const char * signal ,const QObject * receiver ,const char * method ,Qt::ConnectionType type = Qt::AutoConnection )1. sender 前信号来自那个控件
2. signal 信号的类型
3. receiver 信号如何处理的类
4. method 这个对象该如何处理(要处理信号提供的成员函数!)
5. type ⽤于指定关联⽅式,默认的关联⽅式为 Qt::AutoConnection 一般不用设置

简单信号槽实例

  1. 创建按钮 QPushButton 变量 button并 构造 传递 this
  2. 设置文本 为关闭
  3. 设置位置 200 200
  4. 使用connect设置四个参数
    1. 传递 button 变量,设置信号源
    2. 信号的类型,根据按钮对象类域获取 clicked 信号函数
      1. 其中注意的是:上述两个参数必须对应匹配
      2. 也就是说 button 的类型 如果是 QPushButton*
      3. 那么 第二个参数的信号 必须是 QPushButton 内置的信号(父类的信号&QPushButton::clicked)
        在这里插入图片描述
    3. 处理信号的类对象:此处填写 this代表当前类
    4. 信号处理的函数,此处调用Widget::close(widget继承的类中的函数)作用:关闭当前窗口/控件
      在这里插入图片描述

其中的一些问题:

Qt 里面到底提供了许多内置的信号 和 槽 让我们可以直接使用(如:QPushButton的clicked信号、QWidget 的 close 槽)

通过文档查看:
在这里插入图片描述
其中 假如在某个文档中没有找到你想要找到方法、槽函数或者信号时,不妨看看他的父类
其中写在:
在这里插入图片描述

  • 其中 abstract 就是抽象类
  • Qt 中会提供很多种按钮,其中QAbstractButton中就会存在许多 “共性” 的内容
  • 这样就能通过继承的方式让,多个类中都使用到,并且不需要自行再定义:
    在这里插入图片描述
    其中clicked就在 QAbstractButton 内部:
    在这里插入图片描述
    其中clicked内部会写也是最要关注的点:什么时候触发信号?
    在这里插入图片描述

再次回顾connect函数:
在这里插入图片描述

  1. 其中第二个参数和第四个参数时,发现我们传递的是函数指针,但函数显示需要的参数类型是char *,这不是有问题吗?

其中C++中是并不允许不同类型间的传递的,那么是如何实现的呢?

  • 这个函数声明是以前旧版本的 Qt 的connect 函数声明
  • 老版本中的写法:需要加上两个宏 SIGNALSLOT,将函数指针转换为 const char*
    在这里插入图片描述
  • 从 Qt 5 中进行了修改,给connect提供了重载,给第二个参数和第四个参数 改成了 泛型参数,允许咱们传入任意类型的函数指针
    在这里插入图片描述

自定义槽

所谓的槽 slot 本质就是一个普通的成员函数,所以自定义槽本质就是新增成员函数

其中使用 conncet 连接的时候,将槽函数的参数填成自己的成员函数

代码实现信号槽

  1. 声明函数自己的槽函数:handle
  2. handle槽函数:
    1. 具体的实现:通过当前Widget类的this指针调用setWindowsTitle:设置窗口标题
  3. 通过connect进行PushButton按钮和handle槽函数的连接

具体信号槽源码:

widget.h

#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();void handle();
private:Ui::Widget *ui;
};
#endif // WIDGET_H

widget.cpp

#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
#include <QDebug>
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);//创建一个PushButton对象QPushButton* button = new QPushButton(this);button->setText("关闭窗口");button->move(200,200);//添加信号槽connect(button,&QPushButton::clicked,this,&Widget::handle);}Widget::~Widget()
{delete ui;
}void Widget::handle(){this->setWindowTitle("按钮已按下");
}

点击前:
在这里插入图片描述
点击后:
在这里插入图片描述

了解:在以前版本的Qt中,槽函数必须放到 public / private / protected slots这样的域内限定符中(现在版本并不需要这样写)
在这里插入图片描述

  • 此处的slots 是 Qt 自己的扩展关键字,它的实现基于 Qt 中广泛使用的元编程技术(基于代码,生成代码)
  • 元编程:qmake 构建Qt项目的时候,就会专门的扫描器,扫描代码中的关键字,基于这些关键字生成相关的代码

使用ui文件搭建信号槽

在拖拽式处,可以右键组件,找到构建槽这个选项
在这里插入图片描述

  1. 弹出的窗口就会列出 QPushButton 给我们提供的所有信号,都是能使用的
    在这里插入图片描述

  2. 双击后就能使用了,也会自动生成一个函数
    在这里插入图片描述
    并且函数的声明也会自动生成
    在这里插入图片描述

  3. 只需要在函数中编写即可!

    1. 方法同上
      在这里插入图片描述

其中我们在代码中发现并没有connect,所以Qt中除了connect来连接信号槽之外:
还能通过函数名的方式来自动连接(具体如下图)
在这里插入图片描述

  • 当名称出错后,就无法连接成功,并且还会报错(Qt 中调用connectSlotsByName的时候,就会触发上述的自动连接信号槽的规则,该函数就是在 ui_widget.h 中的)
    在这里插入图片描述
    在这里插入图片描述

总结:

  1. 通过界面化的创建控件,还是推荐使用名称的方式快速连接信号槽
  2. 但如果是通过代码的方式来创建控件的话,还是得手动的 connect(因为自己代码中本身没有 connectSlotsByName)

  • 自定义槽函数,非常关键,大部分情况下都是自定义槽函数的

  • 槽函数,就是用户触发某个操作之后,要进行的业务逻辑

  • 而自定义信号是比较少见的,实际开发中会很少需要自定义信号的

  • 因为信号就是用户的某个操作,而对于GUI中,用户能进行的操作是很少的,可以穷举的

  • Qt内置的信号,基本就可以应付大部分开发场景的


自定义信号

虽然创建的Widget没有定义任何信号,但由于继承了 QWidget 和 QObject,而这两个类中就提供了一些信号可以直接使用了

  1. 而Qt中的信号,本质上也是一个函数
  2. Qt 5 以及更高版本中,槽函数和普通函数之间没啥区别
  3. 信号则是非常特殊的,程序员需要写出函数声明,告诉Qt这是一个信号
  4. 这个函数的定义,是Qt编译过程中,自动生成的(自动生成的过程,程序员无法干涉)
  5. 信号在Qt中的特殊机制,他需要配合Qt框架做很多操作

自定义信号

信号函数创建的要求

  1. 这个函数的返回值必须是 void
  2. 有没有参数都可以

使用Qt 扩展的关键字:signals 进行创建信号

在qmake的时候分析 / 生成工具,将下面的函数声明认为是信号,并且给这些函数自动生成函数定义
在这里插入图片描述
就能将自定义的信号连接槽
在这里插入图片描述

建立连接,不代表信号发出来了,而此处只是简单的将信号和槽连接了,还需要触发信号

如何触发自定义信号

Qt 内置的信号,都不需要手动通过代码触发,用户在GUI进行某些操作,就会触发对应的信号(内置到Qt框架中了)
关键字emit发射自定义信号

emit 自定义信号
如:emit mySignal

在这里插入图片描述
但上述情况会在构造函数中立马触发信号,从而立马执行槽函数
对于发射信号的情况,并不是非要在构造函数中,而是应该根据具体情况,写在所需要的地方
具体见下面代码:
在这里插入图片描述

在最新版本中,即使不写emit,信号也能发出去!(mySignal构造中都写了)

带参数的信号和槽的注意点

信号和槽 也可以带参数

  • 当信号带有参数的时候,槽的参数必须和信号的参数一致!
  • 此时发射信号的时候,就可以给信号函数传递实参,与之对应的这个参数就会被传递到对应的槽函数中,也就起到了信号给槽传参的效果
  • 其中:参数必须一致,主要参数必须一致
    在这里插入图片描述
    然后对于实现代码来说,也需要进行添加参数
    在这里插入图片描述

对于传参来说可以起到复用代码的效果,根据不同的场景传递不同的参数
通过发送不同的信号达到不同的效果

Qt 中很多内置的信号,也是带有信号的(这些参数不是咋们自己传递的)
如下图
在这里插入图片描述

  • 其中可以允许个数不一致,但要求信号的参数个数必须比槽的参数个数要多

难道不应该严格要求一致吗?

  • 因为:信号槽之间的绑定不一定是1 对 1的,一个槽函数,有可能绑定多个信号,如果严格要求,也就意味着信号绑定槽的要求变高了
  • 换而言之,当下这样的规则,就允许信号和槽之间的绑定更灵活了,更多的信号可以绑定到这个槽函数上,个数不一致槽函数就会按照参数顺序,拿到信号的前N个参数(只能多不能少)

在这里插入图片描述

在这里插入图片描述

  • 并且同样要保证信号的参数类型和槽的参数类型一致
    在这里插入图片描述

Q_OBject宏:

Qt 中如果要让某个类能使用 信号槽(可以在类中定义信号和槽函数)
则必须要在类最开始的地方,写下 Q_OBject 宏
(这个宏给展开成许多额外的代码)
在这里插入图片描述
在这里插入图片描述

总结:

所谓的信号槽,终究是要解决的问题,就是响应用户的操作,信号槽,其实在GUI开发的各种框架中,是一个比较特色的存在

connect机制是为了:

  1. 解耦合,把触发用户操作的控件 和 处理对应用户的操作逻辑解耦合
  2. 多对多的效果:一个信号,可以connect到多个槽函数,一个槽函数也可以被多个信号connect
  3. 而其中的connect本就就像一个关联表,将多对多的信号和槽的情况连接起来

在这里插入图片描述
综上,Qt 引入信号槽记住,最本质的目的,就是为了能够让信号和槽之间按照多对多的方式来进行关联,其他GUI框架往往不具备这样特性

而在GUI开发的过程中,“多对多”这件事,其实是个“伪需求”,绝大多数 一对一就够了

disconnect 信号与槽的断开

  • 使用 disconnect 来断开信号槽的连接

  • connect和disconne使用的方式是类似的,主动断开往往是把信号重新绑定到另一个槽函数

具体操作如下:

  • 首先第一次点击pushbutton只会触发一个槽函数
  • 当再次把一个信号绑定一个槽函数后,此时一个信号就会触发两个槽函数(也就是验证了支持多对多!,如下图)
    在这里插入图片描述
    在这里插入图片描述

实现一个断开连接,然后再次连接新的信号和槽

  • 当点击pushButton2按钮后就会断开 第一个 pushButton的信号槽
  • 然后连接到新的槽函数handleClick2
    在这里插入图片描述
  • 其中若不进行断开,则当有一个信号后会触发两个槽函数
    在这里插入图片描述

使用lambda表达式定义槽函数

lambda本质就是一个匿名函数,主要应用于“回调函数”场景(一次性使用)

  • lambda表达式:{}(方括号:可捕捉参数、圆括号:参数、大括号:函数体)
  • 通过lambda表达式大大的减少了槽函数的编写(不用再去声明/定义槽函数了)
    在这里插入图片描述

其中若要使用内部的成员变量/成员函数的话

  1. 需要给lambda表达式进行 “变量捕获”
  2. 假如要使用button,那么在括号中添加button变量
  3. 假若要捕获多个变量时,可以使用 = 的形式(值)捕获(拷贝得到)所有变量

源码:

#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
#include <QDebug>
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);//创建一个PushButton对象QPushButton* button = new QPushButton(this);button->setText("关闭窗口");//添加信号槽connect(this,&Widget::MySignal,this,[=]{qDebug() << "lambda表达式" ;button->move(200,200);});
}

在这里插入图片描述
在这里插入图片描述

注:

  1. 如果槽函数比较简单或者一次使用,那么就使用lambda表达式快速编程
  2. 其中要确定捕获到 lambda 内部的变量时有意义的(仍然存储没有被销毁),因为回调函数执行时机是不确定的,所以需要注意捕获对象生命周期
  3. 还能进行引用方式捕获那么就将 = 替代为 &(Qt中很少这样写,一般捕获的就是控件的指针,因为按引用还得跟关注变量的生命周期,变量可能会随栈直接释放)
  4. lambda 语法是 C++11 中引用的,对于Qt5及以上版本是默认按 C++11来编译的,而如果使用Qt4或者更老版本,就需要手动在 .pro 文件中添加 C++11的编译选项 CONFIG += c++11

总结回顾

  1. 信号槽是什么:设计三个要素
    1. 信号源
    2. 信号的类型
    3. 信号的处理方式
  2. 信号槽的使用
    1. connect
  3. 如何查阅文档
    1. 一个控件内置了那些信号,何时触发
    2. 控件内置了那些槽,作用是什么
    3. 一些需要的信号槽,可能在父类中存在
  4. 自定义槽函数
    1. 本质上就是自定义一个普通的成员函数
    2. 还可以让 Qt Creator 自动生成(虽然没显示写 connect,但还是可以通过函数名特定规则来完成自动连接)
  5. 自定义信号
    1. 本质也是一个成员函数(函数的定义是Qt自己生成的,咱们只需要写函数声明)
    2. signals自定义关键字中
    3. emit 来完成信号的发射
  6. 信号和槽还可以带有参数
    1. 发射信号的时候,把参数传递给对应的槽
    2. 信号的参数和槽的参数要一致(至少类型匹配,信号的个数要等于或多余槽的参数)
  7. 信号槽存在的意义
    1. 解耦合(高内聚低耦合(低耦合:代码间相互不会影响、高内聚:相关代码都写在一起))
    2. 多对多的效果(类似 mysql 中的多对多),一个信号多个槽、一个槽多个信号
  8. discount、lambda表达式简化槽函数的定义

本章完。预知后事如何,暂听下回分解。

如果有任何问题欢迎讨论哈!

如果觉得这篇文章对你有所帮助的话点点赞吧!

持续更新大量C++细致内容,早关注不迷路。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/77827.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【项目】基于MCP+Tabelstore架构实现知识库答疑系统

基于MCPTabelstore架构实现知识库答疑系统 整体流程设计&#xff08;一&#xff09;Agent 架构&#xff08;二&#xff09;知识库存储&#xff08;1&#xff09;向量数据库Tablestore&#xff08;2&#xff09;MCP Server &#xff08;三&#xff09;知识库构建&#xff08;1&a…

免费将静态网站部署到服务器方法(仅支持HTML,CSS,JS)

原视频链接&#xff1a;把HTML免费部署到网站上&#xff0c;实现别人也能访问的教程来啦QAQ_哔哩哔哩_bilibili 注意&#xff1a;仅支持HTML、CSS、JS。不支持Vue等框架。 1.打开网站www.wordpress.org 点击红框按钮 点击红框按钮下载wordpress模板文件并解压。 将自己编写的…

游戏引擎学习第235天:在 Windows 上初始化 OpenGL

奇怪有问题 之前没注意到 这个问题是Count 0 GlobalConstants_Renderer_UsedDebugCamer 打开的话会有Bug Count是零的话就不让排序了 game.h: 查阅 TODO 列表 大家好&#xff0c;欢迎来到 game Hero&#xff0c;这是一档我们在直播中一起编写完整游戏的节目。不幸的是&a…

使用eCharts绘制中国地图

eCharts官网&#xff1a;https://echarts.apache.org/zh/index.html 1. 首先新建一个html页面&#xff0c;并引入echarts <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-…

Linux与Anaconda环境部署与管理(运维交接)

文章目录 一、前言二、Linux基础命令三、进程管理与监控四、后台任务与服务管理五、Anaconda环境管理六、JAR包的运行与管理七、网络与端口映射八、安全与权限管理九、故障排查与日志分析十、附录 一、前言 本文将详细介绍Linux系统下的常用命令以及Anaconda环境管理&#xff…

php:实现压缩文件上传、解压、文件更名、删除上传临时文件、存入数据库等操作

一、效果图 1.上传文件 2.压缩包文件 3.itemno1文件 二层结构 或 三层结构 4.上传到系统路径\ItemNo 5.更名后的itemno1文件(命名:当天日期+六位随机数) 二、普通实现 1、内容介绍 含有两种结构 二层结构:zip->料号文件夹->料号文件三层结构:zip->总文件夹-&g…

基于大语言模型的减肥健身计划系统设计与实现

基于大语言模型的减肥健身计划系统设计与实现 【包含内容】 【一】项目提供完整源代码及详细注释 【二】系统设计思路与实现说明 【三】功能演示与部署指南 【技术栈】 ①&#xff1a;系统环境&#xff1a;Python 3.x Django 4.2 ②&#xff1a;开发环境&#xff1a;Web服务…

c#开发大冲锋游戏登录器

1 前言 本文主要分享登录器的简要开发过程&#xff0c;只适合小白选手&#xff0c;高手请自动避让。 此项目是复刻大冲锋计划中的子集。 &#xff08;注&#xff1a;大冲锋是迅雷代理的一款次时代多职业第一人称FPS射击游戏&#xff0c;目前已经关服嗝屁。&#xff09; 2 …

Linux[基础指令][2]

Linux[基础指令][2] cp(复制) 格式:cp [-rf] 源文件 {普通文件,目录} 拷贝 cp -r 递归拷贝目录 蓝色为目录,白色为具体文件 拷贝后面加一个不存在的文件会新建文件再拷贝 cp -ir -i是覆盖的时候询问 如果目标文件存在就会覆盖原有文件 mv(重命名/剪切) 格式:mv 源文件…

React18+ 项目搭建-从初始化、技术选型到开发部署的全流程规划

搭建一个 React 项目需要从项目初始化、技术选型到开发部署的全流程规划。以下是详细步骤和推荐的技术栈&#xff1a; 一、项目初始化 1. 选择脚手架工具 推荐工具&#xff1a; Vite&#xff08;现代轻量级工具&#xff0c;支持 React 模板&#xff0c;速度快&#xff09;&am…

人工智能学习框架完全指南(2025年更新版)

一、核心框架分类与适用场景 人工智能框架根据功能可分为深度学习框架、机器学习框架、强化学习框架和传统工具库,以下是主流工具及选型建议: 1. 深度学习框架 (1)PyTorch 核心优势:动态计算图、灵活性强,适合科研与快速原型开发,支持多模态任务(如NLP、CV) 。技术生…

MySQL 详解之事务管理

MySQL 详解之事务管理 在数据库领域,事务是一个核心概念,它确保了数据操作的可靠性和一致性。尤其是在处理涉及多个步骤且必须全部成功或全部失败的业务场景时,事务更是不可或缺。本篇文章将深入探讨 MySQL 中的事务管理,帮助您全面理解事务的工作原理及其在实际应用中的重…

SpringAI+DeepSeek大模型应用开发——5 ChatPDF

ChatPDF 知识库 RAG检索增强 由于训练大模型非常耗时&#xff0c;再加上训练语料本身比较滞后&#xff0c;所以大模型存在知识限制问题&#xff1a; 知识数据比较落后&#xff0c;往往是几个月之前的&#xff1b;不包含太过专业领域或者企业私有的数据&#xff1b; 为了解决…

SSH 互信被破坏能导致 RAC 异常关闭吗

一、 SSH 互信和 RAC 的关系 1、SSH 互信对 RAC 的作用 Oracle 11g R2 在安装 Grid Infrastructure 的时候&#xff0c;能够通过安装程序配置节 点间的 SSH 用户等效性&#xff0c;之所以要在安装之前配置 SSH 用户等效性&#xff0c;是为了能 够在安装前使用 C…

【数字图像处理】立体视觉信息提取

双目立体视觉原理 设一个为参考平面&#xff0c;一个为目标平面。增加了一个摄像头后&#xff0c;P与Q在目标面T上有分别的成像点 双目立体视觉&#xff1a;从两个不同的位置观察同一物体&#xff0c;用三角测量原理计算摄像机到该物体的距离的 方法 原理&#xff1a;三角测量…

基于springboot+vue的校园二手物品交易平台

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

【Redis】Redis基本命令(1)

KEYS 返回所有满足样式&#xff08;pattern&#xff09;的key。 KEY * 返回所有key&#xff0c;不简易使用 性能问题&#xff1a;当 Redis 存储百万级键时&#xff0c;会消耗大量 CPU 和内存资源&#xff0c;Redis 是单线程模型&#xff0c;KEYS * 执行期间会阻塞其他所有命令…

C#通用常见面试题-精心整理

以下是优化后的版本,在原有内容基础上补充了应用场景,其他结构保持不变: 上位机面试题解答(技术详解+示例) C#-IOC框架 1. 值类型和引用类型的本质区别 解释 值类型:存储在栈中,直接保存数据值(如 int, struct)。引用类型:存储在堆中,变量保存对象地址(如 class,…

K8S节点出现Evicted状态“被驱逐”

在Kubernetes集群中&#xff0c;Pod状态为“被驱逐&#xff08;evicted&#xff09;”表示Pod无法在当前节点上继续运行&#xff0c;已被集群从节点上移除。 问题分析&#xff1a; 节点磁盘空间不足 &#xff0c;使用df -h查看磁盘使用情况 可以看到根目录 / 已100%满&#x…

[密码学基础]国密算法深度解析:中国密码标准的自主化之路

国密算法深度解析&#xff1a;中国密码标准的自主化之路 国密算法&#xff08;SM系列算法&#xff09;是中国自主研发的密码技术标准体系&#xff0c;旨在打破国际密码技术垄断&#xff0c;保障国家信息安全。本文将从技术原理、应用场景和生态发展三个维度&#xff0c;全面解…