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

Qt的信号槽机制

1. 什么是元对象编译器和元对象系统?

        在开始讲信号槽之前,我们先了解下Qt的框架的核心组成部分,Qt的元对象编译器(MOC)和元对象系统是Qt框架的核心组成部分,它们使得Qt拥有了信号与槽机制、反射(introspection)和属性系统等强大的特性。下面分别解释这两个概念:

1.1 元对象编译器(MOC)

        元对象编译器是Qt特有的一个预处理器,它不是标准C++的一部分。MOC会处理使用了Qt的特殊宏(如`Q_OBJECT`、`signals`、`slots`等)的C++头文件。它的主要任务是为使用了这些宏的类生成额外的C++源代码文件。这个生成的源文件包含了元信息以及信号和槽机制所需的实现代码。

MOC生成的代码包括但不限于:

- 类的元信息(类名、父类、信号和槽的名称等)。
- 用于实现信号和槽机制的函数,包括信号发射的存根和用于槽调用的代码。
- 用于动态属性系统的代码。
- 实现Qt的反射机制的代码,允许在运行时查询对象的类型信息和成员函数。

1.2 元对象系统

        元对象系统是Qt运行时环境的一部分,它使用MOC生成的代码来提供动态特性,如:

- 信号与槽:一个高级的事件订阅和通知机制,用于对象间的通信。
- 对象反射:允许在运行时查询对象的类型信息(如它的类名、它继承的基类、它拥有的信号和槽)。
- 属性系统:允许在运行时查询和修改对象的属性。
- 动态方法调用:允许在运行时调用对象的方法。

        每个通过`Q_OBJECT`宏声明的类,都能在运行时通过其元对象(`QMetaObject`)来访问这些特性。`QMetaObject`实例包含了关于其对应类的所有信息,包括信号、槽、属性等。这种机制使得Qt的对象可以在运行时进行更多动态操作。

        简而言之,元对象编译器为元对象系统生成必需的胶水代码,而元对象系统则利用这些代码,在运行时提供动态的、反射式的特性。这两个元素共同构成了Qt框架中对象间通信和动态类型管理的基础。

2. 信号槽机制

        2.1 元对象编译器(MOC):


               MOC扫描通过`Q_OBJECT`宏标记的类,并为这些类生成附加的C++代码。这个代码包括信号和槽的定义,以及类的元信息(如类名、信号/槽列表、属性等)。   假设有如下类定义:

 class MyClass : public QObject {Q_OBJECTpublic:MyClass(QObject *parent = nullptr) : QObject(parent)   signals:void mySignal(int);public slots:void mySlot(int);};

        MOC将为此类生成一个名为`moc_myclass.cpp`的源文件,其中包含了用于信号和槽的实现细节。这个文件通常包括:
   - 信号的存根(stub)函数
   - 类的元信息(元对象代码)
   - 用于调用槽的静态函数

        2.1.1 信号的存根(stub)函数是什么?       

                在Qt的信号和槽机制中,信号的存根(stub)函数是MOC生成的一段代码,它充当信号的实现。在Qt中,信号函数本身是不包含用户定义的实现的;你只需要在类的头文件中声明它们。当你在代码中发射(emit)一个信号时,实际上是调用了这个存根函数。

                存根函数的主要职责是通知Qt元对象系统有信号发生,并传递任何相关的参数。然后,元对象系统负责调用和这个信号相关联的所有槽函数。

这里是一个信号存根函数的简化示例(伪代码):

// MyClass 类中的信号声明部分
class MyClass : public QObject {Q_OBJECTpublic:...signals:void mySignal(int value);...
};// MOC 生成的存根函数
void MyClass::mySignal(int value) {// 内部生成的代码,用来激活信号QMetaObject::activate(this, &MyClass::staticMetaObject, signalIndex, &value);
}

        在上面的代码中,`mySignal` 信号的存根函数被MOC生成,并且包含了调用 `QMetaObject::activate` 函数的代码。这个 `activate` 函数是元对象系统的一部分,它负责查找所有连接到 `mySignal` 信号的槽,并依次调用它们。

        请注意,开发者不需要编写信号的存根函数;它们是由MOC自动根据类的头文件生成的。开发者只需要声明信号,并在必要的时候使用 `emit` 关键字来发射它们。例如:

emit mySignal(123);

        这行代码在运行时实际上调用的就是MOC为 `mySignal` 信号生成的存根函数。

        2.1.2 activate 函数运行原理

            在Qt中,信号和槽之间的连接是通过 `QObject::connect` 函数建立的。这个函数告诉Qt元对象系统,当特定的信号被发射时,应该调用哪个槽函数。连接可以在运行时动态建立,也就是说,在程序的执行过程中,可以根据需要将任何信号连接到任何槽上。

        下面是一个信号和槽连接的例子:

QObject::connect(sender, &SenderClass::signalName, receiver, &ReceiverClass::slotName);

        在这个例子中,我们假设 `sender` 是一个指向 `SenderClass` 实例的指针,而 `SenderClass` 中有一个名为 `signalName` 的信号。`receiver` 是指向 `ReceiverClass` 实例的指针,`ReceiverClass` 中有一个名为 `slotName` 的槽函数。

        当 `connect` 函数被调用时,Qt元对象系统会记录下信号和槽之间的连接。这个信息被用于在信号发射时,查找和调用所有连接到该信号的槽函数。

`QMetaObject::activate` 函数内部的工作原理如下:

1. 当信号发射时(即,当存根函数被调用时),`activate` 函数被执行。
2. `activate` 函数查询内部的连接列表,这个列表记录了所有连接到该信号的槽。
3. 对于每一个连接,`activate` 函数会调用相应的槽函数。如果槽函数接受参数,`activate` 会传递信号的参数给槽。

        这个过程是由Qt的元对象系统在背后自动管理的,开发者不需要编写代码来处理这些低级细节。您只需要知道如何使用 `connect` 函数来建立连接,以及如何使用 `emit` 关键字来发射信号。

        Qt的信号和槽机制非常强大,因为它允许对象之间进行松耦合的通信。槽函数不需要知道是哪个信号触发了它们,也不需要知道信号来自哪个对象。同样,对象可以发射信号而不需要知道谁将接收它们。这种机制极大地提高了代码的可重用性和可维护性。

        2.1.3 activate函数怎么知道哪个信号发送了?

                `activate`函数是Qt元对象系统的一部分,用于在运行时处理信号的发射和槽的调用。它知道是哪个信号被发射的,因为每次信号发射时,存根函数都会传递特定的信息给`activate`函数。

这个过程中涉及到几个关键的步骤和元素:

1. 信号发射(Emission):
   - 当你调用一个信号(如`emit mySignal(value);`),实际上你调用的是由MOC为该信号生成的存根函数。

2. 信号索引(Signal Index):
   - 每个信号在其类的元对象中都有一个唯一的索引值。这个索引是在编译时由MOC根据信号在类中的声明顺序计算得出的。
   - 当存根函数被调用时,它使用这个索引作为参数之一调用`QMetaObject::activate`函数。

3. 参数传递(Parameter Passing):
   - 除了信号索引,存根函数还将信号的参数作为参数传递给`activate`函数。这样,`activate`函数就可以将这些参数传递给目标槽函数。

4. 槽函数调用(Slot Invocation):
   - `QMetaObject::activate`函数使用信号索引来查找所有连接到该信号的槽,并使用信号的参数来调用它们。连接信息存储在内部的数据结构中,这些数据结构在运行时通过`QObject::connect`函数填充。

下面是`QMetaObject::activate`函数调用的伪代码流程:

// 假设这是由MOC生成的存根函数的调用
void MyClass::mySignal(int value) {// ...省略其他代码...QMetaObject::activate(this, &MyClass::staticMetaObject, signalIndex, &value);
}// activate函数的概念实现
void QMetaObject::activate(QObject *sender, QMetaObject *m, int local_signal_index, void **argv) {// 确定全局信号索引int signal_index = m->methodOffset() + local_signal_index;// 查找对应于信号的所有连接,并调用相应的槽foreach (const Connection &c, connectionsForSignal(signal_index)) {if (c.receiver) {c.slot_method(sender, argv); // argv 包含了所有传递给信号的参数}}
}

        在这个示例中,`activate`函数接收到了信号索引和信号参数。这个索引用于在信号和槽的连接表中查找应该被调用的槽函数。然后,`activate`函数根据这些信息调用所有连接的槽函数,并将信号参数传递给它们。

        这样,`activate`函数就可以知道是哪个信号被发射,并且能够将该信号路由到所有已连接的槽函数。这是Qt信号和槽机制的核心,允许对象之间进行灵活和动态的通信。

        2. 2 信号和槽的存储:


           MOC生成的元信息包含信号和槽的名称和参数类型。这个信息存储在每个对象实例的元对象中。

        2.3. 信号和槽的连接:


           连接信号和槽时使用的`QObject::connect()`函数大致上是这样的:

QObject::connect(&sender, &MyClass::mySignal, &receiver, &MyClass::mySlot);

         在内部,`QObject::connect()`会创建一个连接数据结构,它包含了关于信号和槽的信息,以及它们所属的对象。

2.4. 信号的发射:

        当你调用`emit mySignal(10);`时,MOC为`mySignal`生成的代码会被执行。这段代码会遍历所有与该信号连接的槽,并调用它们。这通常是通过调用`QMetaObject::activate()`来完成的。

  // 伪代码void MyClass::mySignal(int value) {// MOC生成的信号函数QMetaObject::activate(this, &MyClass::staticMetaObject, signalIndex, &value);}

2.5. 槽函数的调用:

        槽函数的调用是通过`QMetaObject::activate()`在运行时通过元对象系统完成的。当一个信号被激活时,Qt会查找与之相连接的所有槽,并逐个调用它们。

   // 伪代码void QMetaObject::activate(QObject *sender, QMetaObject *m, int local_signal_index, void **argv) {// 遍历连接列表for (每个连接到该信号的接收器) {// 调用槽函数slot = connection->slot;(receiver->*slot)(/* 参数转换和传递 */);}}

        如果信号和槽在不同的线程,Qt会安排一个事件(QEvent)并将其发送到接收者所在的线程的事件队列中,事件处理函数将在目标线程中调用槽函数。

        信号和槽机制的核心是QObject和QMetaObject。QObject提供了基础的通信能力,而QMetaObject负责存储类的元信息和提供动态类型检查和方法调用等能力。通过MOC生成的代码和这些类的合作,Qt可以在运行时动态地连接对象,传递参数,并且安全地调用方法。这就是Qt信号和槽机制强大灵活性的来源。

3. connect的第五个参数

        在Qt中,`QObject::connect` 方法有一个重载版本,它接受第五个参数,这个参数是一个枚举 `Qt::ConnectionType`,它指定了信号和槽之间连接的类型。`Qt::ConnectionType` 枚举的值决定了信号是直接发送到槽,还是通过事件队列来进行异步调用。这个参数是可选的,如果不提供,默认是 `Qt::AutoConnection`。

`Qt::ConnectionType` 枚举的几个可能的值包括:

- `Qt::AutoConnection` (默认): Qt会根据接收者是否位于发射者的线程中自动选择是使用 `Qt::DirectConnection` 还是 `Qt::QueuedConnection`。如果接收者和发射者在同一个线程,它会使用 `Qt::DirectConnection`,否则会使用 `Qt::QueuedConnection`。

