Qt扫盲-信号槽理论总结

news2024/11/24 2:30:09

信号槽理论总结

  • 一、概述
  • 二、信号槽
  • 三、信号
  • 四、槽函数
  • 五、小例子
  • 六、 信号槽的默认参数
  • 七、高级使用
  • 八、 在Qt 里使用第三方的信号槽

一、概述

信号和槽用于对象之间的通信。信号和槽机制是Qt的一个核心特性,也是与其他框架所提供的特性最大不同的部分。Qt的元对象系统就是信号和槽的基础。

在GUI编程中,当我们更改一个widget时,我们通常希望另一个widget也能收到通知。更一般地说,我们希望任何类型的对象都能够相互通信。例如,如果用户单击了关闭按钮,我们可能希望调用窗口的Close()函数。

其他开发工具包使用回调函数来实现这种通信。回调是一个指向函数的指针,所以如果你希望处理函数通知你一些事件,你可以将一个指向另一个函数(回调)的指针传递给处理函数。处理函数在适当的时候调用回调函数。虽然确实存在使用这种方法的成功框架,但回调可能不直观,而且在回调参数的类型方面可能存在一些不匹配的问题。

二、信号槽

在Qt中,我们有另一种回调技术:使用信号(signal)和槽(slot)。当特定事件发生时,会发出一个信号。Qt的窗口组件有很多预定义的信号,我们还可以子类化窗口组件来添加我们自己的信号。槽是响应特定信号而调用的函数。Qt的窗口组件有很多预定义的槽,但通常的做法是子类化窗口组件并添加自己的槽,这样就可以处理自定义的信号。

信号和槽机制是类型安全的:信号的签名必须与接收槽的签名匹配 也就是参数类型是匹配的。(实际上,槽的签名可能比接收到的信号短,因为它可以忽略额外的参数。)由于签名是兼容的,Qt提供了两种信号槽的语法

一种是:基于函数指针的语法时,编译器可以帮助我们检测类型不匹配,在编译时校验,推荐使用。
二种是:基于字符串的 SIGNAL 和SLOT 语法,将在运行时检测类型不匹配,在运行时校验,不推荐使用。

信号和槽是松散耦合的:一个发出信号的类既不知道也不关心哪个槽接收信号。Qt的信号和槽机制确保如果将信号连接到槽,槽将在正确的时间调用信号的参数。信号和槽可以接受任意数量的任何类型的参数。它们是完全类型安全的。

所有继承自QObject或其某个子类(如QWidget)的类都可以包含信号和槽。当对象以其他对象可能感兴趣的方式改变其状态时,就会发出信号。这就是对象通信所做的全部工作。它不知道也不关心是否有任何东西正在接收它发出的信号。这是真正的信息封装,并确保对象可以作为软件组件使用。

槽可用于接收信号,但它们也是普通的成员函数。就像对象不知道是否有任何东西接收到它的信号一样,槽也不知道是否有任何信号连接到它。这确保了可以用Qt创建真正独立的组件。

您可以将任意数量的信号连接到单个槽,并且可以根据需要将一个信号连接到任意数量的槽。甚至可以将一个信号直接连接到另一个信号。(这将在第一个信号发出时立即发出第二个信号。)

信号和槽一起构成了强大的组件编程机制。图例如下:
在这里插入图片描述

三、信号

当对象的内部状态以某种方式发生改变时,对象的客户端或所有者可能会感兴趣,这时对象就会发出信号。信号是公共访问函数,可以在任何地方发出,但我们建议只在定义信号及其子类的类中发出它们。

当信号发出时,与之相连的槽通常会立即执行,就像普通的函数调用一样。当发生这种情况时,信号和槽机制完全独立于任何GUI事件循环。当所有任务槽都返回后,emit语句后面的代码才会执行。使用排队连接时,情况略有不同。在这种情况下,emit关键字后面的代码将立即继续执行,而任务槽将稍后执行。

如果多个槽连接到一个信号,当信号发出时,这些槽将按照连接的顺序依次执行。

