当前位置: 首页 > news >正文

信号与槽的认识

目录

  • 1. 信号和槽概述
  • 2. 信号和槽的使用
    • 2.1 连接信号和槽
    • 2.2 查看内置信号和槽
    • 2.3 通过 Qt Creator 生成信号槽代码
  • 3. 自定义信号和槽
    • 3.1 基本语法
    • 3.2 带参数的信号和槽
  • 4. 信号与槽的连接方式
    • 4.1 一对一
    • 4.2 一对多
    • 4.3 多对一
  • 5. 信号和槽的其他说明
    • 5.1 信号与槽的断开
    • 5.2 Qt4 版本信号与槽的连接
    • 5.3 使用 Lambda 表达式定义槽函数
    • 5.4 信号与槽的优缺点

1. 信号和槽概述

(1)信号和槽机制解析:

  • 在 Qt 中,用户和控件的每次交互过程称为一个事件。例如:

    • “用户点击按钮” 是一个事件;
    • “用户关闭窗口” 也是一个事件。
  • 每个事件会发出一个信号:

    • 点击按钮会发出 “按钮被点击” 的信号;
    • 关闭窗口会发出 “窗口被关闭” 的信号。

(2)控件对信号的处理:

  • Qt 中的所有控件都具备接收信号的能力,一个控件可接收多个不同信号。对于接收到的每个信号,控件会做出相应的响应动作(也就是)。例如:
    1. 按钮所在的窗口接收到 “按钮被点击” 的信号后,可能执行 “关闭自己” 的槽函数。
    2. 输入框接收到 “输入框被点击” 的信号后,可能执行 “显示闪烁光标,等待用户输入” 的槽函数。

(3)信号与槽的核心作用:

  • 信号和槽是 Qt 特有的消息传输机制,用于关联相互独立的控件。例如:
    • 按钮” 和 “窗口” 本是独立控件,点击按钮默认不影响窗口。
    • 通过信号与槽机制,可将按钮的 “被点击” 信号与窗口的 “关闭” 槽绑定,实现 “点击按钮关闭窗口” 的效果。


(4)信号的本质:

  • 信号是由于用户对窗口或控件进行了某些操作(触发特定事件),对应的 Qt 窗口类会发出的通知。信号的本质是事件。例如:
    • 按钮单击、双击。
    • 窗口刷新。
    • 鼠标移动、按下、释放。
    • 键盘输入。
  • 信号的呈现形式:
    • 操作某个窗口或控件时,该对象会捕获触发的事件。
    • 事件触发后,Qt 框架会向开发者发出对应的特定信号。
    • 信号以函数形式呈现:事件发生时,Qt 框架调用对应的信号函数通知开发者。
    • 信号的发出者:实例化的类对象(如按钮、窗口等)。

(5)槽的本质:

  • 槽(Slot)是对信号响应的函数,本质为普通 C++ 函数,但具有以下特性:
    • 可定义在类的任何位置(public、protected 或 private)。
    • 可接受任意参数、支持重载,但不能有默认参数。
    • 可直接调用,但通常与信号关联,信号触发时自动执行。
  • 槽函数与⼀般的函数不同的是:槽函数可以与⼀个信号关联,当信号被发射时,关联的槽函数被自动执行。示例如下:
    • 信号:按钮被点击 → 信号函数 clicked()。
    • :窗口关闭 → 槽函数 close()。
    • 关联效果:clicked() 触发时调用 close(),实现“点击按钮关闭窗口”。

(6)关键总结:

  • 信号和槽机制底层是通过函数间的相互调用实现的。每个信号都可以用函数来表示,称为信号函数;每个槽也可以用函数表示,称为槽函数。例如: “按钮被按下” 这个信号可以用 clicked() 函数表示,“窗口关闭” 这个槽可以用 close() 函数表示,假如使用信号和槽机制实现:“点击按钮会关闭窗口” 的功能,其实就是 clicked() 函数调用 close() 函数的效果。
  • 信号函数和槽函数通常位于某个类中,和普通的成员函数相比,它们的特别之处在于:
    • 信号函数用 signals 关键字修饰,槽函数用 public slots、protected slots 或者 private slots 修饰。signalsslots 是 Qt 在 C++ 的基础上扩展的关键字,专门用来指明信号函数和槽函数;
    • 信号函数只需要声明,不需要定义(实现),而槽函数需要定义(实现)。

2. 信号和槽的使用

2.1 连接信号和槽

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

  • 关于 QObject:QObject 是 Qt 内置的父类。Qt 中提供的很多类都是直接或者间接继承自 QObject。这一点的设定和 Java 是非常相似的

(2)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)系统自带的信号和槽通常是通过 “Qt 帮助文档” 来查询。 如上述示例,要查询 “按钮” 的信号,在帮助文档中输入:QPushButton。

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

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

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

