Qt开发③Qt的信号和槽_概念+使用+自定义信号和槽+连接方式

ops/2025/2/20 23:11:00/

目录

1. 信号和槽概述

1.1 事件和控件

1.2 信号的本质

1.3 槽的本质

2. 信号和槽的使用

2.1 connect 连接信号和槽

2.2 查看内置信号和槽

2.3 Qt Creator 生成信号槽代码

3. 自定义信号和槽

3.1 不带参数的信号和槽

3.2 带参数的信号和槽

4. 信号与槽的连接方式

4.1 一对一

4.2 一对多

4.3 多对一

4.4 多对多

5. 信号与槽的断开

6. Qt4 版本信号与槽的连接

7. Lambda 定义槽函数

7.1 局部变量引入方式 [ ] 

7.2 函数参数 ( )

7.3 选项 Opt

7.4 返回值类型 ->

7.5 函数体 { }

7.6 槽函数使用 Lambda

8. 信号与槽的优缺点

本篇完。


1. 信号和槽概述

1.1 事件和控件

        在 Qt 中,用户和控件的每次交互过程称为一个事件。比如 “用户点击按钮” 是一个事件,“用户关闭窗口” 也是一个事件。每个事件都会发出一个信号,例如用户点击按钮会发出 “按钮被点击” 的信号,用户关闭窗口会发出 “窗口被关闭” 的信号。

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

        信号和槽是 Qt 特有的消息传输机制,可以通过 connect 这样的函数,把一个信号和一个槽这种相互独立的控件关联起来。比如,“按钮” 和 “窗口” 本身是两个独立的控件,点击 “按钮” 并不会对 “窗口” 造成任何影响。通过信号和槽机制,可以将 “按钮” 和 “窗口” 关联起来,实现 “点击按钮会使窗口关闭” 的效果。


1.2 信号的本质

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

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

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

  • 我们对哪个窗口进行操作, 哪个窗口就可以捕捉到这些被触发的事件。
  • 对于使用者来说触发了一个事件,我们就可以得到 Qt 框架给我们发出的某个特定信号。

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

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


1.3 槽的本质

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

        槽函数与一般的函数不同的是:槽函数可以与一个信号关联,当信号被发射时,关联的槽函数被自动执行。

所谓的 “槽函数” 本质上也是一种 “回调函数”(callback)。

C 语言阶段 - 函数指针:

  • 实现转移表,降低代码的 “圈复杂度”
  • 实现回调函数效果(qsort)

C++ 阶段:

  • STL 中,函数对象 / 仿函数
  • lambda 表达式

Linux 阶段:

  • 信号处理函数
  • 线程的入口函数
  • epoll 基于回调的机制

信号和槽机制底层是通过函数间的相互调用实现的
        每个信号都可以用函数来表示,称为信号函数;每个槽也可以用函数表示,称为槽函数。

        例如:“按钮被按下” 这个信号可以用 clicked() 函数表示,“窗口关闭” 这个槽可以用 close() 函数表示,假如使用信号和槽机制 - 实现:“点击按钮会关闭窗口” 的功能,其实就是 clicked() 函数调用 close() 函数的效果。

信号函数和槽函数通常位于某个类中

和普通的成员函数相比,它们的特别之处:

  • 信号函数用 signals 关键字修饰,槽函数用 public slots、protected slots 或者 private slots 修饰。signals 和 slots 是 Qt 在 C++ 的基础上扩展的关键字,专门用来指明信号函数和槽函数。
  • 信号函数只需要声明,不需要定义(实现),而槽函数需要定义(实现)。

        信号函数的定义是 Qt 自动在编译程序之前生成的,我们编写 Qt 应用程序无需关注这个,这种自动生成代码的机制称为 “ 元编程 ”(Meta Programming),这种操作在很多场景中都能见到。


2. 信号和槽的使用

2.1 connect 连接信号和槽

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

关于 QObject:

        QObject 是 Qt 内置的父类,Qt 中提供的很多类都是直接或者间接继承自 QObject。(在 Java 中也存在相似的设定)

connect() 函数原型:

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 查看内置信号和槽

打开帮助文档有三种方式,实际编程中使用哪种都可以。

(1)光标放到要查询的类名 / 方法名上,直接按 F1


(2)Qt Creator 左侧边栏中直接用鼠标单击 “帮助” 按钮。


(3)找到 Qt Creator 的安装路径,在 "bin" 文件夹下找到 assistant.exe,双击打开


        系统自带的信号和槽通常是通过 “Qt 帮助文档” 来查询的。如上述示例,要查询 “按钮” 的信号,在帮助文档中输入:QPushButton

        首先可以在 "Contents" 中寻找关键字 signals,如果没有找到,继续去父类中查找,因此我们去他的父类 QAbstractButton 中继续查找关键字 signals

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