- `Qt::DirectConnection`: 槽函数会在信号发射的那一刻立即被调用,无论接收者和发射者是否在同一个线程。这意味着槽函数是在信号发射者的线程上下文中执行的。

- `Qt::QueuedConnection`: 发射信号的事件会被放入接收者所在的线程的事件队列中。接收者的槽函数将会在接收者所在的线程的事件循环中稍后被调用。这种连接类型在跨线程通信时特别有用。

- `Qt::BlockingQueuedConnection`: 类似于 `Qt::QueuedConnection`,但是发射信号的线程会等到接收者线程中的槽函数执行完毕后再继续执行。这种类型的连接必须在不同线程之间使用,否则会导致死锁。

- `Qt::UniqueConnection`: 这个参数可以和其他类型组合使用(通过按位或操作)。它确保不会为同一信号和槽创建重复的连接。如果尝试重复连接,`connect` 函数将不会建立连接并返回 `false`。

下面是一个带有第五个参数的 `connect` 函数的例子:

QObject::connect(sender, &SenderClass::signalName,receiver, &ReceiverClass::slotName, Qt::QueuedConnection);

        在这个例子中,当 `signalName` 被发射时,`slotName` 槽函数将通过事件队列异步调用。这是跨线程工作时的一个典型用例。        

相关文章:

Qt的信号槽机制

1. 什么是元对象编译器和元对象系统? 在开始讲信号槽之前,我们先了解下Qt的框架的核心组成部分,Qt的元对象编译器(MOC)和元对象系统是Qt框架的核心组成部分,它们使得Qt拥有了信号与槽机制、反射&#xff08…...

跨域问题总结

文章目录 概要web应用整体请求流程技术名词解释跨域问题产生的原理解决方案前端代码角度前端服务器角度后端代码角度后端服务器角度 小结 概要 在不成熟的前后端开发过程中,经常遇到跨域问题; 在前后端分离的模式下的开发过程中,经常遇到跨域…...

K8s-MySQL主从集群

K8s-MySQL主从集群 引言 该案例代码均可从https://github.com/WeiXiao-Hyy/k8s_example 获取,欢迎Star! 需求 一个“主从复制”的MySQL集群有一个主节点Master有多个从节点Slave从节点需要能水平扩展所以写操作只能在主节点上执行读操作可以在所有节点…...

seo js转码工具

js转码工具作用 用于把js加密 如果不想让别人看到自己的js 代码就可以使用这个方法 js工具网址 https://tool.chinaz.com/js.aspx 效果...

【SQL】601. 体育馆的人流量(with as 临时表;id减去row_number()思路)

前述 知识点学习: with as 和临时表的使用12、关于临时表和with as子查询部分 题目描述 leetcode题目:601. 体育馆的人流量 思路 关键:如何确定id是连续的三行或更多行记录 方法一: 多次连表,筛选查询方法二&…...

java上传本地文件到服务器共享

在Windows系统中,将本地文件夹中的某个文件上传到另一台Windows服务器电脑上,前提:两台电脑网络互通,要接收文件的Windows服务器文件夹开启了共享,可以被本机用如下方式进行写入和读取: 如何配置服务器共享请自行百度查找。 所需要的maven依赖如下: <dependency>…...

Redis场景总结

使用场景 在大型的秒杀库存扣减&#xff0c;app首页流量高峰&#xff0c;很容易将传统的关系型数据库(mysql&#xff0c;oracle等&#xff09;给压垮。 还有很多没必要持久化的数据&#xff0c;比如说短信验证码&#xff0c;点赞数等。 分布式锁。 分布式缓存(会话共享)。 …...

2024.3.11 C++作业

1、提示并输入一个字符串&#xff0c;统计该字符中大写、小写字母个数、数字个数、空格个数以及其他字符个数要求使用C风格字符串完成 #include <iostream>using namespace std;int main() {char str[20];cout << "please enter the str:";gets(str);in…...

