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 。用于表单域验证错误信息,设…...
【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器
一.自适应梯度算法Adagrad概述 Adagrad(Adaptive Gradient Algorithm)是一种自适应学习率的优化算法,由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率,适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...
大型活动交通拥堵治理的视觉算法应用
大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动(如演唱会、马拉松赛事、高考中考等)期间,城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例,暖城商圈曾因观众集中离场导致周边…...
PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建
制造业采购供应链管理是企业运营的核心环节,供应链协同管理在供应链上下游企业之间建立紧密的合作关系,通过信息共享、资源整合、业务协同等方式,实现供应链的全面管理和优化,提高供应链的效率和透明度,降低供应链的成…...
【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)
服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...
C++ 基础特性深度解析
目录 引言 一、命名空间(namespace) C 中的命名空间 与 C 语言的对比 二、缺省参数 C 中的缺省参数 与 C 语言的对比 三、引用(reference) C 中的引用 与 C 语言的对比 四、inline(内联函数…...
EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...
AGain DB和倍数增益的关系
我在设置一款索尼CMOS芯片时,Again增益0db变化为6DB,画面的变化只有2倍DN的增益,比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析: 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...
AI+无人机如何守护濒危物种?YOLOv8实现95%精准识别
【导读】 野生动物监测在理解和保护生态系统中发挥着至关重要的作用。然而,传统的野生动物观察方法往往耗时耗力、成本高昂且范围有限。无人机的出现为野生动物监测提供了有前景的替代方案,能够实现大范围覆盖并远程采集数据。尽管具备这些优势…...
【Linux】Linux 系统默认的目录及作用说明
博主介绍:✌全网粉丝23W,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...
