QT | 信号与槽(超详解)

news/2025/3/15 7:14:12/

 前言
qt信号和槽的详细解释

💓 个人主页:普通young man-CSDN博客

⏩ 文章专栏:C++_普通young man的博客-CSDN博客

⏩ 本人giee:   普通小青年 (pu-tong-young-man) - Gitee.com

      若有问题 评论区见📝

🎉欢迎大家点赞👍收藏⭐文章
————————————————


目录

信号和槽的概述

信号的本质

槽的本质

信号和槽的使用

FunctionPointer ::Object *sender 参数解释

代码实现:

可视化实现

自定义信号和槽

信号函数实现的要求

通过按键发送信号

信号和槽带参数

代码展示:

违反规则举例:

信号和槽的关系

一对一

⼀对多

多对一

信号与槽的断开

Lambda的利用

信号与槽的优缺点

优点:松散耦合

缺点:效率较低

低耦合高聚合

低耦合

高聚合


信号和槽的概述

在 Qt 中,用户与控件的每次交互都构成一个事件,如 “用户点击按钮”“用户关闭窗口” 等。每个事件都会发出相应信号,像点击按钮会发出 “按钮被点击” 信号,关闭窗口会发出 “窗口被关闭” 信号。

Qt 里的所有控件都具备接收信号的能力,且一个控件可接收多个不同信号。对于接收到的信号,控件会做出相应响应动作,这种响应动作在 Qt 中被称为。例如,按钮所在窗口接收到 “按钮被点击” 信号后,可能做出 “关闭自己” 的响应;输入框接收到 “输入框被点击” 信号后,会 “显示闪烁的光标,等待用户输入数据”。

信号和槽是 Qt 独有的消息传输机制,其作用是将相互独立的控件关联起来。以 “按钮” 和 “窗口” 为例,它们原本是独立控件,点击按钮不会对窗口产生影响,但通过信号和槽机制,就能实现 “点击按钮使窗口关闭” 的效果。

信号的本质

信号的本质就是事件,比如用户的点击就可以是一个点击事件,窗口的刷新,鼠标的悬停,按下,释放,键盘的输入等等。

qt中其实信号就是一个函数,当事件产生qt的框架就会通知这个函数,然后这个函数就会发出当前的这个信号(发出这个信号也可以想成一种函数返回)

槽的本质

槽的本质就是处理信号发出的信号并完成对应的动作 , 槽也就是响应信号的一个函数,可以这么想,信号发出,槽函数接收到之后就执行相关操作槽函数和一般函数的区别:槽函数可以与一个信号关联,当信号发出的时候,关联的槽函数自动执行。

注:

在 Qt 的信号和槽机制中,其底层是通过函数间的相互调用来实现的。每个信号都可以用函数表示,这类函数被称为信号函数;每个槽也能用函数来表示,被叫做槽函数。

 

例如,“按钮被按下” 这一信号可以用 clicked() 函数来表示,“窗口关闭” 这个槽则可以用 close() 函数表示。若要使用信号和槽机制实现 “点击按钮会关闭窗口” 的功能,本质上就是 clicked() 函数调用 close() 函数所达成的效果。

 

信号函数和槽函数通常存在于某个类中,与普通成员函数相比,它们具有以下特别之处:

 
  • 关键字修饰:信号函数使用 signals 关键字修饰,槽函数则使用 public slotsprotected slots 或者 private slots 修饰。signals 和 slots 是 Qt 在 C++ 基础上扩展的关键字,专门用于指明信号函数和槽函数。
  • 定义要求:信号函数只需进行声明,无需进行定义(实现);而槽函数则需要进行定义(实现)。

信号和槽的使用

再QT中,QObject类提供了一个静态成员函数connect(),用来连接信号函数与槽函数。

