前言
作者:小蜗牛向前冲
名言:我可以接受失败,但我不能接受放弃
如果觉的博主的文章还不错的话,还请点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正
本期学习:什么是信号和槽,自定义槽函数和信号函数,信号和槽的传参,断开,对lambda表达式的使用
目录
一、信号和槽
1、信号和槽的理解
2、信号的本质
3、槽的本质
二、信号和槽的使用
1、 connect()函数
2、自定义槽函数二种实现方法
3、自定义信号
4、信号和槽的带参传递
三、 信号和槽的其他说明
1、信号与槽的断开
2、使⽤ Lambda 表达式定义槽函数
3、信号与槽的优缺点
一、信号和槽
1、信号和槽的理解
在Qt中用户和控件的每一次交互都称为一个事件,⽐如 "⽤户点击按钮" 是⼀个事件,"⽤户关闭窗⼝" 也是⼀个事件。每个事件都回发送一个信号,当用户点击按钮,就会发送按钮被点击的信号。
Qt中所有的控件都具备接收信号的能力,一个控件可以接受多种信号,对每种信号都会做出相应的响应动作。例如,按钮所在的窗⼝接收到 "按钮被点击" 的信号后,会做 出 "关闭⾃⼰" 的响应动作;在Qt中,对信号做出响应动作就称为槽。
信号和槽是 Qt 特有的消息传输机制,它能将相互独⽴的控件关联起来。⽐如,"按钮" 和 "窗⼝" 本⾝是两个独⽴的控件,点击 "按钮" 并不会对 "窗⼝" 造成任何影响。通过信号和槽机制,可以将 "按 钮" 和 "窗⼝" 关联起来,实现 "点击按钮会使窗⼝关闭" 的效果。
2、信号的本质
信号的本质就是事件。
如:按钮单击、双击 • 窗⼝刷新 • ⿏标移动、⿏标按下、⿏标释放 • 键盘输⼊
信号的呈现形式就是函数, 也就是说某个事件产⽣了, Qt 框架就会调⽤某个对应的信号函数, 通 知使⽤者。
3、槽的本质
槽(Slot)就是对信号响应的函数。槽就是⼀个函数,与⼀般的 C++ 函数是⼀样的,可以定义在 类的任何位置( public、protected 或 private ),可以具有任何参数,可以被重载,也可以被直接调 ⽤(但是不能有默认参数)。槽函数与⼀般的函数不同的是:槽函数可以与⼀个信号关联,当信号被 发射时,关联的槽函数被⾃动执⾏
注意:
(1)信号和槽机制底层是通过函数间的相互
调⽤实现的。每个信号都可以⽤函数来表⽰,称为信号函数;每个槽也可以⽤函数表⽰,称为槽函数。
例如: "按钮被按下" 这个信号可以⽤ clicked() 函数表 ⽰,"窗⼝关闭" 这个槽可以⽤ close() 函数表⽰,假如使⽤信号和槽机制实现:"点击按钮会关闭窗⼝" 的功能,其实就是 clicked() 函数调⽤ close() 函数的效果。
(2)信号函数和槽函数通常位于某个类中,和普通的成员函数相⽐,它们的特别之处在于:
信号函数⽤ signals 关键字修饰,槽函数⽤ public slots、protected slots 或者 private slots 修 饰。signals 和 slots 是 Qt 在 C++ 的基础上扩展的关键字,专⻔⽤来指明信号函数和槽函数; 信号函数只需要声明,不需要定义(实现),⽽槽函数需要定义(实现)。
二、信号和槽的使用
1、 connect()函数
在 Qt 中,QObject 类提供了⼀个静态成员函数 connect() ,该函数专⻔⽤来关联指定的信号函数和槽函数。
connect() 函数原型:
connect(const QObject* sender,//信号的发送者(哪个控件) const char* signal,//发送信息的函数 const QObject* receiver,//信号的接受者 const char* method,//接受信号的槽函数 Qt::ConnectionType type = Qt::AutoConnection);//type: ⽤于指定关联⽅式,默认的关联⽅式为 Qt::AutoConnection,通常不需要⼿动设定。
代码⽰例: 在窗⼝中设置⼀个按钮,当点击 "按钮" 时关闭 "窗⼝" .:
大家看到这里可能会有疑惑,为什么我们传递的参数类型没有疑惑是对的, 我们明明是要给第一个参数(信号的发送者)传给QObject类的,怎么就变成了传QPushButton类呢?
这是因为在QT中QObject是所有类继承的基类,通过层层的继承关系,QPushButton也继承了QObject,根据继承关系这里也可以相当与QObject使用,这里我们理解了参数1和参数3.
那为什么参数2和参数4明明传递的是一个函数指针,怎么能和char*类型的指针匹配。
其实最初的Qt为了能给信号参数和槽参数传递参数,是要给这二个参数搭配二个厷
SIGBNAL和SLOT厷。
connect(but,SIGNAL(&QPushButton::licked),this,SLOT(&Widget::close));
但是从Qt5开始就对是上面的内容进行了简单化,给connect提供了重载版本,其实参数2和参数4变成了泛形编程,就可以允许传递任意类型的函数指针。
QMetaObject::Connection QObject::connect(const QObject *sender,PointerToMemberFunction signal,const QObject *receiver, PointerToMemberFunction slot,Qt::ConnectionType type = Qt::AutoConnection);
2、自定义槽函数二种实现方法
基本语法:
1. 包含Q_OBJECT宏
在你的类定义中,必须包含
Q_OBJECT
宏。这是因为Q_OBJECT
宏允许类使用Qt的元对象系统,包括信号和槽的机制。2. 类继承
确保你的类继承自
QObject
或其子类(如QWidget
、QMainWindow
等),这样类才能支持信号和槽。3. 声明槽函数
在类声明中,槽函数可以在
public slots:
、protected slots:
或private slots:
部分声明,这取决于你希望槽函数的访问级别。4. 连接信号和槽
使用
QObject::connect
方法将信号连接到槽。
这里我们要完成在窗⼝中设置⼀个按钮,当点击 "按钮" 时改变窗口的名称"。
方法1:代码实现:
这里我们要在widget.h头文件中,对槽函数进行声明
在widget.cpp中进行对函数的编写,这里我们让如果开关被按下就对窗口进行显示更改为“按钮已经被按下"。
方法2: 通过图形化界面
这里会自动帮我们生成一个函数
函数名的说明
这里我们发送,我们并没有借助connect函数将信号和槽链接起来,但是其实Qt会通过函数名字自动连接。
3、自定义信号
其实Qt中内置的信号,基本就够覆盖用户的所以操作了。我们知道,信号的本质其实就是函数,也就是说如果我能要进行自定义信号,就需要自定义信号函数
在widget.h中,我们完成了对信号的定义
在widget.cpp中调用connect完成对信号和槽的连接
4、信号和槽的带参传递
Qt 的信号和槽也⽀持带有参数, 同时也可以⽀持重载. 此处我们要求, 信号函数的参数列表要和对应连接的槽函数参数列表⼀致. 此时信号触发, 调⽤到槽函数的时候, 信号函数中的实参就能够被传递到槽函数的形参当中。
在 "widget.h" 头⽂件中声明重载的信号函数以及重载的槽函数
#ifndef WIDGET_H#define WIDGET_H#include <QWidget>QT_BEGIN_NAMESPACEnamespace Ui { class Widget; }QT_END_NAMESPACEclass Widget : public QWidget{ Q_OBJECTpublic: Widget(QWidget *parent = nullptr); ~Widget(); signals: void MySignal(const QString& text); public: void MYStols(const QString& text);private slots: void on_pushButton_clicked();private: Ui::Widget *ui;};#endif // WIDGET_H
在 "Widget.cpp" ⽂件实现重载槽函数以及连接信号和槽。
#include "widget.h"#include "ui_widget.h"Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget){ ui->setupUi(this); connect(this,&Widget::MySignal,this,&Widget::MYStols);}Widget::~Widget(){ delete ui;}void Widget::MYStols(const QString & text){ this->setWindowTitle(text);}void Widget::on_pushButton_clicked(){ //发出自定义的信号 //这里我们就可以通过信号,把我们想要传递的参数给槽的回调函数 emit MySignal("信号发送");}
点击允许程序,在点击按钮输出就可以改变窗口名称
对于这种带参传递的作用最大的好处就是可以进行进行代码复用,因为信号和槽的关系,可以是一对一,一对多的,多对多关系。也就是说,我们可以有多个信号,但我点击不同信号的时候,在同一槽中处理(回调函数),只是不同信号,传递给槽函数的参数不同,从而达到代码复用。
是我们要注意:信号传递给槽函数的参数,可以多但是不能少。
多了就会用槽能接受几个就用几个但是少了一定是会报错误的。
三、 信号和槽的其他说明
1、信号与槽的断开
使⽤ disconnect 即可完成断开. disconnect 的⽤法和 connect 基本⼀致
在 "widget.h" 头⽂件中声明重载的信号函数以及重载的槽函数
#ifndef WIDGET_H#define WIDGET_H#include <QWidget>QT_BEGIN_NAMESPACEnamespace Ui { class Widget; }QT_END_NAMESPACEclass Widget : public QWidget{ Q_OBJECTpublic: Widget(QWidget *parent = nullptr); ~Widget(); public: void MySltos1(); void MySltos2();private slots: void on_pushButton_2_clicked();private: Ui::Widget *ui;};#endif // WIDGET_H
在 "Widget.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); connect(ui->pushButton,&QPushButton::clicked,this,&Widget::MySltos1);}Widget::~Widget(){ delete ui;}void Widget::MySltos1(){ this->setWindowTitle("修改窗口1"); qDebug()<<"MySltos1修改";}void Widget::MySltos2(){ this->setWindowTitle("修改窗口2"); qDebug()<<"MySltos2修改";}void Widget::on_pushButton_2_clicked(){ //首先断开连接 disconnect(ui->pushButton,&QPushButton::clicked,this,&Widget::MySltos1); //然后重新绑定连接 connect(ui->pushButton,&QPushButton::clicked,this,&Widget::MySltos2);}
这里我们允许程序,点击修改,首先是窗口名称变成 修改窗口1
当我们切换修改就变为,在点击修改就变为修改窗口2 。
注意如果没有用disconnect 断开连接,在点击切换后,会调用二个槽函数
2、使⽤ Lambda 表达式定义槽函数
但如果想⽅便的编写槽函数,⽐如在编写函数时连函数名都不想定义,则可以通过 Lambda表达式 来 达到这个⽬的。 Lambda表达式 是 C++11 增加的特性。C++11 中的 Lambda表达式 ⽤于定义并创建匿名的函数对 象,以简化编程⼯作。
对于lambda表达式这里进行简单介绍:
Lambda表达式 的语法格式如下:
[ capture ] ( params ) opt -> ret { Function body; };
capture 捕获列表params 参数表opt 函数选项ret 返回值类型Function body 函数
局部变量引⼊⽅式 [ ]
符号 | 说明 |
[ ] | 局部变量捕获列表。Lambda表达式不能访问外部函数体的任何局部变量 |
[a] | 在函数体内部使⽤值传递的⽅式访问a变量 |
[&b] | 在函数体内部使⽤引⽤传递的⽅式访问b变量 |
[=] | 函数外的所有局部变量都通过值传递的⽅式使⽤, 函数体内使⽤的是副本 |
[&] | 以引⽤的⽅式使⽤Lambda表达式外部的所有变量 |
[=, &foo] | foo使⽤引⽤⽅式, 其余是值传递的⽅式 |
[&, foo] | foo使⽤值传递⽅式,其余引⽤传递 |
[this] | 在函数内部可以使⽤类的成员函数和成员变量,= 和 & 形式也都会默认引⼊ |
运用实例:
3、信号与槽的优缺点
优点: 松散耦合
信号发送者不需要知道发出的信号被哪个对象的槽函数接收,槽函数也不需要知道哪些信号关联了⾃⼰,Qt的信号槽机制保证了信号与槽函数的调⽤。⽀持信号槽机制的类或者⽗类必须继承于 QObject 类
缺点: 效率较低
与回调函数相⽐,信号和槽稍微慢⼀些,因为它们提供了更⾼的灵活性,尽管在实际应⽤程序中差别不大。通过信号调⽤的槽函数⽐直接调⽤的速度慢约10倍(这是定位信号的接收对象所需的开销;遍历所有关联;编组/解组传递的参数;多线程时,信号可能需要排队),这种调⽤速度对性能要求不是 ⾮常⾼的场景是可以忽略的,是可以满⾜绝⼤部分场景。