二、信号与槽

目录

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

第二章 信号与槽

1. 信号和槽概述

在 Qt 中,用户和控件的每次交互都可以看作一个事件。例如点击按钮、关闭窗口、输入内容等,都会触发对应的事件。

事件发生后,Qt 会发出对应的信号;收到信号后执行的响应函数,就是槽函数。信号和槽可以把两个相互独立的对象关联起来,例如把"按钮点击"这个信号和"窗口关闭"这个槽函数连接起来。

信号的本质: 信号可以理解为事件发生后的通知。常见事件包括按钮单击、按钮双击、窗口刷新、鼠标移动、鼠标按下、鼠标释放、键盘输入等。

在代码层面,信号的呈现形式就是函数。某个事件发生后,Qt 框架会触发对应的信号函数。信号的发送者通常是某个已经实例化的 Qt 对象。

槽的本质: 槽(Slot)就是响应信号的函数。槽函数本质上仍然是 C++ 函数,可以定义在 publicprotectedprivate 区域,也可以有参数、重载或被直接调用。

说明: 信号和槽机制底层可以理解为函数之间的调用关系。例如按钮的点击信号可以用 clicked() 表示,窗口关闭槽函数可以用 close() 表示。连接之后,点击按钮就相当于触发 clicked(),再由它调用 close()

信号函数通常写在 signals 下,只需要声明,不需要手动实现;槽函数可以写在 public slotsprotected slotsprivate slots 或普通成员函数区域,需要自己实现。

Qt 会在编译前自动生成信号相关代码,这类机制可以理解为元编程(Meta Programming)。

2. 信号和槽的使用

2.1 连接信号和槽

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

关于 QObject: QObject 是 Qt 内置的基础父类,Qt 中很多类都直接或间接继承自 QObject

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。如果当前类中没有找到,就继续到父类中查找,例如 QPushButton 的父类 QAbstractButton

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

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

Qt Creator 可以快速生成信号槽相关代码。

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

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

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

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

4、可视化生成槽函数;

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

对于普通按钮来说,使用 clicked 信号即可;clicked(bool) 主要用于带状态的按钮,例如复选按钮。

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

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

自动生成槽函数的命名规则: on_XXX_SSS

XXX 表示控件的 objectName 属性,SSS 表示对应的信号。例如 on_pushButton_clicked() 中,pushButton 是对象名,clicked 是对应的信号。

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

实际开发中,除非是 IDE 自动生成代码,否则更推荐显式调用 connect()。这样能更清晰地描述信号和槽的连接关系,也能减少因为命名或拼写问题导致连接失效的情况。

(2)在 widget.cpp 中自动生成槽函数定义。

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

3. 自定义信号和槽

3.1 基本语法

Qt 允许自定义信号函数和槽函数,但书写方式需要遵守固定规则。

自定义信号函数:

1、自定义信号函数必须写在 signals 下。

2、返回值为 void,只需要声明,不需要实现。

3、可以带参数,也可以重载。

自定义槽函数:

1、早期 Qt 版本要求槽函数写在 public slots 下;较新的 Qt 版本也允许写在普通 public 作用域中,或者写成普通全局函数。

2、返回值通常为 void,需要声明,也需要实现。

3、可以带参数,也可以重载。

发送信号: 使用 emit 关键字发送信号。emit 本质上是一个空宏,可写可不写,主要用于提升代码可读性。

示例1:自定义信号和槽。

1、在 widget.h 中声明自定义信号和槽;

2、在 widget.cpp 中实现槽函数,并连接信号和槽。注意图中 ① 和 ② 的顺序不能颠倒:需要先建立连接,再发送信号;如果先发送信号,此时槽函数还没有关联,就不会触发响应。

示例2:自定义 Teacher 信号与 Student 槽函数。

1、在源文件中新建两个类:一个是 Teacher 类,一个是 Student 类。首先选中项目名称,鼠标右键选择 "add new..."。

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

选择 "choose" 后进入类配置界面。注意:在 Qt 中新建类时,需要选择新类的父类。

如果新类不是窗口或控件,也没有更合适的业务父类,可以选择 QObject 作为基类。这样新类对象可以配合 Qt 的对象树机制,便于对象释放。

选择"下一步",进入如下界面:

按照同样的方式添加 Student 类。添加完成后,项目目录新增文件如下:

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

在 student.h 中声明槽函数:

在 widget.h 中实例化 Teacher 对象和 Student 对象;

在 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 中实现槽函数,并连接信号和槽;

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 中实现槽函数,并连接信号和槽;

5. 信号和槽的其他说明

5.1 信号与槽的断开

使用 disconnect() 可以断开信号和槽的连接,基本用法和 connect() 类似。

示例:

5.2 Qt4 版本信号与槽的连接

Qt4 中的 connect() 写法比 Qt5 更复杂,需要配合 SIGNALSLOT 宏使用,而且缺少函数类型检查,代码更容易出错。