/*** @brief 该函数用于在 Qt 中建立信号和槽之间的连接,实现对象间的通信机制。** 在 Qt 的事件驱动编程模型里,信号和槽是核心机制,它使得对象之间可以以一种松耦合的方式进行交互。* 当信号发出时,与之相连的槽函数会被调用执行。** @param sender 指向发出信号的对象的指针,该对象必须是 QObject 或其子类的实例。*               信号就是从这个对象产生的,例如一个按钮对象可以发出被点击的信号。** @param signal 要连接的信号,以字符串形式表示。*               通常使用 SIGNAL() 宏来生成这个字符串,例如 SIGNAL(clicked()) 表示按钮被点击的信号。*               此字符串准确描述了要监听的信号名称。** @param receiver 指向接收信号并执行相应槽函数的对象的指针,同样要求是 QObject 或其子类的实例。*                 当 sender 发出 signal 信号时,receiver 对象中的相应槽函数会被触发。** @param method 要连接的槽函数,以字符串形式表示。*               一般使用 SLOT() 宏来生成,例如 SLOT(close()) 表示关闭窗口的槽函数。*               它指定了当信号发出时,receiver 对象中要执行的具体函数。** @param type 连接类型,指定了信号和槽之间的连接方式,默认值为 Qt::AutoConnection。*             不同的连接类型决定了槽函数何时以及如何被调用,具体如下:*             - Qt::AutoConnection:根据 sender 和 receiver 是否在同一线程自动选择连接方式。*                                   如果在同一线程,采用直接连接;否则采用队列连接。*             - Qt::DirectConnection:信号发出时直接调用槽函数,不考虑线程问题,槽函数会立即执行。*             - Qt::QueuedConnection:将槽函数调用放入 receiver 所在线程的事件队列中,*                                   槽函数会在 receiver 所在线程的事件循环中被处理,确保线程安全。*             - Qt::BlockingQueuedConnection:与 Qt::QueuedConnection 类似,但会阻塞 sender 线程,*                                   直到槽函数执行完毕,适用于需要等待槽函数执行结果的情况。*             - Qt::UniqueConnection:确保连接是唯一的,避免重复连接。若已经存在相同的连接,不会再次建立。** @return 如果成功建立连接,返回 true;若连接失败(如信号或槽不存在等原因),返回 false。*/
bool connect (const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection);

注:

connect函数在连接信号和槽的时候,比如我是一个QPushButton的控件,那connect函数的第二个参数必须是QPushButton的信号函数(父类的信号),不能是其他类的函数

对connect函数的第二,四个参数做解答:

你会发现这两个参数居然传的是char*,我们再来看看我传入的函数是什么返回类型:

返回类型void(*)()函数指针,难道函数指针 == char* ,这当然是不可能的。

这里上面有一个解决办法:
就是在传参数的时候加上两个宏

SIGNAL和SLOT(将函数指针进行转换,就可以传入)

SIGNAL 宏

把信号函数名转换为字符串。在使用 connect 函数连接信号和槽时,要以字符串形式指定信号,SIGNAL 宏就负责完成这个转换,方便将信号和对应的对象关联起来。

SLOT 宏

将槽函数名转换为字符串。同样在 connect 函数中,需要以字符串形式指定槽函数,SLOT 宏能把槽函数转换为符合要求的字符串,从而建立信号和槽之间的连接。

其实,这个connect并不是用的声明上的版本,当我们ctrl+鼠标左键点击connect函数的时候,你会发现是一个qt5之后的版本,这个新的版本还是泛型编程,使得更加灵活

FunctionPointer<Func1>::Object *sender 参数解释

萃取器(Traits)的概念

萃取器是 C++ 模板编程中的一种技术,它的核心作用是从某种类型中提取相关的属性或信息。萃取器通常以模板类的形式呈现,通过模板特化来针对不同的类型提供不同的实现。借助萃取器,我们能够在编译时获取类型的相关信息,从而实现更具通用性和灵活性的代码。

FunctionPointer<Func1>::Object 作为萃取器的解释

FunctionPointer 是 Qt 内部的一个模板类,其主要目的是在编译时对函数指针类型进行解析,进而提取出与该函数指针相关的各种信息,像函数的返回类型、参数类型以及函数所属的对象类型等。FunctionPointer<Func1>::Object 专门用于从信号或槽的函数指针类型 Func1 中提取出函数所属的对象类型。

简单说就是一个模板的特化

代码实现:

我简单的创建一个按键:

如果你想查看自带的信号:可以点到哪个函数之后按键盘上的F1,就可以打开相关文档

这里讲解一个东西,就是你在敲代码的时候会发现:

当你调用自带的信号函数的时候,出现了click和一个clicked,这两个一个就是点击了一下(锯齿图标),一个是点击之后会发出信号(信号发射图标),这个你可以自己试一试效果,当你把clicked改成click的时候就会发现点击按钮的时候,什么效果都没有,原因就是没发出信号。

还有这个close()是Widget内置的槽函数,作用就是关闭这个窗口并且释放。