(1)Qt Creator 可以快速帮助我们生成信号槽相关的代码。代码示例:在窗口中设置⼀个按钮,当点击 “按钮” 时关闭 “窗口”。

  1. 新建项目,如下图为新建完成之后所包含的所有文件; 注意:创建时要生成 UI 设计文件;

  1. 双击 widget.ui 文件,进入 UI 设计界面;

  1. 在 UI 设计窗口中拖入⼀个 “按钮” ,并且修改 “按钮” 的名称及字体大小等;

  1. 可视化生成槽函数;

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


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

  1. 自动生成槽函数原型框架;
    • 在 “widget.h” 头文件中自动添加槽函数的声明;
    • 在 “widget.cpp” 中自动生成槽函数定义;
  • 首先自动添加槽函数的声明:

  • 说明:自动生成槽函数的名称有⼀定的规则。槽函数的命名规则为:on_XXX_SSS,其中:
    1. 以 " on " 开头,中间使用下划线连接起来;
    2. " XXX " 表示的是对象名(控件的 objectName 属性)。
    3. " SSS " 表示的是对应的信号。
  • 如:" on_pushButton_clicked() " ,pushButton 代表的是对象名,clicked 是对应的信号。
  • 按照这种命名风格定义的槽函数,就会被 Qt 自动的和对应的信号进行连接。
  • 但是咱们日常写代码的时候,除非是 IDE 自动生成,否则最好还是不要依赖命名规则,而是显式使用 connect 更好。
  • 一方面显式 connect 可以更清晰直观的描述信号和槽的连接关系。另一方面也防止信号或者槽的名字拼写错误导致连接失效。(当然,是配置大于约定,还是约定大于配置,哪种更好,这样的话题业界尚存在争议。此处个人还是更建议优先考虑显式 connect) 。
  • 最后就是自动生成槽函数定义:

  1. 在槽函数函数定义中添加要实现的功能。实现关闭窗口的效果:

3. 自定义信号和槽

3.1 基本语法

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

  1. 自定义信号函数书写规范:
    • 自定义信号函数必须写到 “signals” 下;
    • 返回值为 void,只需要声明,不需要实现;
    • 可以有参数,也可以发生重载;
  2. 自定义槽函数书写规范:
    • 早期的 Qt 版本要求槽函数必须写到 “public slots” 下,但是现在高级版本的 Qt 允许写到类的 “public” 作用域中或者全局下;
    • 返回值为 void,需要声明,也需要实现;
    • 可以有参数,可以发生重载;
  3. 发送信号:
    • 使用 “emit” 关键字发送信号 。“emit” 是⼀个空的宏。“emit” 其实是可选的,没有什么含义,只是为了提醒开发人员。

(2)示例1:

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

  1. 在 widget.cpp 中实现槽函数,并且关联信号和槽
    • 注意:图中的 ① 和 ② 的顺序不能颠倒。
    • 原因是:首先关联信号和槽,⼀旦检测到信号发射之后就会立马执行关联的槽函数。反之,若先发射信号,此时还没有关联槽函数,当信号发射之后槽函数不会响应。

(3)示例2:当⽼师说 “上课了”,学生们就 “回到座位,开始学习”。

  1. 在源文件中新建两个类,⼀个是老师类,⼀个是学生类;首先选中项目名称,鼠标右键 -----> “add new…”

  1. 点击 “add new…” 之后,出现如下界面:

  1. 选择 “choose” 出现如下界面:
    • 注意:在 Qt 中新建类时,要选择新建类的父类。
    • 显然,当前项目中还没啥类适合做新类的父类,同时新的类也不是⼀个 “窗口” 或者 “控件”。这种情况⼀般选择 QObject 作为基类。
    • 这样做的好处是这个新类的对象可以搭配 Qt 的对象树机制。便于对象的正确释放。

  1. 选择 “下⼀步”,出现如下界面:

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

  1. 在 teacher.h 中声明信号函数:

  1. 在 student.h 中声明槽函数:

  1. 在 widget.h 中实例化 “老师类对象” 和 “学生类对象”;

  1. 在 student.cpp 中实现槽函数:

  1. 在 widget.cpp 中连接自定义信号和槽;

  1. 运行结果如下图示:

(4)示例3:老师点击 “按钮” 触发学生上课;

  • 运行结果如下图示:

3.2 带参数的信号和槽

(1)Qt 的信号和槽也支持带有参数,同时也可以支持重载,此处我们要求,信号函数的参数列表要和对应连接的槽函数参数列表一致。此时信号触发,调用到槽函数的时候,信号函数中的实参就能够被传递到槽函数的形参当中。

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