示例:Qt4 写法。

(1)在 widget.h 中声明信号和槽;

(2)在 widget.cpp 中实现槽函数,并连接信号与槽;

Qt4 写法的特点:

优点: 参数直观。

缺点: 参数类型不做检测,编译期不容易发现错误。

示例:

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

Qt5 提高了信号与槽的灵活性,允许使用任意函数作为槽函数。需要临时定义简单槽函数时,可以使用 Lambda 表达式来简化代码。

Lambda 表达式是 C++11 增加的特性,用于定义匿名函数对象,可以简化槽函数的编写。

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

cpp 复制代码
[capture](params) opt -> ret {
    Function body;
};
组成部分 说明
capture 捕获列表,用于指定外部变量的捕获方式。
params 参数列表,类似普通函数的参数。
opt 函数选项,常见的是 mutable
ret 返回值类型,可以显式指定,也可以由编译器推导。
Function body 函数体,即 Lambda 表达式具体执行的代码。

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

[] 是 Lambda 表达式的捕获列表,用来声明外部变量的捕获方式。

捕获方式 说明
[] 不捕获任何外部局部变量。
[a] 以值传递方式捕获变量 a
[&b] 以引用传递方式捕获变量 b
[=] 以值传递方式捕获外部所有局部变量,函数体中使用的是副本。
[&] 以引用方式捕获外部所有局部变量。
[=, &foo] foo 使用引用捕获,其余变量使用值捕获。
[&, foo] foo 使用值捕获,其余变量使用引用捕获。
[this] 捕获当前对象指针,可以在函数体中访问类的成员函数和成员变量。

说明: 使用引用捕获时要注意变量生命周期。如果 Lambda 执行时,被引用捕获的局部变量已经释放,就会产生不可预期的结果。实际开发中更常见的写法是 [=](){}

早期 Qt 版本如果要使用 Lambda 表达式,需要在 .pro 文件中添加:

qmake 复制代码
CONFIG += c++11

Qt 5 及以上版本通常会在新建项目时自动添加相关配置。

示例1:Lambda 表达式的使用

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

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

2、函数参数()

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

示例:

3、选项 Opt

Opt 部分是可选项,最常用的是 mutable 声明。Lambda 表达式通过值捕获外部局部变量时,默认不能修改这个副本;加上 mutable 后就可以修改。

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

可以显式指定 Lambda 表达式的返回值类型;如果不指定,编译器会根据函数体自动推导;如果没有返回值,可以省略这一部分。

示例1:

示例2:

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

Lambda 表达式的函数体和普通函数体类似,用 {} 表示具体实现。函数体不能省略,但可以为空。

示例:

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

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

示例2:当 connect() 的第三个参数为 this 时,第四个参数使用 Lambda 表达式时,可以省略 this

5.4 信号与槽的优缺点

优点: 松散耦合。

信号发送者不需要知道信号会被哪个对象的槽函数接收,槽函数也不需要关心自己关联了哪些信号。Qt 的信号槽机制会负责完成调用。需要注意的是,支持信号槽机制的类或父类必须继承自 QObject

缺点: 效率略低。

与直接回调相比,信号和槽会稍慢一些,因为它提供了更高的灵活性。额外开销主要来自查找接收对象、遍历连接关系、处理参数以及跨线程排队等过程。不过在大多数 GUI 开发场景中,这点开销通常可以忽略。

对于 GUI 程序来说,用户交互本身通常远慢于函数调用开销。即使信号槽比直接回调慢一些,在大多数界面开发场景中也不会成为性能瓶颈。

相关推荐
不会C语言的男孩2 小时前
C++ Primer 第3章:字符串、向量和数组
开发语言·c++
Dovis(誓平步青云)3 小时前
《QT学习第四篇:常见事件与UDP、TCP、文件系统、(锁、信号量、条件变量》
c语言·开发语言·汇编·qt
code monkey.3 小时前
【Linux之旅】Linux 应用层自定义协议与序列化:从粘包问题到网络计算器
linux·网络·c++
草莓熊Lotso3 小时前
【Linux网络】深入理解 HTTP 协议(二):从协议格式到手写工业级 HTTP 服务器
linux·运维·服务器·网络·c++·http
MC皮蛋侠客12 小时前
C++17 多线程系列(五):C++17 并行算法——从串行到并行的零成本迁移
c++·多线程
雪的季节14 小时前
企业级 Qt 全功能项目
开发语言·数据库·qt
郭涤生16 小时前
C++ 高性能编程最佳实践清单
开发语言·c++
.千余16 小时前
【C++】C++类与对象2:C++构造函数、运算符重载与流输入输出全面解析
c语言·开发语言·前端·c++·经验分享
郭涤生16 小时前
C++ 高性能状态机
开发语言·c++