可视化实现

首先双击这个ui文件,进入可视化的面板

然后托一个按钮,当然你可以自己调整大小,文字大小,位置等等

clicked()就是点击的意思,这个大家如果英语不好的话可以自己翻译一下,双击

双击之后就会在widget.cpp生成出这个函数

  1. 槽函数名称以 on 开头,并且通过下划线将各部分连接起来。
  2. XXX 表示对象名,即控件的 objectName 属性值。该属性可以在 Qt 设计器中设置,用于唯一标识一个控件对象。
  3. SSS 表示对应的信号名称。例如,按钮被点击这一动作对应的信号为 clicked

自定义信号和槽

自定义信号不是很常见,因为在GUI qt这么个框架可以是可以穷举这些信号,因此在qt中可以应付大多数场景。

信号实现起来很简单,因为本质就是一个函数,和槽函数一样比较特殊,但是在qt5中已经和普通的成员函数差不多,因为都继承自最高的那个父类。

信号函数实现的要求

  1. 类定义
    • 继承自 QObject
    • 添加 Q_OBJECT 宏
  2. 信号声明
    • 放在 signals 部分
    • 返回类型为 void
    • 无需实现体
  3. 信号发射
    • 使用 emit 关键字(现在在qt5中也可以不用这个关键字,但是为了代码的易读性,建议添加)
  4. 信号连接
    • 用 QObject::connect 函数
    • 信号与槽参数类型兼容

通过一段代码具体讲解

通过emit发送信号(如果不使用emit就像直接调用函数一样去调用这个信号函数,在编译的时候就会自定发出信号),但是现在就是一个简单的信号,他现在什么操作都没有

调用自己的信号函数

我们通过connect来连接一个槽函数:

代码编译结果:
发现我改变了窗口的标题

如果我们不调用信号函数,就什么都不会发生:

通过按键发送信号

编译结果:

信号和槽带参数

信号和槽函数也是可以带参数的,但是嘞有要求:
1.信号的槽函数的参数必须一 一对应

2.信号的参数个数 >= 槽的参数个数

代码展示:

首先给一个两个按钮

通过按钮点击发送信号,但是这个信号函数是带参数的,也就是说他会带上这个参数一起发送出去

槽函数会接收这个信号以及参数

你可能会问他们怎么就能传参?其实大家不要忘了connect函数可以连接这两个函数,所以要求大家信号函数和槽函数的参数一定要保持一致

违反规则举例:

当槽函数的参数比信号多的时候,就会报错不匹配

从这个代码可以看出槽函数的参数是两个,当我们编译的时候就会被检查到信号和插槽参数不兼容

那为什么槽函数就不能比信号的参数多,而信号的参数就可以比槽函数的参数少嘞?

其实简单的说就是一种传递机制,信号函数作为发送端,槽函数作为接收端,这里我们必须解释一下在qt中信号和槽函数是一个多对多的关系,下面我专门来一个标题

信号和槽的关系
 
一对一

一个信号连接一个槽。一个信号连接一个信号。

⼀对多

⼀个信号连接多个槽

多对一

多个信号连接⼀个槽函数

  • 参数传递机制
    • Qt 的信号与槽连接机制在实现时,会根据信号和槽的参数信息进行参数传递。当信号参数多于槽函数参数时,Qt 可以很方便地忽略多余的参数,只传递槽函数需要的参数。
    • 但如果槽函数参数多于信号参数,就无法实现参数的正确传递,因为没有足够的信息来填充槽函数的所有参数。

信号与槽的断开

断开连接我们到的是disconnect 函数,他的参数列表:

  • 第一个参数 ui->pushButton 是信号的发送者,也就是按钮对象。
  • 第二个参数 &QPushButton::clicked 是要断开连接的信号。
  • 第三个参数 this 是信号的接收者,也就是 Widget 对象。
  • 第四个参数 &Widget::handlefunc 是要断开连接的槽函数。

下面我直接通过一个代码直观的看出他的作用:

通过这个代码,我可以实现通过第一个按钮进行打印标题一

当点击槽函数切换的按钮是,它会断开之前的连接,重新连接新的槽函数,再次点击按钮一打印标题二

可以打印日志来看是否切换


Lambda的利用

其实qt中使用的是c++11:

直接上代码:

通过这段代码,解释一下,我创建了一个按钮,然后给他设置了一个文字,lambada实现一个我点击之后他会到目标xy坐标位置

