目录
1. 带参数的信号和槽
重载信号槽
参数列表匹配规则
信号参数多于槽的情况
2. 信号与槽的连接方式
⭕ 信号槽 的意义
3. 信号和槽的其他说明
1. 信号与槽的断开
2. Qt4 版本信号与槽的连接
Qt4 优缺点
3.Lambda 定义槽函数
语法格式
槽函数使用 Lambda
信号与槽的优缺点
补充
1.对于回调函数
2.对于 linux 中 信号的处理
3.C++多态的实现
4.模板 偏特化
qthb-toc" style="margin-left:0px;">小结
1. 信号槽是什么
2. 信号槽使用 connect
3. 如何查阅文档
4. 自定义槽函数
5. 自定义信号
6. 信号和槽还可以带有参数
7. 信号槽存在的意义
8. disconnect 使用方式
9. lambda 表达式简化槽函数的定义
前文:
[Qt] 信号和槽(1) | 本质 | 使用 | 自定义
1. 带参数的信号和槽
Qt 的信号和槽不仅支持无参,还支持带有参数的形式,并且可以重载。这意味着你可以有多个同名但参数列表不同的信号或槽。
- 信号与槽参数一致性:当连接带参数的信号和槽时,要求信号函数的参数列表类型要与槽函数相匹配。
- 如果个数不一致,则信号的参数个数必须多于或等于槽的参数个数,这样信号触发时能够传递实参给槽函数的形参。
重载信号槽
- 在
widget.h
头文件中声明重载的信号和槽。
- 在
Widget.cpp
文件中实现这些重载成员并建立它们之间的连接。
- 执行后观察结果以确保连接正确。
参数列表匹配规则
- 声明:在
widget.h
中定义信号和槽的原型。
- 实现与连接:在
widget.cpp
中具体实现槽的功能逻辑,并进行信号到槽的连接。
注意,虽然允许信号参数多于槽参数,最佳实践是保持两者参数数量一致,以避免潜在问题。
信号参数多于槽的情况
- 声明:同样在
widget.h
中完成。 - 实现与连接:在
widget.cpp
中处理,只使用信号的一部分参数。
2. 信号与槽的连接方式
1. 一对一
- 一个信号连接一个槽
-
- 在
widget.h
中声明信号、槽及信号发射函数。 - 在
widget.cpp
实现上述成员并连接信号与槽。
- 在
- 一个信号连接另一个信号
-
- 在
widget.cpp
中添加额外代码来实现这种连接。
- 在
2. 一对多
- 一个信号可以连接多个槽,在
widget.h
和widget.cpp
中分别声明和实现。
.h
.cpp
3. 多对一
- 多个信号可以连接同一个槽,同样需要在
widget.h
和widget.cpp
中操作。 - 类比上面的
4. 多对多
- 理论上可以实现多个信号连接多个槽,但实际上这很少见,因为“一对一”已经能满足大多数需求。
在 "widget.h" 头文件中声明三个信号以及三个槽:
在 "widget.cpp" 文件中实现槽函数以及连接信号和槽:
实际上,随着程序员经验越来越多,在 GUI 开发的过程中,“多对多” 其实是个 “伪需求”,实际开发很少会用到,绝大部分情况来说,“一对一” 就够用了。
⭕ 信号槽 的意义
- 解耦合(写代码追求 “高内聚,低耦合”)
- 多对多(非常类似于 MySQL 中的 “多对多”)
3. 信号和槽的其他说明
1. 信号与槽的断开
- 可以通过
disconnect
函数来断开信号与槽的连接,其用法与connect
类似。 - 在大多数情况下,一旦信号和槽被正确连接后就不再需要管理它们,因此
disconnect
的使用频率较低。 - 主动断开通常是 为了解绑信号并将其重新绑定到另一个槽函数上。
2. Qt4 版本信号与槽的连接
- 在 Qt4 中,
connect
需要配合SIGNAL
和SLOT
宏使用,相比 Qt5 更加复杂,并且缺少对函数类型的检查,这可能导致代码更容易出错。
Qt4 优缺点
- 优点:参数直观
- 缺点:不进行参数类型检测
Qt 5 开始,对上述写法做了简化,不再需要写 SIGNAL 和 SLOT 宏了
- 简化:Qt 5 的
connect
提供了重载版本。重载版本中,第二个参数和第四个参数成了泛型参数,允许传入任意类型的函数指针。
泛型参数的使用
template <typename Func1, typename Func2>
static inline QMetaObject::Connection connect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender,Func1 signal,const typename QtPrivate::FunctionPointer<Func2>::Object *receiver,Func2 slot,Qt::ConnectionType type = Qt::AutoConnection)
参数检查功能
connect
函数 使用模板 带有一定的参数检查功能。- 如果你传入的第一个参数和第二个参数不匹配,或者第三个参数和第四个参数不匹配(例如,2、4 参数的函数指针,不是 1、3 参数的成员函数),此时代码编译出错。
示例代码
// 连接信号到槽函数
QObject::connect(sender, &Sender::signal, receiver, &Receiver::slot);
- Qt 封装的类型萃取器 使得泛型编程更加灵活。
- 通过模板参数,可以处理不同类型的信号和槽函数。
3.Lambda 定义槽函数
之前 写的这篇博文中 有专门讲到
[C++11#46](三) 详解lambda | 可变参数模板 | emplace_back | 默认的移动构造
- Qt5 引入了更大的灵活性,允许使用任意函数作为槽,包括通过 C++11 的 Lambda 表达式来简化槽函数的编写,无需命名函数。
- Lambda 表达式提供了一种简洁的方式来创建匿名函数对象,增强了代码的表达力。
语法格式
[ capture ] ( params ) opt -> ret { Function body;
};
- 捕获列表 [capture]:标识 Lambda 表达式的开始。它决定了外部变量如何被捕获,通常使用
[=]
按值捕获所有外部变量,或[&]
按引用捕获(较少见),也可以指定捕获特定变量[a]
。 - 参数列表 (params):类似于普通函数定义中的参数列表,可以为空。
- 返回类型 -> ret:可选部分,用于指定 Lambda 表达式的返回类型。
- 函数体:包含 Lambda 表达式的执行逻辑。
详解
局部变量引入方式 [ ]
标识:[ ]
标识一个 Lambda 表达式的开始,不可省略。
说明:
- 使用引用方式捕获对象时,若局部变量在 Lambda 函数调用前被释放,则会导致不可预知的行为。
- 对于早期版本的 Qt,需要在
.pro
文件中添加CONFIG += C++11
来启用 Lambda 表达式支持;Qt5 及以上版本默认支持,无需手动配置。
使用
- 按值捕获所有变量:以
[=]
方式传递,Lambda 表达式可以访问所有外部变量。 - 仅捕获特定变量 a:以
[a]
方式传递,Lambda 表达式只能使用指定的a
变量。 - 按引用捕获变量 [&]:虽然可以在 Qt 中这样写,但较少见。一般捕获的是控件指针,对于这些指针来说,按值或按引用传递区别不大。选择按引用来传递时,需注意引用变量的生命周期。
函数参数 ( )
(params)
定义了 Lambda 函数接收的参数类型和个数,类似于普通函数定义中的小括号部分。- 参数可以通过按值(如:
(int a, int b)
)或按引用(如:(int &a, int &b)
)的方式传递。 - 函数参数部分可以省略,省略后相当于无参函数。
选项 Opt
Opt 部分是可选项,最常用的是 mutable 声明,这部分可以省略。
Lambda 表达式外部的局部变量通过值传递进来时,其默认是 const,所以不能修改这个局部变量的拷贝,加上 mutable 就可以修改。
返回值类型 ->
可以显式指定 Lambda 表达式的返回值类型。如果不指定,则编译器会根据实现推导出返回类型。如果 Lambda 表达式没有返回值,这部分可以忽略。
函数体 { }
{ }
标识 Lambda 表达式的实现部分,不能省略,但函数体本身可以为空。
槽函数使用 Lambda
- 示例 A:点击按钮关闭窗口。
- 简化表达:当
connect
函数第三个参数为this
时,第四个参数可以直接使用 Lambda 表达式而省略掉this
。
信号与槽的优缺点
优点:
松散耦合。
- 发送者和接收者不需要互相了解对方,增加了灵活性。
- 所有支持信号槽机制的类或其父类必须继承自
QObject
类。
缺点:
效率较低。
- 相较于直接回调,信号槽机制稍微慢一些,因为提供了更多的灵活性。尽管实际应用中这种差异通常很小,对性能要求不是非常高的场景是可以接受的。
- 对于用户而言,这样的延迟几乎不可感知。
补充
1.对于回调函数
15.C与指针--超全总结
之前 写的这篇文章中有提到
回调函数
- 定义:把函数A的指针作为参数传递给另一函数B—实现B(A),A是被调用函数,就是回调函数
使用场景:
- 模拟实现计算器
- Qsort
2.对于 linux 中 信号的处理
[Linux][OS][详解信号的产生]
3.C++多态的实现
[C++#28][多态] 两个条件 | 虚函数表 | 抽象类 | override 和 final | 重载 重写 重定义
这样就实现出了不同对象去完成同一行为时,展现出不同的形态
⭕多态的两个条件:
- 虚函数覆盖:基类的虚函数被派生类重写。
- 对象的指针或引用调用虚函数:通过基类的指针或引用来调用派生类中重写的虚函数。
4.模板 偏特化
Qt 5 开始,对写法做了简化,不再需要写 SIGNAL 和 SLOT 宏了
- Qt 5 的
connect
函数简化了信号槽机制的使用。 - 通过泛型参数,提高了代码的灵活性和可读性。
- 参数检查功能确保了信号和槽函数的正确连接。
类型萃取器
- 为了从成员函数指针中提取出对象类型信息,Qt 使用了一种称为“类型萃取器”的工具。
- 这种工具通常是通过模板特化实现的,它可以识别并处理不同类型的成员函数指针。
- 例如,
QtPrivate::FunctionPointer<Func1>::Object
就是一个用于获取成员函数所属对象类型的模板表达式,它依赖于模板偏特化来为不同类型的成员函数提供正确的实现。
12.C++模板进阶 | 代码膨胀
qthb">小结
1. 信号槽是什么
- 信号源:触发信号的对象。
- 信号的类型:信号可以是各种事件,如按钮点击、窗口关闭等。
- 信号的处理方式:通过连接信号到槽函数来处理这些事件。
2. 信号槽使用 connect
- 使用
connect
函数将信号与槽函数连接起来。
3. 如何查阅文档
- 查阅一个控件内置了哪些信号和槽,以及它们的作用。
- 可能需要查询该类的父类/爷爷类/祖宗类以获取更多信息。
4. 自定义槽函数
- 实质上就是自定义一个普通的成员函数。
- 可以让 Qt Creator 自动生成(虽然没有显式 connect,但可以通过函数名字特定规则来完成自动连接)。
5. 自定义信号
- 这种情况 比较少
- 信号本质就是成员函数(函数的定义是 Qt 自己生成的,只需要写函数声明)。
- 在
signals:
自定义关键字中定义信号。 - 使用
emit
来完成信号的发射(emit
也可以省略)。
6. 信号和槽还可以带有参数
- 发射信号时,把参数传递给对应的槽。
- 信号的参数和槽的参数要一致:
-
- 类型匹配
- 个数,信号的参数要多于槽的参数。
7. 信号槽存在的意义
- 多对多效率(非常类似于 MySQL 中的多对多)。(伪命题)
- “高内聚,低耦合”。
8. disconnect
使用方式
- 使用
disconnect
断开信号与槽的连接。
9. lambda 表达式简化槽函数的定义
像 Qt 这样的框架,里面涉及到很多机制,都是和之前学到过的一些 编程语言/数据结构/操作系统/网络/数据库 基础知识有关联关系的,例如上面的 补充内容