信号由moc自动生成,不能在.cpp文件中实现它们永远不能有返回类型(即使用void)

关于参数的注意事项:我们的经验表明,如果信号和槽不使用特殊类型,则它们的可重用性更强。 也即是信号函数传递的时候,参数越简单越好,简单就松耦合,依赖就更低。

如果QScrollBar::valueChanged()使用特殊类型,例如假设的QScrollBar::Range,它只能连接到专门为QScrollBar设计的槽。将不同的输入部件连接在一起是不可能的。

四、槽函数

当连接到槽的信号发出时,槽就被调用。槽是普通的c++函数,可以正常调用;它们唯一的特点是可以连接信号。
由于槽函数是普通的成员函数,因此直接调用时遵循普通的c++规则。

作为槽,它们可以被任何组件调用,无论其访问级别,通过信号槽连接。这意味着从任意类的实例发出的信号可能导致在不相关类的实例中调用私有槽。你还可以将slot定义为虚拟的,我们发现这在实践中非常有用。

与回调函数相比,信号和槽的速度要稍慢一些,因为它们提供了更大的灵活性,尽管在实际应用中差别不大。

一般来说,发送一个连接到某些槽的信号,比直接调用接收器(使用非虚函数调用)大约慢10倍。这是定位连接对象所需的开销,安全遍历所有连接(即检查后续的接收者在发射期间没有被销毁),以及以通用方式marshall任何参数所需的开销。虽然10个非虚函数调用听起来很多,但它比任何new或delete操作的开销要小得多。当你在后台执行需要new或delete的字符串、向量或列表操作时,信号和槽的开销只占整个函数调用开销的很小一部分。每当你在一个槽中进行系统调用时,情况也是如此;或者间接调用超过10个函数。

信号和槽机制的简单性和灵活性是值得的,你的用户甚至不会注意到这些开销。

请注意,在与基于qt的应用程序一起编译时,其他定义称为 signals 或者 slots 的变量的库可能会导致编译器警告和错误。因为Qt里面用了这个关键字,我们可以用 #undef 产生问题的预处理器符号。

五、小例子

一个简单的 C++ 程序

class Counter
{
  public:
      Counter() { m_value = 0; }

      int value() const { return m_value; }
      void setValue(int value);

  private:
      int m_value;
};

一个基于 QObject的程序,要使用信号或槽功能,这个类必须在声明的开头提到Q_OBJECT。它们还必须(直接或间接)派生自QObject。

#include <QObject>

class Counter : public QObject
{
      Q_OBJECT

public:
      Counter() { m_value = 0; }

      int value() const { return m_value; }

//定义槽
public slots:
      void setValue(int value)
      {
      	if (value != m_value)
      	 {
         	 m_value = value;
         	 emit valueChanged(value); 	//发射信号
          }
      }
  }

//定义信号
signals:
      void valueChanged(int newValue);

private:
      int m_value;
};

绑定信号槽

Counter a, b;

//绑定槽函数
QObject::connect(&a, &Counter::valueChanged,
                       &b, &Counter::setValue);

a.setValue(12);     // a.value() == 12, b.value() == 12
b.setValue(48);     // a.value() == 12, b.value() == 48

调用a.setValue(12)会让a发出一个valueChanged(12)信号,b会在它的setValue()插槽中接收到这个信号,即b.setValue(12)被调用。然后b发出相同的valueChanged()信号,但由于没有槽连接到b的valueChanged()信号,该信号被忽略。

注意,setValue()函数只在value != m_value时设置值并发出信号。这可以防止在循环连接(例如,将b.b valuechanged()连接到a.setValue()时)出现无限循环。
默认情况下,你建立的每一个连接都会发出一个信号;对于重复的连接,会发出两个信号。

只要调用disconnect(),就可以断开所有这些连接。如果传递Qt::UniqueConnection类型,则仅当它不是重复连接时才会建立连接。如果已经有重复的(相同对象上的相同插槽的完全相同的信号),连接将失败,connect将返回false。
这个例子说明了对象可以一起工作,而不需要知道彼此的任何信息。要实现这一点,只需要将对象连接在一起,这可以通过一些简单的QObject::connect()函数调用或使用uic的自动连接功能来实现。

