【Qt】Qt中的信号槽

一、信号和槽概述

       信号槽是Qt矿建引以为豪的机制之一。

       所谓信号槽,实际上就是观察者模式(发布——订阅模式)。当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号。这种发出的信号是没有目的的,类似于广播。如果有对象对这个信号感兴趣,它就会使用连接函数(connect),意思是将想要处理的信号和自己的一个函数(称为槽)绑定来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自定被回调。

       这就类似于观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。

1.1 信号的本质

       信号是由于用户对窗口或控件进行了某些操作,导致窗口或控件产生了某个特定事件,这时候Qt对应的窗口类会发出某个信号,对用以此户的挑选做出反应。

根据上面的论述,我们可以得出一个结论——信号的本质就是事件,例如:

  • 按钮单击、双击
  • 窗口刷新
  • 鼠标移动、鼠标按下、鼠标释放
  • 键盘输入

在Qt中,信号是通过什么形式呈现给使用者的呢??

  • 我们对哪个窗口进行操作,哪个窗口就可以捕捉到这些被触发的事件
  • 对于使用者来说,触发一个事件,我们就可以得到Qt框架给我们发出的某个特定信号
  • 信号的呈现形式就是函数,也就是说某个事件产生了,Qt框架就会调用某个对应的信号函数,通知使用者。

       在Qt中信号的发出者是某个实例化的类对象,对象内部可以进行相关事件的检测。

1.2 槽的本质

       在Qt中,槽函数是一类特殊功能的函数,在编码过程中,也可以作为类的普通成员函数来使用。之所以称之为槽函数,是因为它们还有一个职责就是对Qt框架中产生的信号进行处理。

举个例子:

        有一天,我们在和女朋友一起逛街,突然,女朋友说:“我肚子饿了!”,于是我们带着她们去吃饭。(这就相当于女朋友发出一个信号,我收到信号并将其处理掉)

实例对象角色描述
女朋友信号发出者信号携带的信息:我饿了
信号接收者处理女朋友发射的信号:带她去吃饭

       在Qt中槽函数的所有者也是某个类的实例对象。

1.3 信号和槽的关系

在Qt中,信号和槽函数都是独立的个体,本身没有任何联系,但是由于某种特性需求,我们可以将二者连接在一起,好比牛郎和织女想要见面,必须要有喜鹊为他们搭桥。

在Qt中,我们需要使用QObject类中的connect函数进行两者的关联。

连接信号和槽的connect()函数原型如下,其中PointerToMenberFunction是一个指向函数地址的指针:

