- 关注博主,后期持续更新系列文章
- 如果有错误感谢请大家批评指出,及时修改
- 感谢大家点赞👍收藏⭐评论✍
详解Qt中的信号与槽
文章编号:Qt 学习笔记 / 12
一、信号和槽的基本概念
QT中的信号和槽是用于实现对象之间的通信的机制。每个对象都可以发出一个信号,其他对象可以通过连接到该信号的槽来接收并处理信号。
通过将信号和槽连接起来,可以实现对象之间的交互和通信。一个对象的信号可以连接到其他对象的槽,也可以将多个信号连接到同一个槽上。
Qt中可以使用connect函数,把一个信号和一个槽关联起来,后续只要信号触发,Qt就会自动执行槽函数。
信号源:由哪个控件发出信号
信号类型:用户进行不同的操作,就可能触发不同的信号
信号处理方式:槽(slot)== 函数
QT中的信号和槽是通过使用QObject类的特性来实现的,需要使用宏来声明信号和槽,并使用信号和槽的宏来进行连接。QT提供了一个QMetaObject系统来管理信号和槽的连接和调用。
使用信号和槽机制可以使代码更加灵活,模块化和可重用。它使得对象之间的交互变得简单而直观,并允许通过连接和断开连接来动态地改变交互方式。
1. 什么是信号
信号可以被认为是一个事件,当某些条件发生时,对象会发出一个信号。例如,鼠标点击、键盘输入或者是对象的状态改变都可以作为信号。
1.1 信号本质
信号是由于用户对窗⼝或控件进行了某些操作,导致窗⼝或控件产⽣了某个特定事件,这时 Qt 对
应的窗⼝类会发出某个信号,以此对⽤⼾的操作做出反应。
1.2 信号举例
信号的本质就是事件
- 按钮单击、双击
- 窗⼝刷新
- ⿏标移动、⿏标按下、⿏标释放
- 键盘输⼊
在 Qt 中信号是通过什么形式呈现给使用者的?
- 我们对哪个窗⼝进⾏操作, 哪个窗⼝就可以捕捉到这些被触发的事件。
- 对于使⽤者来说触发了⼀个事件我们就可以得到 Qt 框架给我们发出的某个特定信号。
- 信号的呈现形式就是函数, 也就是说某个事件产⽣了, Qt 框架就会调⽤某个对应的信号函数, 通知使⽤者。
2. 什么是槽
槽是接收信号的函数,当一个信号被发出时,连接到该信号的槽会被调用。槽可以执行任意代码,包括更新界面、处理数据等。
2.1 槽的本质
槽(Slot)就是对信号响应的函数。槽就是⼀个函数,与⼀般的 C++ 函数是⼀样的,可以定义在类的任何位置(public、protected 或 private),可以具有任何参数,可以被重载,也可以被直接调用(但是不能有默认参数)。槽函数与⼀般的函数不同的是:槽函数可以与⼀个信号关联,当信号被发射时,关联的槽函数被自动执行。
2.2 说明
(1)信号和槽机制底层是通过函数间的相互调⽤实现的。每个信号都可以⽤函数来表⽰,称为信号函数;每个槽也可以用函数表示,称为槽函数。
例如: “按钮被按下” 这个信号可以⽤ clicked() 函数表示,“窗⼝关闭” 这个槽可以用 close() 函数表示,假如使⽤信号和槽机制-实现:“点击按钮会关闭窗口” 的功能,其实就是 clicked() 函数调⽤ close() 函数的效果。
(2)信号函数和槽函数通常位于某个类中,和普通的成员函数相⽐,它们的特别之处在于:
- 信号函数⽤ signals 关键字修饰,槽函数⽤ public slots、protected slots 或者 private slots 修饰。signals 和 slots 是 Qt 在 C++ 的基础上扩展的关键字,专⻔⽤来指明信号函数和槽函数;
- 信号函数只需要声明,不需要定义(实现),而槽函数需要定义(实现)。
二、信号和槽如何使用
1. connect函数
1.1 函数原型
connect (const QObject * sender, //sender:信号的发送者 const char * signal , //signal:发送的信号(信号函数) const QObject * receiver , //receiver:信号的接收者 const char * method , //method:接收信号的槽函数 Qt::ConnectionType type = Qt::AutoConnection ) //目前阶段不用考虑,用的不多 //⽤于指定关联⽅式,默认的关联⽅式为 Qt::AutoConnection,通常不需要⼿动设定。
1.2 参数说明
• sender:信号的发送者;
• signal:发送的信号(信号函数);
• receiver:信号的接收者;
• method:接收信号的槽函数;
• type:用于指定关联方式,默认的关联⽅式为 Qt::AutoConnection,通常不需要手动设定。
1.3 代码示例
代码功能:在窗⼝中设置⼀个按钮,当点击 “按钮” 时关闭 “窗⼝”
使用connect进行信号和槽连接,实现点击按钮 关闭窗口的功能
示例代码:
#include "widget.h" #include "ui_widget.h" #include <QPushButton> Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); QPushButton* button = new QPushButton(this); button -> setText("按钮"); //设置按钮文本 button->resize(100,100); //设置按钮大小 button->move(200,200); //设置按钮位置 //连接信号和槽 实现点击按钮 关闭窗口的功能 connect(button,&QPushButton::clicked,this,&Widget::close); } Widget::~Widget() { delete ui; }
2. 如何查看内置信号和槽
在上述代码中使用到的信号和槽可以在“Qt帮助文档(Assistant)”中查看,下面介绍如何查看Qt内置的信号和槽,下面以查询“QPushButton(按钮)”的信号为例。
2.1 查询信号
- 打开Assistant,在索引栏中输入QPushButton
- 然后可以在 “Contents” 中寻找关键字 signals,如果没有请看下一步
- 如果没有找到, 继续去⽗类中查找.,点击父类QAbstractButton
- 在⽗类 QAbstractButton 中继续查找关键字signals
- 找到信号clicked()
2.1 查询槽
槽函数的寻找⽅式和信号⼀样,只不过它的关键字是 slot 。
3. 使用 Qt Creator 生成信号槽代码(图形化快速生成信号槽代码)
在Qt Creator中可以快速帮助我们⽣成信号槽相关的代码
3.1 实现步骤
代码示例如下:
代码功能:在窗⼝中设置⼀个按钮,当点击 “按钮” 时关闭 “窗⼝”
- 新建一个Qt项目,如下图为新建完成之后所包含的所有⽂件;
- 打开widget.ui文件,进入UI界面
- 在界面中放置一个按钮,并且修改按钮的大小及名称
- 右键按钮,选择转到槽,生成槽函数
- 选择信号clicked(),点击OK
- 自动生成槽函数原型框架
- 在 “widget.h” 头⽂件中⾃动添加槽函数的声明
说明:
⾃动⽣成槽函数的名称有⼀定的规则。槽函数的命名规则为:on_XXX_SSS,其中:
1. 以 " on " 开头,中间使用下划线连接起来;
2. " XXX " 表示的是对象名(控件的 objectName 属性)。
3. " SSS "表示的是对应的信号。
如:" on_pushButton_clicked() " ,pushButton 代表的是对象名,clicked是对应的信号。
- 在 “widget.cpp” 文件中⾃动⽣成槽函数定义.
- 在槽函数函数定义中添加要实现的功能. 实现关闭窗口的效果.
3.2 实现结果
点击按钮窗口关闭。
三、自定义信号和槽
1. 基础语法
在Qt中,自定义信号和槽可以通过使用signals和slots关键字来定义,在日常项目中用到较少。
在 Qt 中,允许⾃定义信号的发送⽅以及接收⽅,即可以⾃定义信号函数和槽函数。但是对于⾃定义的信号函数和槽函数有⼀定的书写规范
需要注意的是,为了使用自定义信号和槽,类必须包含Q_OBJECT宏,并且需要使用Qt的元对象编译系统(MOC)。这意味着类定义必须位于一个.h文件中,并且在CMake或QMake构建系统中添加适当的语句来使用MOC编译器。
1.1 自定义信号函数书写规范
- ⾃定义信号函数必须写到 “signals” 下;
- 返回值为 void,只需要声明,不需要实现;
- 可以有参数,也可以发⽣重载;
1.2 自定义槽函数书写规范
- 早期的 Qt 版本要求槽函数必须写到 “public slots” 下,但是现在⾼级版本的 Qt 允许写到类的"public" 作⽤域中或者全局下;
- 返回值为 void,需要声明,也需要实现;
- 可以有参数,可以发⽣重载;
1.3 发送信号
- 使⽤ “emit” 关键字发送信号 。“emit” 是⼀个空的宏。“emit” 其实是可选的,没有什么含义,只是为了提醒开发⼈员。
1.4 示例代码
- 在 widget.h 中声明⾃定义的信号和槽
2.在 widget.cpp 中实现槽函数,并且关联信号和槽
示例代码:
//widget.h #ifndef WIDGET_H #define WIDGET_H #include <QWidget> QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACE class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); signals: void MySignal();//信号声明 public: void MySlots();//槽函数声明 private slots: void on_pushButton_clicked(); private: Ui::Widget *ui; }; #endif // WIDGET_H
//widget.cpp #include "widget.h" #include "ui_widget.h" #include <QDebug> Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); connect(this,&Widget::MySignal,this,&Widget::MySlots); //emit MySignal(); } Widget::~Widget() { delete ui; } void Widget::MySlots() { this->setWindowTitle("修改窗口1"); } void Widget::on_pushButton_clicked() { emit MySignal(); }
2. 带参数的信号和槽
Qt 的信号和槽也可以带有参数, 同时也可以⽀持重载.
信号函数的参数列表要和对应连接的槽函数参数列表⼀致.
此时信号触发, 调⽤到槽函数的时候, 信号函数中的实参就能够被传递到槽函数的形参当中,就起到让信号给槽传参的效果
注意:为了使用自定义信号和槽,类必须包含Q_OBJECT宏,并且需要使用Qt的元对象编译系统(MOC)。这意味着类定义必须位于一个.h文件中,并且在CMake或QMake构建系统中添加适当的语句来使用MOC编译器。
2.1 示例代码
自定义信号和槽,参数必须一致,主要要求类型一致;个数如果不一致也可以,要求信号参数个数比槽参数个数多要多
注意:一个槽函数, 有可能会绑定多个信号,如果我们严格要求参数个数一致,就意味着信号绑定到槽的要求就变高了换而言之,当下这样的规则,就允许信号和槽之间的绑定更灵活了更多的信号可以绑定到这个槽函数上了
//widget.h #ifndef WIDGET_H #define WIDGET_H #include <QWidget> QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACE class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); signals: void MySignals(const QString& text1,const QString& text2); //此处text可以省略 public: void handleMySignal(const QString& text); //此处text可以省略 private slots: void on_pushButton_clicked(); //UI自动生成的槽函数 void on_pushButton_2_clicked(); //UI自动生成的槽函数 private: Ui::Widget *ui; }; #endif // WIDGET_H
//widget.cpp #include "widget.h" #include "ui_widget.h" #include<QDebug> Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); connect(this,&Widget::MySignals,this,&Widget::handleMySignal); } Widget::~Widget() { delete ui; } void Widget::handleMySignal(const QString& text) { qDebug()<<text; this->setWindowTitle(text); } void Widget::on_pushButton_clicked() { emit MySignals("按钮1修改参数",""); } void Widget::on_pushButton_2_clicked() { emit MySignals("按钮2修改参数",""); }
四、信号与槽的连接方式
1. 一对一
分为两种形式:⼀个信号连接⼀个槽 和 ⼀个信号连接⼀个信号。
1.1 信号连接槽
- 在 “widget.h” 中声明信号和槽
- 在 “widget.cpp” 中实现槽函数,信号发射函数以及连接信号和槽
1.2 信号连接信号
- 在 “widget.cpp” ⽂件中添加如下代码:
2. 一对多
- 在 “widget.h” 头⽂件中声明⼀个信号和三个槽
- 在 “widget.cpp” ⽂件中实现槽函数以及连接信号和槽;
3. 多对一
- 在 “widget.h” 头⽂件中声明两个信号以及⼀个槽
- 在 “widget.cpp” ⽂件中实现槽函数以及连接信号和槽
五、信号与槽的优缺点
1. 优点: 松散耦合
信号发送者不需要知道发出的信号被哪个对象的槽函数接收,槽函数也不需要知道哪些信号关联了自己,Qt的信号槽机制保证了信号与槽函数的调⽤。⽀持信号槽机制的类或者⽗类必须继承QObject类。
2. 缺点: 效率较低
与回调函数相⽐,信号和槽稍微慢⼀些,因为它们提供了更⾼的灵活性,尽管在实际应⽤程序中差别不⼤。通过信号调⽤的槽函数⽐直接调⽤的速度慢约10倍(这是定位信号的接收对象所需的开销;遍历所有关联;编组/解组传递的参数;多线程时,信号可能需要排队),这种调⽤速度对性能要求不是⾮常⾼的场景是可以忽略的,是可以满⾜绝⼤部分场景。