【wps】wps与office办公函数储备使用(结合了使用案例 持续更新)

【wps】wps与office办公函数储备使用(结合了使用案例 持续更新) 1、TODAY函数 返回当前电脑系统显示的日期 TODAY函数&#xff1a;表示返回当前电脑系统显示的日期。 公式用法&#xff1a;TODAY() 2、NOW函数 返回当前电脑系统显示的日期和时间 NOW函数&#xff1a;表示返…...

初级爬虫实战——伯克利新闻

文章目录 发现宝藏一、 目标二、简单分析网页1. 寻找所有新闻2. 分析模块、版面和文章 三、爬取新闻1. 爬取模块2. 爬取版面3. 爬取文章 四、完整代码五、效果展示 发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不…...

WPF资源的继承

假设这里有一个全局的资源 <Style TargetType"TextBlock"><Setter Property"FontSize" Value"40"/> </Style> 这是时候有些控件可能需要一个样式在这个基础上加一点内容的 <Style x:Key"textBlockStyle" Targ…...

linux网络通信(TCP)

TCP通信 1.socket----->第一个socket 失败-1&#xff0c;错误码 参数类型很多&#xff0c;man查看 2.connect 由于s_addr需要一个32位的数&#xff0c;使用下面函数将点分十进制字符串ip地址以网络字节序转换成32字节数值 同理端口号也有一个转换函数 我们的端口号位两个字…...

Mybatis 多个简单类型参数传入sql语句

如果只有一个简单类型的参数传入sql语句&#xff0c;我们可以在在#{}中可以随意命名&#xff0c;都可以获取到数据。但通常与接口方法中的参数同名。 但是如果有多个简单类型参数&#xff0c;如果没有特殊处理&#xff0c;那么Mybatis无法根据参数名获取数据。 正确获取方式如…...

SpringCloud OpenFeign 服务接口调用

一、前言 接下来是开展一系列的 SpringCloud 的学习之旅&#xff0c;从传统的模块之间调用&#xff0c;一步步的升级为 SpringCloud 模块之间的调用&#xff0c;此篇文章为第四篇&#xff0c;即介绍 Feign 和 OpenFeign 服务接口调用。 二、概述 2.1 Feign 是什么 Feign 是一…...

WAP网站商业计划书(附下载)

这份文件“WAP网站商业计划书.zip”详细阐述了一个由富有创造力和远见的大学生团队构思的创业项目。这个计划旨在开发并运营一个专注于无线应用协议&#xff08;WAP&#xff09;技术的网站&#xff0c;以服务于移动设备用户群体&#xff0c;提供一个易于访问、功能丰富且用户体…...

【存储】ZYNQ+NVMe小型化全国产存储解决方案

文章目录 1、背景2、基础理论3、设计方案3.1、FPGA设计方案3.1.1、NVMe控制器实现3.1.2、NVMe控制器实现 3.2 驱动软件设计方案3.2.1 读写NVMe磁盘软件驱动3.2.2 NVMe磁盘驱动设计3.2.3 标准EXT4文件系统设计 3.3 上位机控制软件设计方案 4、测试结果4.1 硬件测试平台说明4.2 测…...

数据结构之栈详解(C语言手撕)

&#x1f389;个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名乐于分享在学习道路上收获的大二在校生 &#x1f648;个人主页&#x1f389;&#xff1a;GOTXX &#x1f43c;个人WeChat&#xff1a;ILXOXVJE &#x1f43c;本文由GOTXX原创&#xff0c;首发CSDN&…...

Docker学习——Dock镜像

什么是Docker镜像 Docker 镜像类似于虚拟机镜像&#xff0c;可以将它理解为一个只读的模板。 一个镜像可以包含一个基本的操作系统环境&#xff0c;里面仅安装了 Apache 应用程序&#xff08;或 用户需要的其他软件&#xff09; 可以把它称为一个 Apache 镜像。镜像是创建 Do…...