connect本来就是一个泛型的,lambada可以又std::function()包装器接收

使用lambada时候需要注意:

lambada由于作用域的原因需要捕获外部变量,这个是c++11的基础,所以这里访问不到tmp,

注意qt不需要传引用,因为qt中很多api的参数都是指针,所以直接传值和传引用是一样的

捕获方式语法示例解释特点示例代码
值捕获[var1, var2, ...]明确指定要捕获的外部变量,Lambda 内部会复制这些变量的值。- 外部变量的修改不会影响 Lambda 内部的值。
- Lambda 内部对捕获变量的修改不会影响外部变量。
cpp<br>int x = 10;<br>auto lambda = [x]() {<br> return x * 2;<br>};<br>x = 20;<br>std::cout << lambda() << std::endl; // 输出 20<br>
引用捕获[&var1, &var2, ...]明确指定以引用的方式捕获外部变量,Lambda 内部使用的是外部变量的引用。- 外部变量的修改会影响 Lambda 内部的值。
- Lambda 内部对捕获变量的修改会影响外部变量。
cpp<br>int x = 10;<br>auto lambda = [&x]() {<br> x *= 2;<br>};<br>lambda();<br>std::cout << x << std::endl; // 输出 20<br>
隐式值捕获[=]以值的方式捕获所有外部变量。- 方便快捷,无需逐个列出要捕获的变量。
- 外部变量的修改不会影响 Lambda 内部的值。
cpp<br>int x = 10, y = 20;<br>auto lambda = [=]() {<br> return x + y;<br>};<br>x = 30; y = 40;<br>std::cout << lambda() << std::endl; // 输出 30<br>
隐式引用捕获[&]以引用的方式捕获所有外部变量。- 方便捕获多个变量。
- 外部变量的修改会影响 Lambda 内部的值,反之亦然。
cpp<br>int x = 10, y = 20;<br>auto lambda = [&]() {<br> x += y;<br>};<br>lambda();<br>std::cout << x << std::endl; // 输出 30<br>
混合捕获[=, &var1] 或 [&, var1]结合了隐式捕获和显式捕获,[=, &var1] 表示除 var1 以引用方式捕获外,其他变量以值的方式捕获;[&, var1] 表示除 var1 以值的方式捕获外,其他变量以引用方式捕获。

信号与槽的优缺点

优点:松散耦合

  • 信号发送者无需知晓哪个对象的槽函数会接收其发出的信号,槽函数也不必了解有哪些信号与其关联,Qt 的信号槽机制会保障信号与槽函数的调用。
  • 支持信号槽机制的类或其父类必须继承自 QObject 类。

缺点:效率较低

  • 相较于回调函数,信号和槽的执行速度稍慢,这是因其提供了更高的灵活性。
  • 通过信号调用槽函数比直接调用速度慢约 10 倍,这是由于定位信号接收对象、遍历所有关联、编组和解组传递参数,以及多线程时信号可能需要排队等操作带来的开销。
  • 不过,在对性能要求不是极高的场景中,这种速度差异可忽略不计,能满足绝大部分应用场景。

低耦合高聚合

低耦合

  • 生活类比:想象你和你的邻居是两个 “个体”。你平时的生活和邻居的生活互不干扰,比如你今天决定去看电影,邻居决定在家看书,你们各自的决定和行为基本不会影响到对方。即使邻居突然要出门旅行,对你的日常生活也没有太大的影响,你依然可以按部就班地过自己的日子。这就类似于低耦合的状态,两个个体之间相互依赖程度很低。
  • 编程概念:在编程中,低耦合指的是模块与模块之间的相互依赖程度低。不同的模块就像是不同的 “个体”,它们各自有独立的功能,一个模块的修改或者变动,对其他模块的影响较小。比如在一个大型的电商系统中,用户登录模块和商品推荐模块就是低耦合的。用户登录模块负责处理用户的身份验证和登录相关功能,商品推荐模块根据用户的浏览和购买历史来推荐商品。如果对用户登录模块进行优化,比如增加一种新的登录方式(如指纹登录),一般情况下,这并不会影响到商品推荐模块的正常运行,两个模块之间相互干扰少。

