【QT】信号与槽

🎬 个人主页艾莉丝努力练剑
专栏传送门 :《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录
Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享

⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平


🎬 艾莉丝的简介:


文章目录

  • [1 ~> 信号和槽概述](#1 ~> 信号和槽概述)
    • [1.1 概述](#1.1 概述)
    • [1.2 信号的本质](#1.2 信号的本质)
    • [1.3 槽的本质](#1.3 槽的本质)
    • [1.4 说明](#1.4 说明)
    • [1.5 最佳实践](#1.5 最佳实践)
  • [2 ~> 信号和槽的使用](#2 ~> 信号和槽的使用)
    • [2.1 连接信号和槽](#2.1 连接信号和槽)
    • [2.2 查看内置信号和槽(查看文档)](#2.2 查看内置信号和槽(查看文档))
    • [2.3 通过Qt Creator生成信号槽代码](#2.3 通过Qt Creator生成信号槽代码)
    • [2.4 小结](#2.4 小结)
  • [3 ~> 自定义信号和槽](#3 ~> 自定义信号和槽)
  • [4 ~> 信号与槽的连接方式](#4 ~> 信号与槽的连接方式)
  • [5 ~> 信号和槽的其他说明](#5 ~> 信号和槽的其他说明)
    • [5.1 信号与槽的断开](#5.1 信号与槽的断开)
    • [5.2 Qt4版本信号与槽的连接](#5.2 Qt4版本信号与槽的连接)
    • [5.3 使用Lambda表达式定义槽函数](#5.3 使用Lambda表达式定义槽函数)
    • [5.4 信号与槽的优缺点](#5.4 信号与槽的优缺点)
      • [5.4.1 优点:松散耦合](#5.4.1 优点:松散耦合)
      • [5.4.2 缺点:效率较低](#5.4.2 缺点:效率较低)
    • [5.5 小结](#5.5 小结)
      • [5.5.1 关于信号槽的两个补充知识点](#5.5.1 关于信号槽的两个补充知识点)
  • [6 ~> 信号与槽小结](#6 ~> 信号与槽小结)
  • 结尾


1 ~> 信号和槽概述

1.1 概述

在Qt中,用户和控件的每次交互过程称为一个事件。比如"用户点击按钮"是一个事件,"用户关

闭窗口"也是一个事件。每个事件都会发出一个信号,例如用户点击按钮会发出"按钮被点击"的信号,用户关闭窗口会发出"窗口被关闭"的信号。

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

信号和槽是Qt特有的消息传输机制,它能将相互独立的控件关联起来。比如,"按钮"和"窗口"

本身是两个独立的控件,点击"按钮"并不会对"窗口"造成任何影响。通过信号和槽机制,可以将"按钮"和"窗口"关联起来,实现"点击按钮会使窗口关闭"的效果。

1.2 信号的本质

信号是由于用户对窗口或控件进行了某些操作,导致窗口或控件产生了某个特定事件,这时Qt对应的窗口类会发出某个信号,以此对用户的操作做出反应。因此,信号的本质就是事件。如:

  • 按钮单击、双击
  • 窗口刷新
  • 鼠标移动、鼠标按下、鼠标释放
  • 键盘输入

那么,在Qt中信号是通过什么形式呈现给使用者的呢?

  • 我们对哪个窗口进行操作,哪个窗口就可以捕捉到这些被触发的事件。

    对于使用者来说触发了一个事件我们就可以得到Qt框架给我们发出的某个特定信号。

  • 信号的呈现形式就是函数, 也就是说某个事件产生了,Qt框架就会调用某个对应的信号函数,通知使用者。

在Qt中信号的发出者是某个实例化的类对象。

1.3 槽的本质

槽(Slot)就是对信号响应的函数。 槽就是一个函数,与一般的C++函数是一样的,可以定义在类的任何位置(public、protected或private),可以具有任何参数,可以被重载,也可以被直接调用(但是不能有默认参数)。槽函数与一般的函数不同的是:槽函数可以与一个信号关联,当信号被发射时,关联的槽函数被自动执行。

1.4 说明

(1)信号和槽机制底层是通过函数间的相互调用实现的。每个信号都可以用函数来表示,称为信号函数;每个槽也可以用函数表示,称为槽函数。例如:"按钮被按下"这个信号可以用clicked()函数 表示,"窗口关闭"这个槽可以用close()函数 表示,假如使用信号和槽机制实现:"点击按钮会关闭窗口"的功能,其实就是clicked()函数 调用close()函数的效果。

(2)信号函数和槽函数通常位于某个类中,和普通的成员函数相比,它们的特别之处在于:

  • 信号函数用signals 关键字修饰,槽函数用public slots、protected slots 或者private slots 修饰。signalsslots是Qt在C++的基础上扩展的关键字,专门用来指明信号函数和槽函数;

  • 信号函数只需要声明,不需要定义(实现),而槽函数需要定义(实现)。

信号函数的定义是Qt自动在编译程序之前生成的。编写Qt应用程序的程序猿无需关注。

这种自动生成代码的机制称为元编程(MetaProgramming)。这种操作在很多场景中都能见到。

1.5 最佳实践


2 ~> 信号和槽的使用

2.1 连接信号和槽

在Qt中,QObject类提供了一个静态成员函数connect(),该函数专门用来关联指定的信号函数和槽函数。

关于QObject

QObject是Qt内置的父类。Qt中提供的很多类都是直接或者间接继承自QObject。

这一点的设定和Java 是非常相似的。

connect() 函数原型:

cpp 复制代码
connect (const QObject *sender, 
 const char * signal ,
 const QObject * receiver , 
 const char * method , 
 Qt::ConnectionType type = Qt::AutoConnection )
参数 说明
sender 信号的发送者
signal 发送的信号(信号函数)
receiver 信号的接收者
method 接收信号的槽函数
type 用于指定关联方式,默认的关联方式为 Qt::AutoConnection,通常不需要手动设定

代码示例:在窗口中设置一个按钮,当点击"按钮"时关闭"窗口"。

2.2 查看内置信号和槽(查看文档)

系统自带的信号和槽通常是通过 "Qt帮助文档" 来查询。

如上述示例,要查询 "按钮" 的信号,在帮助文档中输入:QPushButton

  • 首先可以在 "Contents" 中寻找关键字signals

  • 如果没有找到,继续去父类中查找.因此我们去他的父类QAbstractButton 中继续查找关键字signals

这里的 clicked() 就是要找的信号。槽函数的寻找方式和信号一样,只不过它的关键字是slot

查阅文档中的信号的时候,最重点就是关注信号的发送时机(用户进行了啥样的操作,就能够产生这个信号)------

2.3 通过Qt Creator生成信号槽代码

Qt Creator可以快速帮助我们生成信号槽相关的代码。

代码示例: 在窗口中设置一个按钮,当点击"按钮"时关闭"窗口"。

1、新建项目,如下图为新建完成之后所包含的所有文件------

注意:创建时要生成UI设计文件(双击即可跳转到 【设计】 界面)。

2、双击widget.ui文件,进入Ul设计界面------

3、在UI设计窗口中拖入一个"按钮",并且修改"按钮"的名称及字体大小等等------

4、可视化生成槽函数,如下图所示------

当单击 【转到槽...】 之后,出现如下界面:对于按钮来说,当点击时发送的信号是:clicked(),所以此处选择:clicked(),如下图所示------

对于普通按钮来说,使用clicked 信号即可。clicked(bool) 没有意义的。具有特殊状态的按钮(比如复选按钮)才会用到clicked(bool)

5、自动生成槽函数原型框架;

(1)在"widget.h"头文件中自动添加槽函数的声明

说明:

自动生成槽函数的名称有一定的规则。槽函数的命名规则 为:on_XXX_SSS,其中:

  • 1、以**"on"** 开头,中间使用下划线连接起来;
  • 2、"XXX" 表示的是对象名(控件的objectName属性)。
  • 3、"SSS" 表示的是对应的信号。

如:"on_pushButton_clicked()"pushButton代表的是对象名,clicked是对应的信号。

按照这种命名风格定义的槽函数,就会被Qt自动的和对应的信号进行连接。

但是咱们日常写代码的时候,除非是IDE自动生成,否则最好还是不要依赖命名规则,而是显式使用connect 更好。

(1)一方面显式connect可以更清晰直观的描述信号和槽的连接关系。

(2)另一方面也防止信号或者槽的名字拼写错误导致连接失效。

当然,是配置大于约定,还是约定大于配置,哪种更好,这样的话题业界尚存在争议,此处我个人还是更建议优先考虑显式connect。

(2)在"widget.cpp"中自动生成槽函数定义,如下图:

6、在槽函数函数定义中添加要实现的功能,实现关闭窗口的效果:

2.4 小结


3 ~> 自定义信号和槽

3.1 基本语法

在Qt中,允许自定义信号的发送方以及接收方,即可以自定义信号函数和槽函数。但是对于自定义的信号函数和槽函数有一定的书写规范。

1、自定义信号函数书写规范

  • (1)自定义信号函数必须写到"signals"下;
  • (2)返回值为void,只需要声明,不需要实现;
  • (3)可以有参数,也可以发生重载。

2、自定义槽函数书写规范

  • (1)早期的Qt版本要求槽函数必须写到"publicslots"下,但是现在高级版本的Qt允许写到类的"public"作用域中或者全局下;
  • (2)返回值为void,需要声明,也需要实现;
  • (3)可以有参数,可以发生重载;

3、发送信号

使用 "emit" 关键字发送信号。"emit"是一个空的宏。"emit"其实是可选的,没有什么含义,只是为了提醒开发人员。

示例1:

1、在widget.h中声明自定义的信号和槽, 如图所示:


2、在widget.cpp中实现槽函数,并且关联信号和槽

注意:图中的①和②的顺序不能颠倒。

原因是,首先关联信号和槽,一旦检测到信号发射之后就会立马执行关联的槽函数。反之,若先发射信号,此时还没有关联槽函数,当信号发射之后槽函数不会响应。

示例2:当老师说"上课了",学生们就"回到座位,开始学习"。

1、在源文件中新建两个类,一个是老师类,一个是学生类;首先选中项目名称,鼠标右键~~>【add new...】

点击【add new...】之后,出现如下界面:

选择【choose...】出现如下界面.

注意:

在Qt中新建类时,要选择新建类的父类。

显然,当前项目中还没啥类适合做新类的父类,同时新的类也不是一个"窗口"或者"控件"这种情况一般选择QObject 作为基类。

这样做的好处是这个新类的对象可以搭配Qt的对象树机制.便于对象的正确释放。

选择 【下一步】 ,出现如下界面:

对于 "学生类" 以上述同样的方式进行添加,添加完成之后,项目目录新增文件如下:

teacher.h 中声明信号函数:

student.h 中声明槽函数:

widget.h 中实例化 "老师类对象""学生类对象"

student.cpp 中实现槽函数:

widget.cpp 中连接自定义信号和槽:

运行结果如下所示:

示例3:老师点击"按钮"触发学生上课

运行结果如下所示:

3.2 带参数的信号和槽

Qt的信号和槽也支持带有参数,同时也可以支持重载。

此处我们要求,信号函数的参数列表要和对应连接的槽函数参数列表一致。

此时信号触发,调用到槽函数的时候,信号函数中的实参就能够被传递到槽函数的形参当中。

通过这样的机制,就可以让信号给槽传递数据了。

示例1:重载信号槽

(1)在"widget.h"头文件中声明重载的信号函数以及重载的槽函数;如下图所示:

(2)在"Widget.cpp"文件实现重载槽函数以及连接信号和槽。

注意:在定义函数指针时要指明函数指针的作用域。

(3)执行结果如下图所示:

示例2:信号槽参数列表匹配规则

1、在"widget.h"头文件中声明信号和槽函数:

2、在"widget.cpp"文件中实现槽函数以及连接信号和槽:

其实信号的参数个数可以多于槽函数的参数个数,但是槽的参数个数不能多于信号参数个数。 但是实际开发中最好还是保持参数个数也能匹配一致。

示例3:

1、在"widget.h"头文件中声明信号和槽函数:

2、在"widget.cpp"文件中实现槽函数以及连接信号和槽:

3.3 结论


4 ~> 信号与槽的连接方式

4.1 一对一

主要有两种形式,分别是:一个信号连接一个槽 一个信号连接一个信号

(1)一个信号连接一个槽

示例:

1、在"widget.h"中声明信号和槽以及信号发射函数

2、在"widget.cpp"中实现槽函数,信号发射函数以及连接信号和槽:

(2)一个信号连接另一个信号

示例:

在上述示例的基础上,在"widget.cpp"文件中添加如下代码:

4.2 一对多

一个信号连接多个槽

示例:

(1)在"widget.h"头文件中声明一个信号三个槽

(2)在"widget.cpp"文件中实现槽函数以及连接信号和槽:

4.3 多对---

多个信号连接一个槽函数。

示例:

(1)在"widget.h"头文件中声明两个信号 以及一个槽

(2)在"widget.cpp"文件中实现槽函数以及连接信号和槽:

4.4 小结


5 ~> 信号和槽的其他说明

5.1 信号与槽的断开

使用disconnect即可完成断开。

disconnect的用法和connect基本一致。

示例:

5.2 Qt4版本信号与槽的连接

Qt4 中的 connect 用法和 Qt5 相比是更复杂的.需要搭配 SIGNALSLOT 宏来完成,而且缺少必要的函数类型的检查,使代码更容易出错。

示例:

(1)在"widget.h"头文件中声明信号和槽:

(2)在"widget.cpp"文件中实现槽函数以及连接信号与槽:

Qt4版本信号与槽连接的优缺点

  • 优点: 参数直观;
  • 缺点: 参数类型不做检测。

示例:

5.3 使用Lambda表达式定义槽函数

Qt5Qt4的基础上提高了信号与槽的灵活性,允许使用任意函数作为槽函数

但如果想方便的编写槽函数,比如在编写函数时连函数名都不想定义,则可以通过Lambda表达式来达到这个目的。

Lambda表达式C++11增加的特性。C++11中的 Lambda表达式 用于定义并创建匿名的函数对象,以简化编程工作。

Lambda表达式 的语法格式如下:

cpp 复制代码
[ capture ] ( params ) opt -> ret { 
 Function body; 
};

说明:

部分 说明
capture 捕获列表
params 参数表
opt 函数选项
ret 返回值类型
Function body 函数体

1、局部变量引入方式 [ ]

[ ] :标识一个Lambda表达式的开始------不可省略

符号 说明
[ ] 局部变量捕获列表。Lambda表达式不能访问外部函数体的任何局部变量
[a] 在函数体内部使用值传递的方式访问a变量
[&b] 在函数体内部使用引用传递的方式访问b变量
[=] 函数外的所有局部变量都通过值传递的方式使用,函数体内使用的是副本
[&] 以引用的方式使用Lambda表达式外部的所有变量
[=, &foo] foo使用引用方式,其余是值传递的方式
[&, foo] foo使用值传递方式,其余引用传递
[this] 在函数内部可以使用类的成员函数和成员变量,=和&形式也都会默认引入

说明:

  • 由于使用引用方式捕获对象会有局部变量释放了而Lambda函数还没有被调用的情况。如果执行Lambda函数,那么引用传递方式捕获进来的局部变量的值不可预知。所以绝大多数场合使用的形式为:[=](){ }
  • 早期版本的Qt,若要使用Lambda表达式,要在".pro"文件中添加:CONFIG += C++11
    因为Lambda表达式是C++11标准提出的。Qt5以上的版本无需手动添加,在新建项目时会自动添加。

示例1:Lambda表达式的使用。

示例2:以[ ]方式传递,外部的所有变量在Lambda表达式中都可以使用。

示例3:以[a]方式传递,在Lambda表达式中只能使用传递进来的a。

2、函数参数 ()

(params) 表示Lambda函数对象接收的参数,类似于函数定义中的小括号表示函数接收的参数类型和个数。参数可以通过按值 (如:(inta,intb))和按引用 (如:(int&a,int&b))两种方式进行传递。 函数参数部分可以省略, 省略后相当于无参的函数。

示例:

3、选项Opt

Opt部分是可选项,最常用的是 mutable声明 ,这部分可以省略

Lambda表达式外部的局部变量通过值传递进来时,其默认是const ,所以不能修改这个局部变量的拷贝 ,加上mutable就可以修改。

4、Lambda表达式的返回值类型 ->

可以指定Lambda表达式返回值类型;如果不指定返回值类型,则编译器会根据代码实现为函数推导一个返回类型;如果没有返回值,则可忽略此部分。

示例1:

示例2:

5、Lambda表达式的函数体{}

Lambda表达式的函数体部分与普通函数体一致。用{}标识函数的实现,不能省略,但函数体可以为空。

示例:

6、槽函数使用Lambda表达式来实现

示例1:点击按钮关闭窗口。

示例2:当"connect"函数第三个参数为"this"时,第四个参数使用Lambda表达式时,可以省略掉"this"------

5.4 信号与槽的优缺点

5.4.1 优点:松散耦合

信号发送者不需要知道发出的信号被哪个对象的槽函数接收,槽函数也不需要知道哪些信号关联了自己,Qt的信号槽机制保证了信号与槽函数的调用。支持信号槽机制的类或者父类必须继承于QObject类。

5.4.2 缺点:效率较低

与回调函数相比,信号和槽稍微慢一些,因为它们提供了更高的灵活性,尽管在实际应用程序中差别不大。通过信号调用的槽函数比直接调用的速度慢约10倍(这是定位信号的接收对象所需的开销;遍历所有关联;编组/解组传递的参数;多线程时,信号可能需要排队),这种调用速度对性能要求不是非常高的场景是可以忽略的,是可以满足绝大部分场景。

一个客户端程序中,最慢的环节往往是"人"。

假设本身基于回调的方式是10us,使用信号槽的方式是100us.对于使用程序的人来说,是感知不到的。

5.5 小结

5.5.1 关于信号槽的两个补充知识点


6 ~> 信号与槽小结


结尾

uu们,本文的内容到这里就全部结束了,艾莉丝在这里再次感谢您的阅读!

结语:希望对学习QT相关内容的uu有所帮助,不要忘记给博主"一键四连"哦!

往期回顾

【QT】Qt 从零上手:Hello World、项目文件与实战避坑指南

🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡 ૮₍ ˶ ˊ ᴥ ˋ˶₎ა

相关推荐
轩情吖5 小时前
Qt的窗口(二)
开发语言·c++·qt·qdialog·对话框·桌面级开发
雨季6665 小时前
Flutter 三端应用实战:OpenHarmony “极简安全文本对齐调节器”
开发语言·前端·javascript·安全·flutter·交互
小苏兮5 小时前
【把Linux“聊”明白】命令行参数与环境变量
linux·运维·服务器·学习
草莓熊Lotso5 小时前
脉脉独家【AI创作者xAMA第二期】| 从拼图游戏到AI设计革命
android·开发语言·c++·人工智能·脉脉
高-老师5 小时前
基于R语言的贝叶斯网络模型的实践技术应用;R语言实现Bayesian Network分析的基本流程
开发语言·r语言·贝叶斯网络
中國龍在廣州6 小时前
AI时代“新BAT”正在崛起
大数据·人工智能·深度学习·重构·机器人
William_cl6 小时前
C# ASP.NET路由系统全解析:传统路由 vs 属性路由,避坑 + 实战一网打尽
开发语言·c#·asp.net
cuijiecheng20186 小时前
Linux下inih库的使用
linux·运维·服务器
丝瓜蛋汤6 小时前
unsloth 部署(简单易上手版本)
人工智能·深度学习