CorelDRAW Graphics Suite2024专业图形设计软件Windows/Mac最新25.0.0.230版

CorelDRAW Graphics Suite 2024是一款专业的图形设计软件&#xff0c;它集成了CorelDRAW Standard 2024和其他高级图形处理工具&#xff0c;为用户提供了全面的图形设计和编辑解决方案。 该软件拥有强大的矢量编辑功能&#xff0c;用户可以轻松创建和编辑矢量图形&#xff0c;…...

el-form表单中,对非表单内字段增加校验的方法

1、问题说明&#xff1a; 在开发表单的时候&#xff0c;可能会遇到el-form-item中绑定的值不在表单绑定的数据对象中。 此时用prop绑定该字段名是无效的&#xff0c;需要单独对这个字段进行校验。 在el-form-item中有一个属性 error 。用于表单域验证错误信息&#xff0c;设…...

HTML 语义化

目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案&#xff1a; 语义化标签&#xff1a; <header>&#xff1a;页头<nav>&#xff1a;导航<main>&#xff1a;主要内容<article>&#x…...

Cursor实现用excel数据填充word模版的方法

cursor主页&#xff1a;https://www.cursor.com/ 任务目标&#xff1a;把excel格式的数据里的单元格&#xff0c;按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例&#xff0c;…...

抖音增长新引擎:品融电商,一站式全案代运营领跑者

抖音增长新引擎&#xff1a;品融电商&#xff0c;一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中&#xff0c;品牌如何破浪前行&#xff1f;自建团队成本高、效果难控&#xff1b;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...

高危文件识别的常用算法:原理、应用与企业场景

高危文件识别的常用算法&#xff1a;原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件&#xff0c;如包含恶意代码、敏感数据或欺诈内容的文档&#xff0c;在企业协同办公环境中&#xff08;如Teams、Google Workspace&#xff09;尤为重要。结合大模型技术&…...

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制

在数字化浪潮席卷全球的今天&#xff0c;数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具&#xff0c;在大规模数据获取中发挥着关键作用。然而&#xff0c;传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时&#xff0c;常出现数据质…...

MySQL 知识小结(一)

一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库&#xff0c;分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷&#xff0c;但是文件存放起来数据比较冗余&#xff0c;用二进制能够更好管理咱们M…...

C++_哈希表

本篇文章是对C学习的哈希表部分的学习分享 相信一定会对你有所帮助~ 那咱们废话不多说&#xff0c;直接开始吧&#xff01; 一、基础概念 1. 哈希核心思想&#xff1a; 哈希函数的作用&#xff1a;通过此函数建立一个Key与存储位置之间的映射关系。理想目标&#xff1a;实现…...

Spring Boot + MyBatis 集成支付宝支付流程

Spring Boot MyBatis 集成支付宝支付流程 核心流程 商户系统生成订单调用支付宝创建预支付订单用户跳转支付宝完成支付支付宝异步通知支付结果商户处理支付结果更新订单状态支付宝同步跳转回商户页面 代码实现示例&#xff08;电脑网站支付&#xff09; 1. 添加依赖 <!…...

MCP和Function Calling

MCP MCP&#xff08;Model Context Protocol&#xff0c;模型上下文协议&#xff09; &#xff0c;2024年11月底&#xff0c;由 Anthropic 推出的一种开放标准&#xff0c;旨在统一大模型与外部数据源和工具之间的通信协议。MCP 的主要目的在于解决当前 AI 模型因数据孤岛限制而…...

codeforces C. Cool Partition

目录 题目简述&#xff1a; 思路&#xff1a; 总代码&#xff1a; https://codeforces.com/contest/2117/problem/C 题目简述&#xff1a; 给定一个整数数组&#xff0c;现要求你对数组进行分割&#xff0c;但需满足条件&#xff1a;前一个子数组中的值必须在后一个子数组中…...