前言
对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 slots
、protected 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生成出这个函数
- 槽函数名称以
on
开头,并且通过下划线将各部分连接起来。XXX
表示对象名,即控件的objectName
属性值。该属性可以在 Qt 设计器中设置,用于唯一标识一个控件对象。SSS
表示对应的信号名称。例如,按钮被点击这一动作对应的信号为clicked
自定义信号和槽
自定义信号不是很常见,因为在GUI qt这么个框架可以是可以穷举这些信号,因此在qt中可以应付大多数场景。
信号实现起来很简单,因为本质就是一个函数,和槽函数一样比较特殊,但是在qt5中已经和普通的成员函数差不多,因为都继承自最高的那个父类。
信号函数实现的要求
- 类定义
- 继承自
QObject
- 添加
Q_OBJECT
宏- 信号声明
- 放在
signals
部分- 返回类型为
void
- 无需实现体
- 信号发射
- 使用
emit
关键字(现在在qt5中也可以不用这个关键字,但是为了代码的易读性,建议添加)- 信号连接
- 用
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 倍,这是由于定位信号接收对象、遍历所有关联、编组和解组传递参数,以及多线程时信号可能需要排队等操作带来的开销。
- 不过,在对性能要求不是极高的场景中,这种速度差异可忽略不计,能满足绝大部分应用场景。
低耦合高聚合
低耦合
- 生活类比:想象你和你的邻居是两个 “个体”。你平时的生活和邻居的生活互不干扰,比如你今天决定去看电影,邻居决定在家看书,你们各自的决定和行为基本不会影响到对方。即使邻居突然要出门旅行,对你的日常生活也没有太大的影响,你依然可以按部就班地过自己的日子。这就类似于低耦合的状态,两个个体之间相互依赖程度很低。
- 编程概念:在编程中,低耦合指的是模块与模块之间的相互依赖程度低。不同的模块就像是不同的 “个体”,它们各自有独立的功能,一个模块的修改或者变动,对其他模块的影响较小。比如在一个大型的电商系统中,用户登录模块和商品推荐模块就是低耦合的。用户登录模块负责处理用户的身份验证和登录相关功能,商品推荐模块根据用户的浏览和购买历史来推荐商品。如果对用户登录模块进行优化,比如增加一种新的登录方式(如指纹登录),一般情况下,这并不会影响到商品推荐模块的正常运行,两个模块之间相互干扰少。
高聚合
- 生活类比:再拿一个家庭来举例,家庭成员之间分工明确且紧密合作。比如在准备一顿丰盛的晚餐时,爸爸负责买菜,妈妈负责烹饪,孩子负责摆放餐具和收拾餐桌。大家都围绕着 “准备晚餐” 这个目标行动,各自的任务都和这个目标紧密相关,并且相互配合得很好。这就像高聚合的状态,大家为了一个共同的目标,各自的工作都高度相关且集中。
- 编程概念:在编程里,高聚合是指一个模块内部的各个组成部分之间的联系紧密,它们都围绕着一个核心功能来设计和实现。例如在一个文本处理软件中,有一个专门用于文本编辑的模块。这个模块内包含了文本的输入、修改、删除、格式设置等功能,这些功能都紧密围绕着 “文本编辑” 这个核心任务。它们之间相互配合,共同完成对文本的各种操作,模块内部的各个部分之间联系紧密,不会出现一些与文本编辑无关的功能混杂在其中,这就是高聚合的体现。/