📑 目录
- 🔗 [1. 信号与槽的连接方式](#1. 信号与槽的连接方式)
- 🔹 [1.1 一对一](#1.1 一对一)
- 🔹 [1.2 一对多](#1.2 一对多)
- 🔹 [1.3 多对一](#1.3 多对一)
- 🧩 [5. 信号和槽的其他说明](#5. 信号和槽的其他说明)
- ✂️ [5.1 信号与槽的断开](#5.1 信号与槽的断开)
- 🕰️ [5.2 Qt4 版本信号与槽的连接](#5.2 Qt4 版本信号与槽的连接)
- 🚀 [5.3 使用 Lambda 表达式定义槽函数](#5.3 使用 Lambda 表达式定义槽函数)
- ⚖️ [5.4 信号与槽的优缺点](#5.4 信号与槽的优缺点)
- 🎓 [6. 小总结](#6. 小总结)
- 🎯 7. 经典面试题与解答(#7-经典面试题与解
🔗 1. 信号与槽的连接方式信号与槽的连接方式
📌 本章导读 :信号与槽的连接方式主要分为一对一、一对多、多对一三种。理解这些连接方式,是掌握 Qt 事件### 🔹 1.1 一对一## 1.1 一对一
🔗 概念 :一对一连接主要有两种形式,分别是:一个信号连接一个槽 和 一个信号连接一个信号。
(1)一个信号连接一个槽
💭 设计初衷:Qt 中信号和槽,connect 这个机制,设想是美好的?
1)解耦合
把触发用户操作的控件和处理对应用户的操作逻辑解耦合。
2)"多对多"效果
一个信号可以 connect 多个槽函数,一个槽函数可以被多个信号 connect。
📝 类比理解:设计数据库的表结构,就需要理清楚实体与实体之间的关系。Qt 引入信号和槽机制,最本质的目的(初心)是为了让信号和槽之间按照"多对多"的方式来进行关联。其他的 GUI 框架往往也不具备这样的特性。
⚠️ 实际经验:实际上,随着程序开发这件事,大家的经验越来越多。其实在 GUI 开发的过程中,"多对多"其实是一个伪需求。实际开发过程中很少会遇到,绝大多数场景下,一对一就够用了。
📋 示例:
1、在 widget.h 中声明信号和槽以及信号发射函数;

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

(2)一个信号连接另一个信号
🔄 说明:信号不仅可以连接槽函数,还可以连接另一个信号,实现信号的"接力传递"。
📋 示例:
在上述示例的基础上,在 widget.cpp 文件中添加如下代码:
!在这里插入图片描述(https://i-blog.csdnimg.cn/direct/162f4e9eeb314202a54ca### 🔹 1.2 一对多png)
1.2 一对多
- 一个信号连接多个槽
(1)在 widget.h 头文件中声明一个信号和三个槽;

(2)在 widget.cpp 文件中实现槽函数以及连接信号和槽;
!在这里插入图片描述(https://i-blog.csdnimg.cn/direct/da5364a43ab74faeb619c### 🔹 1.3 多对一ng)
1.3 多对一
🎯 概念:多个信号连接一个槽函数。无论哪个信号被触发,都会调用同一个槽函数来处理。
📋 示例:
(1)在 widget.h 头文件中声明两个信号以及一个槽;

(2)在 widget.cpp 文件中实现槽函数以及连接信号和槽;
!在这里插入图片描述(https://i-blog.csdnimg.cn/direct/4f4829d166594614861c98e9## 🧩 5. 信号和槽的其他说明-
5. 信号和槽的其他说明
📌 本章导读:除了基本的连接方式,信号与槽还有一些重要的补充知识点,包括断开连接、Qt4 旧版语法以及 Lambda 表达式等高级用法。
5.1 信号与槽的断开
✂️ 概念 :使用
disconnect即可完成断开。disconnect的用法和connect基本一致。
📋 示例:

💡 经验之谈 :
disconnect()实际上开发中使用非常少。
5.2 Qt4 版本信号与槽的连接
🕰️ 背景 :Qt4 中的
connect用法和 Qt5 相比是更复杂的。需要搭配SIGNAL和SLOT宏来完成。而且缺少必要的函数类型的检查,使代码更容易出错。
📋 示例:
(1)在 widget.h 头文件中声明信号和槽;

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

⚖️ Qt4 版本信号与槽连接的优缺点:
- ✅ 优点:参数直观;
- ❌ 缺点:参数类型不做检测;
示例:

5.3 使用 Lambda 表达式定义槽函数
🚀 概述 :Qt5 在 Qt4 的基础上提高了信号与槽的灵活性,允许使用任意函数作为槽函数。但如果想方便的编写槽函数,比如在编写函数时连函数名都不想定义,则可以通过 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 函数对象接收的参数,类似于函数定义中的小括号表示函数接收的参数类型和个数。参数可以通过按值(如:(int a, int b))和按引用(如:(int &a, int &b))两种方式进行传递。函数参数部分可以省略,省略后相当于无参的函数。
📋 示例:
cpp
#include "widget.h"
#include "ui_widget.h"
#include<QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
auto func = [](int x,int y){return x * y;};
int res = func(30,20);
qDebug()<<"res = "<<res;//600
}
Widget::~Widget()
{
delete ui;
}
3、选项 Opt
⚙️ 说明 :Opt 部分是可选项,最常用的是
mutable声明,这部分可以省略。Lambda 表达式外部的局部变量通过值传递进来时,其默认是const,所以不能修改这个局部变量的拷贝,加上mutable就可以修改。
cpp
#include "widget.h"
#include "ui_widget.h"
#include<QDebug>
#include<QPushButton>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QPushButton* btn1 = new QPushButton("btn1",this);
QPushButton* btn2 = new QPushButton("btn2",this);
//定义局部变量
int n = 100;
btn1->move(300,100);
btn2->move(500,100);
//添加mutable声明后,可以修改局部变量n的拷贝
connect(btn1,&QPushButton::clicked,this,[n]()mutable{n = 20;qDebug()<<n;});//输出20
//没有添加mutable声明,局部变量n的拷贝不能修改
connect(btn2,&QPushButton::clicked,this,[n](){qDebug()<<n;});//输出10
}
Widget::~Widget()
{
delete ui;
}
4、Lambda 表达式的返回值类型 ->
📤 说明:可以指定 Lambda 表达式返回值类型;如果不指定返回值类型,则编译器会根据代码实现为函数推导一个返回类型;如果没有返回值,则可忽略此部分。
📋 示例1:
cpp
#include "widget.h"
#include "ui_widget.h"
#include<QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
auto func = [](int x,int y)->int{return x * y;};
int res = func(30,20);
qDebug()<<"res = "<<res;//600
}
Widget::~Widget()
{
delete ui;
}
📋 示例2:
cpp
#include "widget.h"
#include "ui_widget.h"
#include<QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
auto func = [](int x,int y){return x * y;};
auto func1 = [](int x,int y)->int{return x * y;};
int res = func(30,20);
int res1 = func1(30,20);
qDebug()<<"res = "<<res;//600
qDebug()<<"res1 = "<<res;//600
}
Widget::~Widget()
{
delete ui;
}
5、Lambda 表达式的函数体 { }
📄 说明 :Lambda 表达式的函数体部分与普通函数体一致。用
{ }标识函数的实现,不能省略,但函数体可以为空。
📋 示例:
cpp
//Lambda表达式函数体为空
[](){};
6、槽函数使用 Lambda 表达式来实现
🎯 说明:后续如果我们对应的槽函数比较简单,而且是一次性使用的,就经常会写作这种 lambda 的形式。
📋 示例1:点击按钮关闭窗口
cpp
#include "widget.h"
#include "ui_widget.h"
#include<QDebug>
#include<QPushButton>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
resize(500,400);
QPushButton* btn = new QPushButton("关闭",this);
connect(btn,&QPushButton::clicked,this,[=](){
this->close();
});
}
Widget::~Widget()
{
delete ui;
}
📋 示例2:当 connect 函数第三个参数为 this 时,第四个参数使用 Lambda 表达式时,可以省略掉 this
cpp
#include "widget.h"
#include "ui_widget.h"
#include<QDebug>
#include<QPushButton>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
resize(500,400);
QPushButton* btn = new QPushButton("关闭",this);
connect(btn,&QPushButton::clicked,[=](){
this->close();
});
}
Widget::~Widget()
{
delete ui;
}
📌 理解:后续如果我们对应的槽函数比较简单,而且是一次性使用的,就经常会写作这种lambda的形式
5.4 信号与槽的优缺点
⚖️ 总结:信号与槽机制是 Qt 的核心特性,但它也有自身的优缺点。
✅ 优点:松散耦合
信号发送者不需要知道发出的信号被哪个对象的槽函数接收,槽函数也不需要知道哪些信号关联了自己,Qt 的信号槽机制保证了信号与槽函数的调用。支持信号槽机制的类或者父类必须继承于 QObject 类。
📝 补充理解:
- 高耦合:一个模块出问题,另一些模块会受到影响,导致不能正常运行。
- 低耦合:一个模块出问题,另一些模块不会受到影响,可以正常运行。
- 低内聚:写代码的时候,实现某个功能点,围绕这个功能的相关代码被放到整个项目的各个地方。
- 高内聚:写代码的时候,某个功能点的代码被集中放在一起了。
❌ 缺点:效率较低
与回调函数相比,信号和槽稍微慢一些,因为它们提供了更高的灵活性,尽管在实际应用程序中差别不大。通过信号调用的槽函数比直接调用的速度慢约 10 倍(这是定位信号的接收对象所需的开销;遍历所有关联;编组/解组传递的参数;多线程时,信号可能需要排队),这种调用速度对性能要求不是非常高的场景是可以忽略的,是可以满足绝大部分场景。
💡 深入理解:一个客户端程序中,最慢的环节往往是"人"。假设本身基于回调的方式是 10us,使用信号槽的方式是 100us。对于使用程序的人来说,是感知不到的。
6. 小总结
🎓 本章核心要点回顾:
- 连接方式:信号与槽支持一对一、一对多、多对一三种连接方式,满足不同场景下的解耦需求。
- 断开连接 :使用
disconnect可以断开信号与槽的关联,但实际开发中使用较少。 - Qt4 vs Qt5 :Qt4 使用
SIGNAL和SLOT宏,参数直观但缺少类型检查;Qt5 简化了语法,支持函数指针,更加安全灵活。 - Lambda 表达式:Qt5 支持使用 Lambda 表达式作为槽函数,特别适合简单、一次性使用的场景,代码更加简洁。
- 优缺点:信号与槽最大的优点是松散耦合,缺点是相比回调函数效率略低(约慢 10 倍),但对绝大多数应用场景可以忽略不计。
💡 自定义槽函数:自定义槽函数非常关键,开发中大部分情况都是需要自定义槽函数的。槽函数就是用户触发某个操作之后,要进行的业务逻辑。自定义信号比较少见,实际开发中很少需要自定义信号------信号就对应到用户的某个操作,在 GUI 中,用户能够进行某些操作是可以穷举的,Qt 中内置的信号基本能够覆盖掉上述所有可能的用户操作了。
7. 经典面试题与解答
🎯 以下整理了几道关于 Qt 信号与槽的经典面试题,帮助你在面试前快速复习。
面试题 1:请简述 Qt 中信号与槽的本质是什么?
✅ 参考答案:
信号的本质是事件。当用户对窗口或控件进行了某些操作(如按钮单击、双击、窗口刷新、鼠标移动、键盘输入等),导致窗口或控件产生了某个特定事件,这时 Qt 对应的窗口类会发出某个信号,以此对用户的操作做出反应。信号的呈现形式就是函数,某个事件产生了,Qt 框架就会调用某个对应的信号函数,通知使用者。
槽(Slot)就是对信号响应的函数。槽就是一个函数,与一般的 C++ 函数是一样的,可以定义在类的任何位置(public、protected 或 private),可以具有任何参数,可以被重载,也可以被直接调用(但是不能有默认参数)。槽函数与一般的函数不同的是:槽函数可以与一个信号关联,当信号被发射时,关联的槽函数被自动执行。
信号和槽机制底层是通过函数间的相互调用实现的。每个信号都可以用函数来表示,称为信号函数;每个槽也可以用函数表示,称为槽函数。
面试题 2:信号函数和槽函数有哪些书写规范?
✅ 参考答案:
自定义信号函数书写规范:
- 自定义信号函数必须写到
signals下; - 返回值为
void,只需要声明,不需要实现; - 可以有参数,也可以发生重载。
自定义槽函数书写规范:
- 早期的 Qt 版本要求槽函数必须写到
public slots下,但是现在高级版本的 Qt 允许写到类的public作用域中或者全局下; - 返回值为
void,需要声明,也需要实现; - 可以有参数,可以发生重载。
面试题 3:信号与槽的参数匹配规则是什么?
✅ 参考答案:
信号函数的参数列表要和对应连接的槽函数参数列表一致。此时信号触发,调用到槽函数的时候,信号函数中的实参就能够被传递到槽函数的形参当中。
具体规则是:信号的参数个数可以多于槽函数的参数个数,但是槽的参数个数不能多于信号参数个数。类型和个数要满足要求(信号的参数个数要多于槽的参数个数)。但是实际开发中最好还是保持参数个数也能匹配一致。
面试题 4:Qt4 和 Qt5 中信号与槽的连接有什么区别?
✅ 参考答案:
Qt4 中的 connect 用法和 Qt5 相比更复杂,需要搭配 SIGNAL 和 SLOT 宏来完成,而且缺少必要的函数类型的检查,使代码更容易出错。优点是参数直观,缺点是参数类型不做检测。
Qt5 之后,对上述的写法进行了简化,不再需要写 SIGNAL 和 SLOT 宏了,给 connect 提供了重载版本,重载版本中,第二个和第四个参数成了泛型参数,允许传入任意类型的函数指针,编译期会进行类型检查,更加安全。
面试题 5:信号与槽机制的优缺点是什么?
✅ 参考答案:
优点:松散耦合。 信号发送者不需要知道发出的信号被哪个对象的槽函数接收,槽函数也不需要知道哪些信号关联了自己,Qt 的信号槽机制保证了信号与槽函数的调用。支持信号槽机制的类或者父类必须继承于 QObject 类。
缺点:效率较低。 与回调函数相比,信号和槽稍微慢一些,因为它们提供了更高的灵活性。通过信号调用的槽函数比直接调用的速度慢约 10 倍(这是定位信号的接收对象所需的开销;遍历所有关联;编组/解组传递的参数;多线程时,信号可能需要排队),这种调用速度对性能要求不是非常高的场景是可以忽略的,是可以满足绝大部分场景。