Qt信号槽原理

news/2024/4/27 18:15:41/文章来源:https://blog.csdn.net/wjfdsklfdkfksd/article/details/130373043

Qt之信号槽原理

一.概述

所谓信号槽,实际就是观察者模式。当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,将想要处理的信号和自己的一个函数(称为槽(slot))绑定来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调。这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。(这里提一句,Qt 的信号槽使用了额外的处理来实现 ,并不是 GoF 经典的观察者模式的实现方式。)

信号和槽是Qt特有的信息传输机制,是Qt设计程序的重要基础,它可以让互不干扰的对象建立一种联系 Qt信号槽有如下优点:

1.类型安全。需要关联的信号槽的签名必须是等同的。即信号的参数类型和参数个数同接受该信号的槽的参数类型和参数个数相同。若信号和槽签名不一致,编译器会报错。

2.松散耦合。信号和槽机制减弱了Qt对象的耦合度。激发信号的Qt对象无需知道是那个对象的那个信号槽接收它发出的信号,它只需在适当的时间发送适当的信号即可,而不需要关心是否被接受和那个对象接受了。Qt就保证了适当的槽得到了调用,即使关联的对象在运行时被删除。程序也不会奔溃。

3.灵活性。一个信号可以关联多个槽,或多个信号关联同一个槽

二.信号槽的实现

众所周知,C++语言的编译过程为预处理->编译->汇编->链接。

a.驱动程序首先运行C预处理器(cpp),将源程序翻译成一个ASCII码的中间文件main.i,
​
b.驱动程序运行C编译器,将main.i翻译成一个ASCII汇编文件main.s
​
c.驱动程序运行汇编器(as),将main.s翻译成一个可重定位目标文件main.o
​
d.运行链接器,将main.o及其一些必要的系统目标文件组合在一起,创建一个可执行的目标文件

但是在qt中,首先会有一个Moc预处理器对源代码进行处理,经过Moc预处理器处理后的代码才是标准的C++代码,此后就可以执行正常的C++编译流程了。正是因为Moc预处理器,Qt才实现了信号槽的功能,下面我们通过用纯C++代码实现一个简洁的信号槽功能来对信号槽深入了解。

使用C++语言模拟信号槽实现:

首先看一下类图:

 

QObject有一个静态元对象QMetaObject,静态元对象存放着信号槽名称的字符信息以及一个根据信号发送者和信号index调用对应槽的函数;

QObject有一个容器connections,此容器是一个map,key是信号index,value是一个Connection,维护者信号槽的对应关系;

QObject有一个静态方法connect,此方法将信号的index作为key,创建一个value插入到对象维护的连接列表容器中;

  • 程序运行时,connect借助两个字符串,即可将信号与槽的关联建立起来,那么,它是如果做到的呢?C++的经验可以告诉我们:

    • 类中应该保存有信号和槽的字符串信息

    • 字符串和信号槽函数要关联

引入元对象系统

定义信号和槽,为了和普通成员进行区分以使得预处理器可以提取信息,定义几个关键字。

#define slots  
#define signals protected  
#define emit  
  • 通过预处理器,将信息提取取来,放置到一个单独的文件中(比如moc_QObject.cpp):

  • 规则很简单,将信号和槽的名字提取出来,放到字符串中。可以有多个信号或槽,按顺序"sig1/nsig2/n"

    static const char sig_names[] = "sig1/nsing2/n";
    static const char slts_names[] = "slot1/nslot2/n";

利用这些信号槽的信息,建立连接;

定义一个结构体,存放信息

struct QMetaObject
{const char * sig_names;const char * slts_names;
};

然后将它作为QObject的一个静态对象,这个就是Qt中的元对象。