2.3 Qt Creator 生成信号槽代码

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

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

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

注意:创建时要生成 UI 设计文件。

(2)双击 widget.ui 文件,进入 UI 设计界面


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


(4)可视化生成槽函数

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

这个窗口列出了 QPushButton 给我们提供的所有信号(还包含了 QPushButton 父类的信号)。

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


(5)自动生成槽函数原型框架

发现在 "widget.h" 头文件中自动添加了槽函数的声明

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

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

        如:"on_pushButton_clicked() ",pushButton 代表的是对象名,clicked 是对应的信号。按照这种命名风格定义的槽函数,就会被 Qt 自动的和对应的信号进行连接。但是我们日常写代码的时候,除非是 IDE 自动生成,否则最好还是不要去依赖命名规则,而是显式使用 connect 更好。

  • 一方面,显式 connect 可以更清晰直观的描述信号和槽的连接关系。
  • 另一方面,也是防止信号或者槽的名字拼写错误导致连接失效。

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


发现在 "widget.cpp" 中自动生成槽函数定义  


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


3. 自定义信号和槽

3.1 不带参数的信号和槽

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

        Qt 中如果要让某个类能够使用信号槽(可以在类中定义信号和槽函数),则必须要在类最开始的地方写下 Q_OBJECT 宏(这个宏可以展开很多代码)。如果不加上这个宏,那么编译时就会报错。这个宏不需要我们自己手动添加,一般 Qt Creator 会帮我们自动生成。


自定义信号函数书写规范:

自定义信号在实际开发中很少需要用到,Qt 内置的信号就足以应付大部分的开发场景了。

        所谓 Qt 的信号,本质上也就是一个 “函数”,在 Qt 5 以及更高版本中,槽函数和普通的成员函数之间没有什么差别。但是,信号是一类非常特殊的函数,我们只要写出函数声明,并告诉 Qt 这是一个 “信号” 即可。这个函数的定义是 Qt 在编译过程中自动生成的。

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

        此处的 signals 是 Qt 自己扩展的关键字。qmake 构建 Qt 项目时,就会调用一些代码的分析 / 生成工具,扫描到类中包含 signals 这个关键字时,就会自动的把下面的函数声明认为是信号,并且给这些信号函数自动的生成函数定义。


自定义槽函数书写规范:

所谓的自定义一个槽函数,其操作规程和自定义一个普通成员函数没有什么区别。

  1. 早期的 Qt 版本要求槽函数必须写到 "public slots" 下,但是现在高级版本的 Qt 不做要求。
  2. 返回值为 void,需要声明,也需要实现。
  3. 可以有参数,可以发生重载。

槽声明的两种写法: 


        此处的 slots 是 Qt 自己扩展的关键字,不是 C++ 标准中的语法。qmake 构建 Qt 项目时,就会调用专门的扫描器,扫描代码中特定的关键字(比如:slot),基于关键字自动生成一大堆相关代码。

发送信号 :

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


使用示例:

在 widget.h 中声明自定义的信号和槽


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

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

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

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