(2)示例1:重载信号槽。

  • 在 “widget.h” 头文件中声明重载的信号函数以及重载的槽函数;如下图所示:

  • 在 “Widget.cpp” 文件实现重载槽函数以及连接信号和槽。注意:在定义函数指针时要指明函数指针的作用域。

  • 执行结果如下图所示:

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

  • 在 “widget.h” 头文件中声明信号和槽函数;

  • 在 “widget.cpp” 文件中实现槽函数以及连接信号和槽;

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

(4)示例3:

  • 在 “widget.h” 头文件中声明信号和槽函数;

  • 在 “widget.cpp” 文件中实现槽函数以及连接信号和槽;

4. 信号与槽的连接方式

4.1 一对一

(1)主要有两种形式,分别是:一个信号连接一个槽 和 一个信号连接一个信号。

(2)一个信号连接一个槽:

(3)示例:

  • 在 “widget.h” 中声明信号和槽以及信号发射函数;

  • 在 “widget.cpp” 中实现槽函数,信号发射函数以及连接信号和槽;

(4)一个信号连接另一个信号:

(5)示例:

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

4.2 一对多

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

(2)示例:

  • 在 “widget.h” 头文件中声明⼀个信号和三个槽;

  • 在 “widget.cpp” 文件中实现槽函数以及连接信号和槽;

4.3 多对一

(1)多个信号连接一个槽函数:

(2)示例:

  • 在 “widget.h” 头文件中声明两个信号以及一个槽;

  • 在 “widget.cpp” 文件中实现槽函数以及连接信号和槽;

5. 信号和槽的其他说明

5.1 信号与槽的断开

(1)使用 disconnect 即可完成断开。disconnect 的用法和 connect 基本一致。示例如下:

5.2 Qt4 版本信号与槽的连接

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

  • 在 “widget.h” 头文件中声明信号和槽;

  • 在 “widget.cpp” 文件中实现槽函数以及连接信号与槽;


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

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

(3)示例:

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

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

  • Lambda表达式 是 C++11 增加的特性。C++11 中的 Lambda表达式 用于定义并创建匿名的函数对象,以简化编程工作。
  • Lambda表达式 的语法格式如下:
[ capture ] ( params ) opt -> ret {Function body;
};
  • 说明:
参数名功能
capture捕获列表
params参数表
opt函数选项
ret返回值类型
Function body函数体

(2)局部变量引入方式: [ ]

  • [ ]:标识⼀个 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

(3)函数参数 ( )

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

(4)选项Opt:

  • Opt 部分是可选项,最常用的是 mutable声明 ,这部分可以省略。
  • Lambda表达式外部的局部变量通过值传递进来时,其默认是 const,所以不能修改这个局部变量的拷贝,加上mutable 就可以修改。

(5)Lambda表达式的返回值类型 ->

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

  • 示例2:

(6)Lambda表达式的函数体 { }

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

(7)槽函数使用Lambda表达式来实现:

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

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

5.4 信号与槽的优缺点

(1)优点:松散耦合。

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

(2)缺点:效率较低。

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

(3)一个客户端程序中,最慢的环节往往是 “人”。

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

相关文章:

  • 在统信UOS1060上进行全盘备份
  • 编写 Markdown 技术文档示例
  • 【计算机视觉】CV项目实战- SiamMask 单阶段分割跟踪器
  • MCP Java SDK 与 Spring AI 强强联手:简化 Java AI 开发流程
  • spark—kafka
  • Linux脏页相关参数
  • unity编辑器的json验证及格式化
  • 哪些心电图表现无缘事业编体检呢?
  • AIGC vs 人类创作者:是竞争还是协作?
  • Kafka 保证多分区的全局顺序性的设计方案和具体实现
  • Linux中使用tar命令打包并按指定日期格式命名文件
  • WebXR教学 05 项目3 太空飞船小游戏
  • 安装Jupyter Notebook 之不断报错 差点放弃版
  • 第一篇:Django简介
  • 【网络原理】TCP提升效率机制(一):滑动窗口
  • Linux内核编译全流程详解与实战指南
  • 【正则表达式】核心知识点全景解析
  • MySQL数据库精研之旅第十期:打造高效联合查询的实战宝典(一)
  • 基于SpringBoot的课程管理系统
  • linux与c语言基础知识(未全部完成)
  • 中国体育报:中国乒协新周期新起点再出发
  • 国产手术机器人+5G技术,上海医生同一天远程为五地患者开刀
  • 经济日报刊文谈外卖平台仍试图凭补贴制造超低价:苦练内功摆脱“内卷式”竞争
  • 古文启蒙佳作!锺叔河《念楼学短合集》出修订版
  • 李家超将率团访问浙江
  • 美伊就核问题在罗马开展第二轮间接谈判