class QObject
{static QMetaObject staticMetaObject;...

利用预处理器生成的moc_Object.cpp:

#include "object.h"
​
static const char sig_names[] = "sig1/n";
static const char slts_names[] = "slot1/n";
QMetaObject QObject::staticMetaObject = {sig_names, slts_names};

建立信号槽连接

利用moc预处理器保存的信息,通过 connect 将信号和槽的对应关系保存到一个 mutlimap中。

struct Connection
{Object * receiver;int method;
};
​
class QObject
{
public:
...static void connect(QObject*, const char*, QObject*, const char*);
...
private:std::multimap<int, Connection> connections;

connect函数:

void QObject::connect(QObject* sender, const char* sig, QObject* receiver, const char* slt)
{int sig_idx = find_string(sender->meta.sig_names, sig);int slt_idx = find_string(receiver->meta.slts_names, slt);if (sig_idx == -1 || slt_idx == -1) {perror("signal or slot not found!");} else {Connection c = {receiver, slt_idx};sender->connections.insert(std::pair<int, Connection>(sig_idx, c));}
}

首先从元对象信息中查找信号和槽的名字是否存在,如果存在,则将信号的索引和接收者的信息存入信号发送者的一个map中。

信号的激活:

在Qt中我们都是使用emit来激活一个信号,这是emit的本来面目。

#define emit

在Qt中我们必须要在类里增加一个Q_OBJECT的宏,发送信号使用emit,定义槽和信号,使用signals,public slots等。其实这些都是一些宏替换,在qobjects.h文件里可以看到这些宏的本来面目。

我们这里使用emit来激活信号。我们在定义信号的时候只写了信号的声明,信号的实现并未给出。而槽函数的实现是开发者给出的。其实信号的实现是Moc编译器帮助我们实现的。

void QObject::sig1()
{QMetaObject::active(this, 0);
}

信号的调用工作由QMetaObject类来完成

class QObject;
struct QMetaObject
{const char * sig_names;const char * slts_names;static void active(QObject * sender, int idx);};

这个函数该怎么写呢:思路很简单

从前面的保存连接的map中,找出与该信号关联的对象和槽 调用该对象这个槽

typedef std::multimap<int, Connection> ConnectionMap;
typedef std::multimap<int, Connection>::iterator ConnectionMapIt;void QMetaObject::active(QObject* sender, int idx)
{ConnectionMapIt it;std::pair<ConnectionMapIt, ConnectionMapIt> ret;ret = sender->connections.equal_range(idx);for (it=ret.first; it!=ret.second; ++it) {Connection c = (*it).second;//c.receiver->metacall(c.method);}
}

槽的调用:

这个最后一个关键问题了,槽函数如何根据一个索引值进行调用。

  • 直接调用槽函数我们都知道了,就一个普通函数

  • 可现在通过索引调用了,那么我们必须定义一个接口函数

class QObject
{void metacall(int idx);
...

该函数如何实现呢?这个又回到我们的元对象预处理过程中了,因为在预处理的过程,我们能将槽的索引和槽的调用关联起来。

所以,在预处理生成的文件(moc_QObject.cpp)中,我们很容易生成其定义:

void QObject::qt_static_metacall(int idx)
{switch (idx){case 0:{slot1();break;}case 1:{slot2();break;}};
}

总结:moc通过元对象系统保存了信号和槽的字符信息,然后为每一个信号和槽分配编号。当我们调用connect函数时,把信号槽的对应关系保存在对象的一个map容器中。当发送信号时(也就是调用信号函数时)通过刚才保存在map容器中的信号槽对应关系找到对应的接收对象和槽函数。

完整代码:

#include <string.h>  
#include "QObject.h"  
#include <iostream>static int find_string(const char * str, const char * substr)
{if (strlen(str) < strlen(substr))return -1;int idx = 0;int len = strlen(substr);bool start = true;const char * pos = str;char cEnd = '\n';while (*pos) {if (start && !strncmp(pos, substr, len) && pos[len] == '\n')return idx;start = false;if (*pos == cEnd) {idx++;start = true;}pos++;}return -1;
}void QObject::connect(QObject* sender, const char* sig, QObject* receiver, const char* slt)
{int sig_idx = find_string(sender->staticMetaObject.sig_names, sig);int slt_idx = find_string(receiver->staticMetaObject.slts_names, slt);if (sig_idx == -1 || slt_idx == -1) {perror("signal or slot not found!");}else {Connection c = { receiver, slt_idx };sender->connections.insert(std::pair<int, Connection>(sig_idx, c));}
}void QObject::slot1()
{std::cout << "into slot1" << std::endl;
}void QObject::slot2()
{std::cout << "into slot2" << std::endl;
}
/*
从前面的保存连接的map中,找出与
该信号关联的对象和槽调用该对象这个槽
*/
void QMetaObject::active(QObject* sender, int idx)
{ConnectionMapIt it;std::pair<ConnectionMapIt, ConnectionMapIt> ret;ret = sender->connections.equal_range(idx);for (it = ret.first; it != ret.second; ++it) {Connection c = (*it).second;c.receiver->qt_static_metacall(c.method);}
}void QObject::testSignal()
{emit sig2();emit sig1();
}
#pragma  once#ifndef Q_OBJECT_H  
#define Q_OBJECT_H
#include <map>  # define slots  
# define signals protected  
# define emit  # define Q_OBJECT static QMetaObject staticMetaObject;void qt_static_metacall(int idx);class QObject;
/*元对象*/
struct QMetaObject
{const char * sig_names;const char * slts_names;/*信号的发送者和信号的索引*/static void active(QObject * sender, int idx);
};struct Connection
{QObject * receiver;int method;
};typedef std::multimap<int, Connection> ConnectionMap;
typedef std::multimap<int, Connection>::iterator ConnectionMapIt;class QObject
{static QMetaObject staticMetaObject; void qt_static_metacall(int idx);public:static void connect(QObject*, const char*, QObject*, const char*);void testSignal();signals:void sig1();void sig2();public slots:void slot1();void slot2();friend struct QMetaObject;private:ConnectionMap connections;
};
#endif  
/*此文件手动生成,其实应该是Moc预编译器生成的*/
#include "QObject.h"  static const char sig_names[] = "sig1\nsig2\n";static const char slts_names[] = "slot1\nslot2\n";QMetaObject QObject::staticMetaObject = { sig_names, slts_names };void QObject::sig1()
{QMetaObject::active(this, 0);
}void QObject::sig2()
{QMetaObject::active(this, 1);
}void QObject::qt_static_metacall(int idx)
{switch (idx){case 0:{slot1();break;}case 1:{slot2();break;}};
}
#include <iostream>
#include <string>
#include "QObject.h"int main()
{QObject obj1, obj2;QObject::connect(&obj1, "sig1", &obj2, "slot1");QObject::connect(&obj1, "sig2", &obj2, "slot2");obj1.testSignal();return 0;;
}

下面是Qt中的宏替换

QObject::connect(countObj1, SIGNAL(valueChanged()), countObj2, SLOT(slotValueChanged()));
QObject::connect(countObj1, "2""valueChanged()", countObj2, "1""slotValueChanged()");
#define Q_OBJECT \
public: \static const QMetaObject staticMetaObject; \virtual const QMetaObject *metaObject() const; \virtual void *qt_metacast(const char *); \virtual int qt_metacall(QMetaObject::Call, int, void **);
private: \static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \struct QPrivateSignal {};
struct Q_CORE_EXPORT QArrayData
{QtPrivate::RefCount ref;int size;uint alloc : 31;uint capacityReserved : 1;qptrdiff offset; // in bytes from beginning of header}

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

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

相关文章

Openswan安装和简单配置

Openswan安装和简单配置 安装环境&#xff1a; 操作系统&#xff1a;Ubuntu20.0.4TLS 用户权限&#xff1a;root下载Openswan: wget https://github.com/xelerance/Openswan/archive/refs/tags/v3.0.0.zip安装Openswan: 解压Openswan&#xff1a;&#xff08;PS&#xff1a…

银行数字化转型导师坚鹏:商业银行数字化风控(2天)

商业银行数字化风控 课程背景&#xff1a; 数字化背景下&#xff0c;很多银行存在以下问题&#xff1a; 不清楚商业银行数字化风控发展现状&#xff1f; 不清楚对公业务数字化风控工作如何开展&#xff1f; 不知道零售业务数字化风控工作如何开展&#xff1f; 课程特色…

海光信息业绩高歌猛进,但其作为国产CPU龙头的“地基”并不牢固

‍数据智能产业创新服务媒体 ——聚焦数智 改变商业 在“芯片寒冬”的大背景下&#xff0c;2022年全球头部芯片半导体公司纷纷下调业绩预期&#xff0c;英特尔、英伟达、美光等无一幸免。但是随着AIGC异军突起&#xff0c;仿佛寒冬中的一股暖流&#xff0c;催生着半导体市场行…

C. Trailing Loves (or L‘oeufs?)(求某个质因子在n的阶乘中的个数 + 思维)

Problem - C - Codeforces Aki喜欢数字&#xff0c;尤其是那些带有尾随零的数字。例如&#xff0c;数字9200有两个尾随零。Aki认为数字拥有的尾随零越多&#xff0c;它就越漂亮。 然而&#xff0c;Aki认为&#xff0c;一个数字拥有的尾随零的数量并不是固定的&#xff0c;而是…

idea中导入spring源码;在spring源码中添加注释

标题&#xff1a;idea中导入spring源码;在spring源码中添加注释 我是跟着他操作的&#xff0c;下文是一些补充说明&#xff1a; 这个也可以借鉴 gradle下载链接【使用网盘下载】,不过有的没有&#xff0c; gradel下载链接&#xff1a;这个比较全 1.Spring源码编译环境 spr…

Karl Guttag:现有Micro LED/LCoS+光波导AR眼镜对比解析

轻量化是未来AR眼镜的发展趋势&#xff0c;为了缩减尺寸&#xff0c;AR眼镜厂商尝试了多种方案&#xff0c;长期来看Micro LED光机在小型化上更有优势&#xff0c;但现阶段LCoS光机的图像表现更好。在CES 2023期间&#xff0c;DigiLens、Lumus、Vuzix、OPPO、Avegant也展出了不…

linux编译安装python的全过程,pip python不与linux系统环境混乱

因为使用要求&#xff0c;使得我需要在linux环境下安装一个独立的python环境&#xff0c;不干扰其他环境。 预安装 在安装python之前&#xff0c;请在linux系统下安装下面这些包&#xff1a; sudo apt-get install namelibssl-dev libcurl4 libcurl4-openssl-dev zlib-devel…

27-Servlet执行原理

目录 1.Tomcat详解 ①接收请求&#xff1a; ②根据请求计算响应&#xff1a; ③返回响应&#xff1a; 2.Tomcat执行流程 2.1.Tomcat 初始化流程 2.2.Tomcat 处理请求流程 2.3.Servlet 的 service 方法的实现 在 Servlet 的代码中并没有写 main ⽅法&#xff0c;那么对应…

【C++关联容器】map的成员函数

目录 map 1. 构造、析构和赋值运算符重载 1.1 构造函数 1.2 析构函数 1.3 赋值运算符重载 2. 迭代器 3. 容量 4. 元素访问 5. 修改器 6. 观察者 7. 操作 8. 分配器 map map是关联容器&#xff0c;它按照特定的顺序存储由关键字值和映射值的组合形成的元素。 在一…

【Springboot系列】项目启动时怎么给mongo表加自动过期索引

1、前言 在之前操作mongo的过程中&#xff0c;都是自动创建&#xff0c;几乎没有手动创建过表。 这次开发中有张表数据量大&#xff0c;并且不是特别重要&#xff0c;数据表维护一个常见的问题是过期数据没有被及时清理&#xff0c;导致数据量过大&#xff0c;查询变得缓慢。…

LeetCode-242. 有效的字母异位词

题目链接 LeetCode-242. 有效的字母异位词 题目描述 题解 题解一&#xff08;Java&#xff09; 作者&#xff1a;仲景 首先&#xff0c;满足条件的情况下&#xff0c;两个字符串的长度一定是相等的&#xff0c;不相等一定不满足条件 使用Hash表来存储字符串s中各个字符出现的…

Spring Security实战(九)—— 使用Spring Security OAuth实现OAuth对接

一、OAuth2.0介绍 OAuth2.0是一种授权协议&#xff0c;允许用户授权第三方应用程序代表他们获取受保护的资源&#xff0c;如个人信息或照片等。它允许用户授权访问他们存储在另一个服务提供商上的资源&#xff0c;而无需将其凭据共享给第三方应用程序。OAuth2.0协议建立在OAuth…

【具体到每一步】从0制作一个uniapp的新闻类页面(界面篇)

目录 项目初始化 / 基础配置 项目创建 配置路由/页面/tabbar pages.json配置tabbar 配置图标/静态资源 导航栏和字体颜色 scroll-view实现横向滚动条样式 公共模块定义components组件 新建组件 使用组件 组件里的结构 布局个人中心页面 组件差异化处理 数据传递 导航…

DevExpress:报表在winform窗体上显示(使用documentViewer控件)

一&#xff1a;控件认识 documentViewer&#xff08;版本DX22.2&#xff09;,老版本中的可能是printControl&#xff08;工具箱面板中可能找不到&#xff09;&#xff0c;通过官网搜索发现&#xff0c;这个控件现在继承于documentViewer这个控件。因此&#xff0c;使用documen…

Unity入门(一)

Unity Unity是一套完善体系与编辑器的跨平台游戏开发工具&#xff0c;也可以称之为游戏引擎。游戏引擎是指一些编写好的可以重复利用的代码与开发游戏所用的各功能编辑器。 基于C#编程&#xff0c;易上手&#xff0c;高安全性独特的面向组件游戏开发思想让游戏开发更加简单易…

【神经网络】tensorflow实验7--回归问题

1. 实验目的 ①掌握一元线性回归模型的实现方法 ②掌握多元线性回归模型的实现方法 ③掌握三维数据可视化方法 2. 实验内容 ①使用TensorFlow建立一元线性回归模型&#xff0c;使用商品房销售数据训练模型&#xff0c;并使用训练好的模型预测房价 ②使用TensorFlow建立多元线…

十、ElasticSearch 实战 - 源码运行

一、概述 想深入理解 Elasticsearch&#xff0c;了解其报错机制&#xff0c;并有针对性的调整参数&#xff0c;阅读其源码是很有必要的。此外&#xff0c;了解优秀开源项目的代码架构&#xff0c;能够提高个人的代码架构能力 阅读 Elasticsearch 源码的第一步是搭建调试环境&…

思维导图从入门到大神

思维导图怎么做&#xff1f;思维导图是一种发散性思维的图。在我们生活的方方面面都有运用。无论是工作、学习、还是生活&#xff0c;我们都可以用到它。那思维导图是怎么绘制的呢&#xff1f;其实非常简单&#xff0c;只要这简单的几步 1、首先在绘制思维导图前&#xff0c;我…

【网络】-- UDP协议

目录 传输层 再谈端口号 端口号范围划分 认识知名端口号&#xff08;Well-Know Port Number&#xff09; 两个问题 netstat pidof UDP协议 UDP的特点 UDP的缓冲区 UDP使用注意事项 基于UDP的应用层协议 传输层 负责数据能够从发送端传输接收端。 再谈端口号 端…

Codeforces Round 861 (Div. 2)(A~D)

A. Lucky Numbers 给出边界l和r&#xff0c;在区间[l, r]之间找到幸运值最大的数字。一个数字的幸运值被定义为数位差的最大值&#xff0c;即数字中最大的数位和最小的数位的差。 思路&#xff1a;因为涉及到至少两位&#xff0c;即个位和十位变化最快&#xff0c;最容易得到相…