六、 信号槽的默认参数

信号和槽的签名可以包含参数,并且参数也可以有默认值。如下

void destroyed(QObject* = nullptr);

当一个QObject被删除时,它会发出这个QObject::destroyed()信号。我们希望捕获这个信号,在任何可能有对已删除的QObject的悬空引用的地方,以便我们可以清理它。合适的槽函数签名可能是:

void objectDestroyed(QObject* obj = nullptr);

要将信号连接到插槽,我们使用QObject::connect()。有几种方法可以连接信号和插槽。第一种是使用函数指针:

connect(sender, &QObject::destroyed, this, &MyObject::objectDestroyed);

将QObject::connect()与函数指针一起使用有几个优点。首先,它允许编译器检查信号的参数是否与插槽的参数兼容。如果需要,编译器也可以隐式转换参数。

你也可以连接functor或c++ 11的lambda表达式:

connect(sender, &QObject::destroyed, this, [=](){ this->m_objects.remove(sender); });

在这两种情况下,我们都在connect()调用中提供this作为上下文。context对象提供了接收器应该在哪个线程中执行的信息。这很重要,因为提供上下文可以确保接收器在上下文线程中执行。
当发送者或上下文被销毁时,lambda将断开连接。你应该注意,在发出信号时,functor中使用的任何对象都是活着的。

另一种将信号连接到插槽的方法是使用QObject::connect()以及signal和slot宏。关于在SIGNAL()和SLOT()宏中是否包含参数的规则是,如果参数有默认值,传递给SIGNAL()宏的签名必须不少于传递给SLOT()宏的签名。
所有这些都可以工作:

connect(sender, SIGNAL(destroyed(QObject*)), this, SLOT(objectDestroyed(Qbject*)));
connect(sender, SIGNAL(destroyed(QObject*)), this, SLOT(objectDestroyed()));
connect(sender, SIGNAL(destroyed()), this, SLOT(objectDestroyed()));

但是这个不行:

connect(sender, SIGNAL(destroyed()), this, SLOT(objectDestroyed(QObject*)));

…因为该槽将期望一个信号不会发送的QObject。此连接将报告运行时错误,默认函数提供的话就不会出错。
注意,当使用字符串方式的这个QObject::connect()重载时,编译器不会检查signal和slot参数。

七、高级使用

如果你需要信号发送者的信息,Qt提供了QObject::sender()函数,它返回一个指向发送信号对象的指针。
通过sender 的 objectName 就可以动态的获取每一个对象是哪个,同样的 ,可以搭配着 find

Lambda表达式是一种向槽传递自定义参数的便捷方式:

connect(action, &QAction::triggered, engine,
          [=]() { engine->processAction(action->text()); });

八、 在Qt 里使用第三方的信号槽

可以将Qt与第三方信号/插槽机制一起使用。你甚至可以在同一个项目中同时使用这两种机制。只需将以下行添加到您的qmake项目(.pro)文件中。

CONFIG += no_keywords

它告诉Qt不要定义moc关键字signals、slots和emit,因为这些名称将被第三方库使用,例如Boost。然后,要继续使用带有no_keywords标志的Qt信号和插槽,只需将源代码中对Qt moc关键字的所有使用替换为相应的Qt宏Q_SIGNALS(或Q_SIGNAL)、Q_SLOTS(或Q_SLOT)和Q_EMIT。

其实就像这个

#include <QObject>

class Counter : public QObject
{
      Q_OBJECT

public:
      Counter() { m_value = 0; }

      int value() const { return m_value; }

//定义槽
public Q_SLOTS:
      void setValue(int value);    

//定义信号
Q_SIGNALS:
      void valueChanged(int newValue);

private:
      int m_value;
};

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

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

相关文章

win10跨网段文件共享