QMetaObject::Connection QObject::connect(const QObject* sender, PointerToMemberFunction signal,const QObject* receiver, PointerToMenberFunction 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()函数进行信号槽的连接的注意事项:

  • connect函数相对于做了信号处理动作的注册
  • 调用connect函数的sender对象的信号没有产生,因此receiver对象的method也不会被调用
  • method槽函数本质是一个回调函数,调用的时机是信号产生之后,调用的是Qt框架来执行的
  • connect连接中的sender和receiver两个指针必须被实例化,否则connect不会成功 

二、标准信号槽的使用

2.1 标准信号/槽

       在Qt中提供了很多标准类中都可以对用户触发的某种特定事件进行检测,因此当用户做了这些操作之后,事件被触发类的内部就会产生对应的信号,这些信号都是Qt类内部自带的,因此称之为标准信号。

同样地,在Qt的很多类的内部中为我提供了很多功能函数,并且这些函数也可以作为触发信号的处理动作,有这类特性的函数在Qt中称之为标准槽函数。

系统自带的信号和槽通常如何查找?这个就需要利用帮助文档,比如在帮助文档中查询按钮的点击信号,那么需要在帮助文档中输入QPushButton

2.2 使用

举个例子:

功能实现:点击窗口上的按钮,关闭窗口

功能分析:按钮:信号发出者                     QPushButton 类型

                  窗口:信号的接收者和处理处   QWidget 类型

// 需要使用的标准信号槽函数
// 单击按钮发出的信号
void QAbstractButton::clicked(bool check = false);
// 关闭窗口的槽函数
bool QWidget::close();// 点击按钮关闭窗口
connect(ui->closewindow, &QPushButton::clicked, this, &MainWindow::close);

       connect()操作一般写在窗口的构造函数汇总,相当于在事件产生之前,在Qt框架中先进行注册,这样在程序运行过程中假设产生了按钮的点击事件,框架就会调用信号接收者对象对应的槽函数,如果信号不产生,槽函数也就一直不会被调用。

三、自定义信号槽的使用

       Qt框架提供的信号槽在某种特定的场景下是无法满足我们的项目的要求,因此我们还设计自己需要的信号和槽,同样还是使用connect()对自定义的信号槽进行连接。

如果想要在Qt类中自定义信号槽,需要满足一些条件,并且也要注意一些事情:

  • 要编写新的类并且让其继承Qt的某些标准类
  • 这个新的子类必须从QObject类或者是QObject子类进行派生
  • 在定义类的头文件中假如Q_OBJECT宏
// 在头文件派生类的时候,首先像下面那样引入Q_OBJECT宏class MyMainWindow : public QWidget
{Q_OBJECT......
}

3.1 自定义信号

       在Qt中信号的本质是事件,但是在框架中也是以函数的形式存在的,只不过信号对应的函数只有声明,没有定义。如果Qt中的标准信号不能满足我们的需求,可以在程序中进行信号的自定义,当自定义信号对应的时间产生之后,认为将这个信号发射出去即可(其实就是调用一下这个信号函数)

自定义信号的要求和注意事项:

  • 信号是类的成员函数
  • 返回值必须是void类型
  • 信号的名字可以根据实际情况进行指定
  • 参数可以随意指定,信号也支持重载
  • 信号需要使用signals关键字进行声明,使用方法类似于public等关键字
  • 信号函数只需要声明,不需要定义(没有函数实现)
  • 在程序中发射自定义信号:发送信号的本质就是调用信号函数。习惯性在信号函数前加关键字:emit,但是可以省略不写,emit只是显示的声明一下信号要被发射了,没有特殊含义,底层中 emit == #define emit
class Test : public Qobject
{Q_OBJECT
signals:void testsignals();// 参数的作用是数据传递,谁调用信号函数谁就指定实参// 实参最终会被传递给槽函数void testsignals(int a);
}'

3.2 自定义槽

       槽函数就是信号的处理动作,在Qt中槽函数可以作为普通的成员函数来使用。如果标准槽函数提供的功能满足不了需求,可以自己定义槽函数进行某些特殊功能的实现。自定义槽函数和自定义的普通函数写法是一样的。

自定义槽的要求和注意事项:

  • 返回值必须是void类型
  • 槽也是函数,因此也支持重载
  • 槽函数需要指定多少个参数,需要看连接信号的参数个数(连接信号的参数个数大于槽函数的参数个数)
  • 槽函数的参数是用来接收信号传递的数据,信号传递的数据就是信号的参数
  • Qt中的槽函数类型是多样的,Qt中的槽函数可以是类的成员函数,全局函数,静态函数,Lambda表达式(匿名函数)
  • 槽函数可以使用关键字进行声明:slots(Qt5中slots可以省略不写)

       再来通过一个例子来自定义一个信号槽:还是上面的场景:女朋友说:“我肚子饿了!”,于是我带她去吃饭

class GirlFriend : public Object
{Q_OBJECT
public:explicit GirlFriend(QObject* parent = nullptr);signals:void hungry();void hungry(QString msg);
};class Me : public Object
{Q_OBJECT
public:explicit Me(QObject* parent = nullptr);public slots:// 槽函数void eatMeal();void eatMeal(QString msg);
};

四、信号槽的扩展

4.1 信号槽使用扩展

一个信号可以连接多个槽函数,发送一个信号有多个处理动作:

  • 需要写多个connect()连接
  • 槽函数的执行顺序是随机的,和connect函数的调用顺序没有关系
  • 信号的接收者可以是一个对象,也可以是多个对象

一个槽函数可以连接多个信号,多个不同的信号,处理动作是相同的

  • 需要写多个connect()连接

信号可以连接信号

  • 信号接收者可以不处理接收的信号,而是继续发射新的信号,这就相当于传递了数据,并没有随数据进行处理
  • connect(const QObject* sender, &QObject::singal,const QObject* receiver, &QObject::signal_new);

信号槽是可以通过函数断开的

  • disconnect(const QObject* sender, &QObject::signal,const QObject* receiver, &QObject::method);

4.2 信号槽的连接方式

4.2.1 Qt5的连接方式

// 语法
QMetaObject::Connection QObject::connect(const QObject* sender, PointerToMenmberFunction signal,const QObject* receiver, PointerToMemberFunction method,Qt::ConnectionType = Qt::AutoConnection);// 信号和槽函数也就是第2,4个参数传递的是地址,编译器在编译过程中会对数据的正确性进行检查
connect(const QObject* sender, &QObject::signal, const QObject* receiver, &QObject::method);

4.2.2 Qt4的连接方式

       这种旧的信号槽连接方式在Qt5中是支持的,但是不推荐使用,因为这种方式在进行信号槽的连接的时候,信号槽函数通过宏SIGNAL和SLOT转换为字符串类型。

       因为信号槽函数的 转换是通过宏来进行转换的,因此传递到宏函数内部的数据不会被进行检测,如果使用者传错了数据,编译器不会报错,但实际上信号槽的连接已经不对了,只有在程序运行起来之后才能发现问题,而且问题不容易被定位。

4.2.3 Qt5处理方式错误的原因

       上边的写法之所以错误是因为这个类中信号槽都是重载过的, 信号和槽都是通过函数名去关联函数的地址, 但是这个同名函数对应两块不同的地址, 一个带参, 一个不带参, 因此编译器就不知道去关联哪块地址了, 所以如果我们在这种时候通过以上方式进行信号槽连接, 编译器就会报错。

4.2.4 总结:

  • Qt4的信号槽连接方式因为使用了宏函数, 宏函数对用户传递的信号槽不会做错误检测, 容易出bug
  • Qt5的信号槽连接方式, 传递的是信号槽函数的地址, 编译器会做错误检测, 减少了bug的产生
  • 当信号槽函数被重载之后, Qt4的信号槽连接方式不受影响
  • 当信号槽函数被重载之后, Qt5中需要给被重载的信号或者槽定义函数指针

五、Lambda表达式

5.1 语法格式

Lambda表达式就是一个匿名函数,语法格式如下:

[capture](params) opt-> ret{ body; };--capture:捕获列表--params:参数列表--opt:函数选项--ret:返回值类型--body:函数体

5.1.1 捕获列表:捕获一定范围内的变量

  • [ ]:不捕捉任何变量
  • [&]:捕获外部作用域中的所有变量,并作为引用在函数体内使用(按引用捕获)
  • [=]:捕获外部作用域中的所有变量 ,并作为副本在函数体内使用(按值捕获),拷贝的副本在匿名函数体内是只读的
  • [= , &foo]:按值捕获外部作用域中的所有变量,并按照引用捕获外部变量foo
  • [bar]:按值捕获bar变量,同时不捕获其他变量
  • [&bar]:按引用捕获bar变量,同时不捕获其他变量
  • [this]:捕获当前类中的this指针,让lambda表达式拥有和当前成员函数同样的访问权限,如果已经使用了&或者=,默认添加次选项

5.1.2 参数列表:和普通函数的参数列表一样

5.1.3 opt选项 -> 可以省略

  • mutable:可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)
  • execption:指定函数抛出的异常,如抛出整数类型的异常,可以使用throw()

