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拥有了信号与槽机制、反射(…...
跨域问题总结
文章目录 概要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场景总结
使用场景 在大型的秒杀库存扣减,app首页流量高峰,很容易将传统的关系型数据库(mysql,oracle等)给压垮。 还有很多没必要持久化的数据,比如说短信验证码,点赞数等。 分布式锁。 分布式缓存(会话共享)。 …...

2024.3.11 C++作业
1、提示并输入一个字符串,统计该字符中大写、小写字母个数、数字个数、空格个数以及其他字符个数要求使用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函数:表示返回当前电脑系统显示的日期。 公式用法:TODAY() 2、NOW函数 返回当前电脑系统显示的日期和时间 NOW函数:表示返…...

初级爬虫实战——伯克利新闻
文章目录 发现宝藏一、 目标二、简单分析网页1. 寻找所有新闻2. 分析模块、版面和文章 三、爬取新闻1. 爬取模块2. 爬取版面3. 爬取文章 四、完整代码五、效果展示 发现宝藏 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不…...
WPF资源的继承
假设这里有一个全局的资源 <Style TargetType"TextBlock"><Setter Property"FontSize" Value"40"/> </Style> 这是时候有些控件可能需要一个样式在这个基础上加一点内容的 <Style x:Key"textBlockStyle" Targ…...

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

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

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

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

【存储】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语言手撕)
🎉个人名片: 🐼作者简介:一名乐于分享在学习道路上收获的大二在校生 🙈个人主页🎉:GOTXX 🐼个人WeChat:ILXOXVJE 🐼本文由GOTXX原创,首发CSDN&…...

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

CorelDRAW Graphics Suite2024专业图形设计软件Windows/Mac最新25.0.0.230版
CorelDRAW Graphics Suite 2024是一款专业的图形设计软件,它集成了CorelDRAW Standard 2024和其他高级图形处理工具,为用户提供了全面的图形设计和编辑解决方案。 该软件拥有强大的矢量编辑功能,用户可以轻松创建和编辑矢量图形,…...
el-form表单中,对非表单内字段增加校验的方法
1、问题说明: 在开发表单的时候,可能会遇到el-form-item中绑定的值不在表单绑定的数据对象中。 此时用prop绑定该字段名是无效的,需要单独对这个字段进行校验。 在el-form-item中有一个属性 error 。用于表单域验证错误信息,设…...

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型
摘要 拍照搜题系统采用“三层管道(多模态 OCR → 语义检索 → 答案渲染)、两级检索(倒排 BM25 向量 HNSW)并以大语言模型兜底”的整体框架: 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后,分别用…...
三维GIS开发cesium智慧地铁教程(5)Cesium相机控制
一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点: 路径验证:确保相对路径.…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...

安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件
在选煤厂、化工厂、钢铁厂等过程生产型企业,其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进,需提前预防假检、错检、漏检,推动智慧生产运维系统数据的流动和现场赋能应用。同时,…...

STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...
oracle与MySQL数据库之间数据同步的技术要点
Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异,它们的数据同步要求既要保持数据的准确性和一致性,又要处理好性能问题。以下是一些主要的技术要点: 数据结构差异 数据类型差异ÿ…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...

论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...
Caliper 配置文件解析:config.yaml
Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...

优选算法第十二讲:队列 + 宽搜 优先级队列
优选算法第十二讲:队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...