win10跨网段文件共享问题描述问题分析网络可达性防火墙权限问题操作网络拓扑示意图操作步骤问题描述 平常&#xff0c;我们经常用的是同一局域网下的网络共享&#xff0c;这在windows上很容易操作。现在&#xff0c;两台PC主机不在同一子网&#xff0c;该如何共享&#xff1f;…

【C/C++】静态顺序表详解(附完整源码)

本章内容 1.什么是线性表 2.什么是顺序表 3.静态顺序表结构的定义 4.静态顺序表的函数接口实现 5.静态顺序表的问题及思考 1.什么是线性表 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构&…

开源飞控初探(四)ArduPilot::Copter固件源码分析

2022.5.7&#xff0c;基于v4.0.5的分析。官网文档没及时更新&#xff0c;本文对当前版本源码的描述可能和官网不一样。1、无人机全栈分层结构图2、Flight Code固件部分上图中的Flight Code层&#xff0c;分为5个部分&#xff1a;车机层。一份代码通过编译配置&#xff0c;可以支…

行转列,动态列枚举分组

【问题】Hi All,Thanks for the wonderful support the community gets from this forum.I am trying to accomplish this in MongoDB. Didn’t think it could get this complicated. thought the problem was interesting to solve.I am trying to get a count of students b…

【库函数】-还在为操作字符串而烦恼,一篇带你解决这样的烦恼,这里详细介绍关于字符串操作的各个库函数,以及模拟实现

&#x1f387;作者&#xff1a;小树苗渴望变成参天大树 &#x1f389;作者宣言&#xff1a;认真写好每一篇博客 &#x1f4a5;作者gitee:link 如 果 你 喜 欢 作 者 的 文 章 &#xff0c;就 给 作 者 点 点 关 注 吧&#xff01; 字符函数和字符串函数&#x1f4a6;前言&…

python函数定义中的/和*的作用

python函数定义中的/和*的作用 特殊参数https://docs.python.org/zh-cn/3/tutorial/controlflow.html#special-parameters 函数定义中的单独出现的/和*的作用 / 和 * 是可选的。 /用来指明他前面的函数形参必须使用位置参数。 *用来指明他后面的函数形参必须为关键字参数的…

儿童感染新冠后怎么用药?什么情况需要就医?

儿童感染新冠病毒的症状&#xff0c;病程有哪些特点&#xff1f;退烧药怎么选&#xff0c;怎么吃&#xff1f;孩子有什么症状需要立即就医&#xff1f;...01儿童感染新冠病毒的症状&#xff0c;病程有哪些特点&#xff1f;王泉&#xff1a;儿童是新冠病毒的易感人群。新冠病毒感…

Unity C#热更新框架ILRuntime接入

简介 ILRuntime项目为基于C#的平台&#xff08;例如Unity&#xff09;提供了一个纯C#实现&#xff0c;快速、方便且可靠的IL运行时&#xff0c;使得能够在不支持JIT的硬件环境&#xff08;如iOS&#xff09;能够实现代码的热更新 官方教程 https://ourpalm.github.io/ILRunt…

揭开 TLS 握手的神秘面纱:它是什么以及它是如何工作的

传输层安全性 (TLS) 旨在为网络通信增加安全性。 就是浏览互联网时HTTP和HTTPS的区别。 使用 TLS 为客户端和服务器增加了额外的工作&#xff0c;但它有其好处&#xff0c;包括&#xff1a; 机密性&#xff1a;TLS 将流量包装在加密隧道中。 这使得窃听者不可能在到达目的地的…

[Swift]UIView抖动动画