5.1.4 返回值类型

       表示函数返回值的类型,当返回值为void,或者函数体中只有溢出return的地方(此时编译器可以自动推断出返回值的类型)时,这部分可以省略。

5.1.5 函数体

       函数的实现,这部分不能省略,但是函数体可以为空。

5.2 定义和调用

       因为Lambda表达式是一个匿名函数,因此是没有函数声明的,直接在程序中进行代码的定义即可,但是如果只定义匿名函数在程序执行过程中是不糊被调用的。

// 匿名函数的定义,程序执行这个匿名函数是不会被调用的
[]() {qDebug() << "Hello, I am lambda...";
};// 匿名函数的定义 + 调用
int ret = [](int a) -> int 
{return a + 1;
}(100); // 100是传递给匿名函数的参数

       在Lambda表达式中的捕获列表中也就是[ ] 中添加不同的关键字,就可以在函数体中使用外部变量了。

// 在匿名函数外部定义变量
int a = 100, b = 200, c = 300;// 调用匿名函数
// 使用引用的方式传递数据
[&](){qDebug() << "a + 1:" << a++ << ", b + c = " << b + c;
}();// 值拷贝的方式使用外部数据
[=](int m, int n) mutable{qDebug() << "Hello, 我是一个lambda表达式...";qDebug() << "使用拷贝的方式传递数据:";// 拷贝的外部数据在函数体内部是只读的,如果不添加mutable关键字是不能修改这些只读数据的值// 添加mutable允许修改的数据是拷贝到函数内部的副本,对外部数据没有影响qDebug() << "a + 1:" << a++ << ", b + c = " << b + c;qDebug() << "m + 1:" << ++m << ", n: " << n;
}(1, 2);

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

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