高聚合

  • 生活类比:再拿一个家庭来举例,家庭成员之间分工明确且紧密合作。比如在准备一顿丰盛的晚餐时,爸爸负责买菜,妈妈负责烹饪,孩子负责摆放餐具和收拾餐桌。大家都围绕着 “准备晚餐” 这个目标行动,各自的任务都和这个目标紧密相关,并且相互配合得很好。这就像高聚合的状态,大家为了一个共同的目标,各自的工作都高度相关且集中。
  • 编程概念:在编程里,高聚合是指一个模块内部的各个组成部分之间的联系紧密,它们都围绕着一个核心功能来设计和实现。例如在一个文本处理软件中,有一个专门用于文本编辑的模块。这个模块内包含了文本的输入、修改、删除、格式设置等功能,这些功能都紧密围绕着 “文本编辑” 这个核心任务。它们之间相互配合,共同完成对文本的各种操作,模块内部的各个部分之间联系紧密,不会出现一些与文本编辑无关的功能混杂在其中,这就是高聚合的体现。/


http://www.ppmy.cn/news/1579254.html

相关文章

C++11 编译使用 aws-cpp-sdk

一、对sdk的编译前准备 1、软件需求 此文档针对于在Linux系统上使用源码进行编译开发操作系统使用原生的contos7Linux。机器配置建议 内存8G以上,CPU 4个 以上GCC 4.9.0 及以上版本Cmake 3.12以上 3.21以下apt install libcurl-devel openssl-devel libuuid-devel pulseaudio-…

内部类 ,匿名对象,编译器优化和静态成员

一.静态成员 1.1 了解静态成员 ⽤static修饰的成员变量&#xff0c;称之为静态成员变量&#xff0c;静态成员变量⼀定要在类外进⾏初始化。 报错了&#xff0c;是因为 不能直接给它值&#xff0c;这里只是声明&#xff0c;这里的值是缺省值&#xff0c;静态成员是属于静态区的…

现代密码学 | 具有保密和认证功能的安全方案

1.案例背景 1.1 2023年6月&#xff0c;微软云电子邮件泄露 事件描述&#xff1a; 2023年6月&#xff0c;属于多家美国政府机构的微软云电子邮件账户遭到非法入侵&#xff0c;其中包括了多位高级政府官员的电子邮件。据报道&#xff0c;美国国务院的10个邮件账户中共有6万封电…

基于Python+Django的网上招聘管理系统

项目介绍 PythonDjango网上招聘系统的设计与实现(Pycharm Django Vue Mysql) 平台采用B/S结构&#xff0c;后端采用主流的Python语言进行开发&#xff0c;前端采用主流的Vue.js进行开发。整个平台包括前台和后台两个部分。 - 前台功能包括&#xff1a;首页、岗位详情页、简历中…

【动态规划篇】746.使用最小花费爬楼梯

746.使用最小花费爬楼梯 题目链接&#xff1a; 746.使用最小花费爬楼梯 题目叙述&#xff1a; 给你一个整数数组 cost &#xff0c;其中 cost[i] 是从楼梯第i个台阶向上爬需要支付的费用。一旦你支付此费用&#xff0c;即可选择向上爬一个或者两个台阶。 你可以选择从下标为 …

【病毒分析】熊猫烧香病毒分析及其查杀修复

目录 前言 一、样本概况 1.1 样本信息 1.2 测试环境及工具 1.3 分析目标 二、具体行为分析 2.1 主要行为 2.1.1 恶意程序对用户造成的危害 2.2 恶意代码分析 2.2.1 加固后的恶意代码树结构图(是否有加固) 2.2.2 恶意程序的代码分析片段 三、解决方案(或总结) 3.1 …

基于PySide6与PyCatia的CATIA几何体智能重命名工具开发实践

一、工具概述 本工具基于CATIA V5/V6的二次开发接口&#xff0c;结合PySide6图形界面框架与PyCatia自动化库&#xff0c;实现了三大核心功能模块&#xff1a;几何体前缀批量添加、后缀动态追加、智能文本替换。该工具显著提升了工程师在大型零件体设计中的几何体命名管理效率&…

Python----计算机视觉处理(Opencv:自适应二值化,取均值,加权求和(高斯定理))

一、自适应二值化 与二值化算法相比&#xff0c;自适应二值化更加适合用在明暗分布不均的图片&#xff0c;因为图片的明暗不均&#xff0c;导致图片 上的每一小部分都要使用不同的阈值进行二值化处理&#xff0c;这时候传统的二值化算法就无法满足我们的需求 了&#xff0c;于是…