以拨打视频电话时的拨打按钮抖动为例 import UIKitclass PACallPrepareAlertView: UIView {IBOutlet weak var callIV: UIImageView!private var isLeave: Bool falsedeinit {isLeave true}override func awakeFromNib() {super.awakeFromNib()shakeAction()}private func s…

套接字编程之接口

套接字编程之接口学习套接字之前你需要知道的套接字编程套接字TCP协议和UDP协议区别UDP协议的编写UDP通信两端流程具体操作接口介绍创建套接字为套接字绑定地址信息发送数据接收数据关闭套接字字节序相关接口&#x1f4cc;————本章重点————&#x1f4cc; &#x1f517;…

【Ansible】ansible 变量

ansible 变量 文章目录ansible 变量一、Ansible 变量介绍二、变量命名规则三、变量类型1.全局变量2.剧本变量3.资产变量4.Facts 变量5.注册变量6.变量优先级一、Ansible 变量介绍 在 PlayBook 一节中&#xff0c;将 PlayBook 类比成了 linux 中的shell。那么它作为一门 ansibl…

搜索引擎收录查询,是什么影响了网站被搜索引擎收录

搜索引擎收录是指程序通过辨别把网站内容进行收录&#xff0c;同时会对这些内容进行价值以及其他方面的辨别。 我们想要知道网站有没有被搜索引擎收录&#xff0c;可以借助iis7seo批量查来查询网站有没有被搜索引擎收录。打开iis7seo批量查&#xff0c;添加需要查询的网站网址&…

计算结构体,位段,联合体(共用体)的大小

目录 一、计算结构体的大小 1.1 结构体的计算并不是简单的类型加运算 1.2 内存对齐 1.2.1 内存对齐规则 1.2.2 图解内存对齐 二、计算位段的大小 2.1位段是什么 2.2 位段的内存分配 三、计算联合体的大小 3.1 什么是联合体 3.2 联合体的内存分配 END. 一、计算结构…

Consensus Algorithm -- Raft

The Raft Consensus Algorithm Go 实现&#xff1a;etcd/raft、dragonboat Rust 实现&#xff1a;TiKV C 实现 &#xff1a;nebula-graph-storage、 RethinkDB、logcabin 拜占庭将军问题 拜占庭将军问题&#xff08;Byzantine failures&#xff09;&#xff0c;是由莱斯利兰…

绝绝子!这些技巧真方便

技巧一&#xff1a;快速切换窗口 如果你还在使用桌面底部“任务栏”切换软件窗口&#xff0c;不妨试试这个“快速切换窗口”快捷键&#xff0c;仅需按住【Alt】键不放&#xff0c;并连续点按【tab】键即可实现窗口的快速切换&#xff0c;果真只有亲自上手体验才知道有多香&…

NLP中的对话机器人——问答机器人的应用场景

引言 本文是七月在线《NLP中的对话机器人》的视频笔记&#xff0c;主要介绍FAQ问答型聊天机器人的实现。 讲得还不错&#xff0c;关键是只要1分钱 FAQ问答机器人 FAQ就是一些常见问题与回答&#xff0c;比如https://letsencrypt.org/docs/faq/。 但是我们要做的不是一问一答…

原生RedHat OpenStack搭建

文章目录OpenStack的搭建方式实验软件及镜像实验环境配置NTP节点初始化配置配置本地yum仓库上传镜像文件至NTP节点搭建基于http的网络yum源搭建NTP服务配置Controller控制节点和Compute计算节点初始化配置安装RedHat OpenStack安装部署OpenStack配置OVS桥接OpenStack的搭建方式…

大赛启幕:2023数字中国创新大赛启动发布会在福州召开

2023年1月10日&#xff0c;由数字中国建设峰会组委会主办&#xff0c;福建省数字办、福建省工信厅、福建省通信管理局、福州市政府、泉州市政府、三明市政府、龙岩市政府等共同承办的2023数字中国创新大赛在福建省福州市盛大启幕。 十二届全国政协副主席王钦敏发来书面致辞。中…

Windows 10随机性死机怎么办?

在诸多的电脑问题中&#xff0c;死机算是格外恼人。尤其是当你正在编辑文档&#xff0c;正在做图&#xff0c;正在玩游戏&#xff0c;电脑突然死机&#xff0c;万一再加上没保存…… 如果你在使用Win10系统的过程中遇到随机性死机的问题&#xff0c;赶紧看看这篇文章。 小编整…