相关文章

LAMP集群分布式实验报告

前景&#xff1a; 1.技术成熟度和稳定性&#xff1a; LAMP架构&#xff08;Linux、Apache、MySQL、PHP&#xff09;自1998年提出以来&#xff0c;经过长时间的发展和完善&#xff0c;已经成为非常成熟和稳定的Web开发平台。其中&#xff0c;Linux操作系统因其高度的灵活性和稳…

Linux 主机一键安全整改策略

为防止linux主机被恶意攻击&#xff0c;和受到攻击后能更快定位到源头&#xff0c;需要对linux主机做一些参数配置。 比如禁用root的远程登录、用户多次密码验证失败后被锁、禁止系统账号交互式登录等等。 下面是linux主机安全整改的一些简单介绍&#xff0c;最后会通过脚本一…

【XR806开发板试用】基础篇,从零开始搭建一个LCD彩屏时钟(ST7735S驱动)

本文从搭建环境开始&#xff0c;step by step教大家使用XR806实现驱动SPI屏幕&#xff08;ST7735S驱动&#xff09;&#xff0c;并连接WiFi实现ntp对时&#xff0c;最终实现把时间显示到屏幕上。 #1. 搭建开发环境 1. 安装编译环境所需的依赖包 基于ubuntu 20.04&#xff0c;按…

UI自动化测试最佳设计模式POM

当使用Selenium进行UI自动化测试时&#xff0c;Page Object Model&#xff08;POM&#xff09;是一种最佳实践的设计模式。POM的核心思想是通过将页面封装成对象&#xff0c;使得测试代码更加清晰、可维护和可重用。 POM的主要组成部分包括页面对象类、元素定位方式和操作方法…

MybatisPlus @TableField之SqlCondition源码解析

应用对象&#xff1a;模型&#xff0c;作用&#xff1a;调用IService接口&#xff0c;使用查询包装器实现灵活的条件查询。 TableField(conditionSqlCondition.LIKE) 注解使用逻辑 com.baomidou.mybatisplus.extension.service.IService.page(分页对象&#xff0c;查询包装器…

“区块链技术在网络安全领域的革新应用与挑战“

区块链技术&#xff0c;以其去中心化、不可篡改和透明度高的特性&#xff0c;在网络安全领域展现出了巨大的革新潜力&#xff0c;同时也带来了一系列新的挑战。以下是区块链技术在网络安全领域的应用及其面临的主要挑战的深入分析。 革新应用 1. 数据保护与隐私增强&#xff…

PHP身份证识别接口、线上平台如何实现身份证实名认证功能?

线上平台实现身份证实名认证的功能&#xff0c;需要结合身份证识别接口来完成。首先&#xff0c;用户通过上传身份证图片或者拍照的方式实现证件信息的提取&#xff0c;身份证实名认证接口通过对提取到的证件信息进行核验&#xff0c;以此来实现线上用户身份的实名认证&#xf…

LabVIEW车轮动平衡检测系统

LabVIEW车轮动平衡检测系统 随着汽车行业的快速发展&#xff0c;车轮动平衡问题对乘坐舒适性、操控稳定性及安全性的影响日益凸显&#xff0c;成为了提高汽车性能的一个关键环节。传统的检测系统因精度低、成本高、操作复杂等问题&#xff0c;难以满足现代汽车行业的需求。开发…

行车安全:UWB模块的智能化在车辆安全系统中的作用

随着交通车辆数量的不断增加和道路交通拥堵的加剧&#xff0c;车辆安全问题日益引起人们的关注。在这种背景下&#xff0c;超宽带&#xff08;UWB&#xff09;技术作为一种新兴的定位技术&#xff0c;正逐渐应用于车辆安全系统中&#xff0c;为提高车辆行车安全性提供了新的解决…

Unity真机打包地形不显示