3.2 带参数的信号和槽

        Qt 的信号和槽也支持带有参数,同时也可以支持重载。要求:信号函数的参数列表要和对应连接的槽函数参数列表一致。(一致主要是要求类型,个数如果不一致也可以,但要求信号的参数个数比槽的参数个数多

        此时信号触发,调用到槽函数的时候,信号函数中的实参就能够被传递到槽函数的形参当中。通过这样的机制就可以让信号给槽传递数据了。

(1)重载信号槽示例

在 "widget.h" 头文件中声明重载的信号函数以及重载的槽函数

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

执行结果:


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

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

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

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


(3)信号的参数个数多于槽函数的参数个数

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

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


4. 信号与槽的连接方式

4.1 一对一

主要有两种形式,分别是:

  1. ⼀个信号连接⼀个槽
  2. ⼀个信号连接⼀个信号

一个信号连接一个槽

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

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


一个信号连接另一个信号

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


4.2 一对多

一个信号连接多个槽:

在 "widget.h" 头文件中声明一个信号和三个槽:


4.3 多对一

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

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


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


4.4 多对多

多个信号连接多个槽函数:

在 "widget.h" 头文件中声明三个信号以及三个槽:

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

        实际上,随着程序员经验越来越多,在 GUI 开发的过程中,“多对多” 其实是个 “伪需求”,实际开发很少会用到,绝大部分情况来说,“一对一” 就够用了。

信号槽存在的意义:

  • 解耦合(写代码追求 “高内聚,低耦合”)
  • 多对多(非常类似于 MySQL 中的 “多对多”)

5. 信号与槽的断开

使用 disconnect 即可完成断开,disconnect 的用法和 connect 基本一致。

        实际上大部分情况下,把信号和槽连接上之后就不必管了,所以 disconnect 使用的比较少。主动断开往往是把信号重新绑定到另一个槽函数上。


6. Qt4 版本信号与槽的连接

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

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

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


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

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

7. Lambda 定义槽函数

        Qt5 在 Qt4 的基础上提高了信号与槽的灵活性,允许使用任意函数作为槽函数。但如果想方便的编写槽函数,比如在编写函数时连函数名都不想定义,则可以通过 Lambda 表达式来达到这个目的。

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

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

[ capture ] ( params ) opt -> ret { Function body;
};

说明:


7.1 局部变量引入方式 [ ] 

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

说明:

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

Lambda 表达式的使用:

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

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

        Lambda 表达式除了可以按照值得方式来捕获变量 [=],还可以按照引用得方式来捕获 [&](但 Qt 中很少这样写),捕获到的变量一般就是各自控件得指针。指针变量按照值传递或者引用来传递都无所谓。如果选择按照引用来传递,还得更关注这个引用得变量本身的生命周期。


7.2 函数参数 ( )

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


7.3 选项 Opt

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

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


7.4 返回值类型 ->

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


7.5 函数体 { }

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


7.6 槽函数使用 Lambda

点击按钮关闭窗口:


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


8. 信号与槽的优缺点

优点:松散耦合

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


缺点:效率较低

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

        一个客户端程序中,最慢的环节往往是 “人”。假设本身基于回调的方式是 10us,使用信号槽的方式是 100us。对于使用程序的人来说,是感知不到的。


本篇完。

下一篇:Qt开发④Qt常用控件_上_QWdget属性+按钮类控件。


http://www.ppmy.cn/ops/159699.html

相关文章

DeepSeek在学术读写翻译中的独特优势

上下文理解能力 DeepSeek的核心优势之一在于其卓越的上下文理解能力。它能够根据前文内容准确理解和回应用户的提问或指令,确保对话的连贯性和相关性。这一能力在处理长篇对话和复杂文本时尤为重要,能够帮助用户更好地把握整体逻辑和细节。 2. 翻译专业…

SOME/IP--协议英文原文讲解7

前言 SOME/IP协议越来越多的用于汽车电子行业中,关于协议详细完全的中文资料却没有,所以我将结合工作经验并对照英文原版协议做一系列的文章。基本分三大块: 1. SOME/IP协议讲解 2. SOME/IP-SD协议讲解 3. python/C举例调试讲解 4.1.5 De-…

什么是平面环形无影光源

平面环形无影光源是一种特殊设计的光源,主要用于消除阴影,提供均匀照明,常见于摄影、显微镜、工业检测等领域。以下是其关键特点和应用: 关键特点 环形设计:光源呈环形,光线从四周均匀照射,减少…

MME-CoT:专为评估大型多模态模型CoT推理能力的基准测试。涵盖了数学、科学、OCR、逻辑、时空和一般场景6个领域。

2025-02-09 ,由CUHK MMLab、CUHK MulLab、字节跳动、、东北大学等机构联合发布MME-CoT数据集,该数据集目的评估大型多模态模型(LMMs)中的思维链(CoT)推理能力,涵盖数学、科学、OCR、逻辑、时空和…

功能测试-黑盒测试

黑盒测试是一种功能测试方法,它将软件视为一个“黑盒”,即测试人员不关心软件的内部结构和实现,细节只关注软件的输入和输出是否符合预期。以下是黑盒测试方法的详细解释: 1. 黑盒测试的核心理念 黑盒测试的核心在于验证软件的功…

【DeepSeek-R1】满血版免费网页端使用(不卡顿,支持联网搜索)

DeepSeek-R1-671B 平台汇总 序号平台名称网址特点使用建议1DeepSeek 官方chat.deepseek.com提供R1和V3满血版,支持联网功能,但高峰期可能卡顿上午使用较流畅,下午和晚上可能卡顿,建议错峰使用2腾讯元宝yuanbao.tencent.com完整版…

Flutter 状态管理库-----GetX(一)

Flutter 状态管理库-----GetX(一) 一、GetX特点 状态管理: GetX 提供了非常简洁的 API 来管理 Flutter 中的状态。它可以通过 GetX() 或 Obx() 来监听和更新界面。通过响应式编程,GetX 可以在数据变化时自动更新 UI。它避免了传统的 Flutter 状态管理方…

Nat. Genet | 单细胞多组回归模型识别功能和疾病相关增强子,并实现染色质潜力分析

Nat. Genet | 单细胞多组回归模型识别功能和疾病相关增强子,并实现染色质潜力分析 本文提出了一种名为SCARlink的基因调控模型,通过结合单细胞RNA测序(scRNA-seq)和单细胞开放染色质测序(scATAC-seq)数据&…