Using terrain.drawInstanced in a build - Shader unsupported: Hidden/Nature/Terrain/Utilities Terrain missing on build 大概是两种思路 第一是材质shader丢失&#xff0c;把Terrain相关的shader都添加到ProjectSetting里 第二是地形Inspector面板&#xff0c;把Draw …

Django配置

后端开发&#xff1a; python 解释器、 pycharm 社区版、 navicate 、 mysql(phpstudy) 前段开发&#xff1a; vs code 、 google 浏览器 django 项目配置 配置项目启动方式 创建模型 创建一个应用 在应用中创建模型类 根据模型类生成数据表 创建应用 创建模型类 …

智能除螨—wtn6040-8s语音芯片方案引领除螨仪新时代

语音螨仪开发背景&#xff1a; 随着物联网技术的快速发展&#xff0c;除螨仪作为家庭清洁的重要工具&#xff0c;其智能化、人性化的设计成为提升市场竞争力的关键。置入语音芯片的除螨仪&#xff0c;通过开机提示、工作状态反馈、操作指引、故障提醒等内容。用户可以更加直观…

邦注科技三机一体除湿干燥机在工业中的应用

三机一体除湿干燥机在工业中的应用广泛且重要&#xff0c;其结合了传统除湿机、冷凝器和加热器的功能&#xff0c;具有节能、环保、方便等特点。以下是关于三机一体除湿干燥机在工业中应用的详细解析&#xff1a; 一、应用领域 电子制造行业&#xff1a;在半导体、集成电路和…

安卓手机APP开发__超宽带(UWB)通信

安卓手机APP开发__超宽带(UWB)通信 目录 概述 控制方/发起方与控制方/响应方 参数范围 后台测距 STS 配置 步骤 使用限制 代码示例 示例应用 UWB 范围 RxJava3 支持 生态系统支持 支持 UWB 的移动设备 第三方 SDK 概述 注意 &#xff1a;UWB 目前仅支持 Jetpac…

JavaScrpit基础入门

JavaScript 是一种用于网页开发的脚本语言&#xff0c;它主要用于增强网页的交互性和动态性。HTML 用于定义网页的结构&#xff0c;包括文本、图像、链接等内容&#xff0c;而 CSS 用于定义网页的样式&#xff0c;包括颜色、布局、字体等。JavaScript 通过与 HTML 和 CSS 结合使…

大模型中的Tokenizer

在使用GPT 、BERT模型输入词语常常会先进行tokenize 。 tokenize的目标是把输入的文本流&#xff0c;切分成一个个子串&#xff0c;每个子串相对有完整的语义&#xff0c;便于学习embedding表达和后续模型的使用。 一、粒度 三种粒度&#xff1a;word/subword/char word词&a…

【云原生】Kubernetes----POD调度策略

目录 引言 一、Pod调度策略 &#xff08;一&#xff09;基本概述 &#xff08;二&#xff09;调度原则 &#xff08;三&#xff09;Predicate常见算法 &#xff08;四&#xff09;优先级排序 &#xff08;五&#xff09;调度过程 1.过滤阶段 2.优先级排序 3.选择最优…

raspberry pi/orienge pi等arm架构硬件打包ros humble docker视觉及机器人开发镜像

raspberry pi/orienge pi等arm架构硬件打包ros humble docker开发镜像 文章目录 前言准备工作拉取镜像编写Dockerfile编译docker镜像启动镜像docker-compose文件编写总结前言 这篇文章主要用于定制化打包需要的docker镜像,由于ros1提供的镜像源是国外的很多软件无法更新,所以…

大模型部署推理应用技术浅析

大模型完成预训练后不是就万事大吉了&#xff0c;离推理应用还有很大距离&#xff0c;需要经过微调、部署等一系列工程化工作。尤其是在2B的行业大模型应用中&#xff0c;为解决大模型的幻觉、时效性和推理成本问题&#xff0c;需要建立单一模型之上的体系。模型部署中的技术大…

使用uniapp开发支付宝小程序时引入卡包插件

1、官网文档 https://opendocs.alipay.com/pre-open/03k88a?pathHash30ce86a9 2、代码开发 //manifest.json 配置文件添加卡包插件 "mp-alipay": {"usingComponents": true,"appid": "xxxxxxxxxxxxxxxxx","plugins": {&…