[Qt的学习日常]--信号和槽

前言

作者:小蜗牛向前冲

名言:我可以接受失败,但我不能接受放弃

  如果觉的博主的文章还不错的话,还请点赞,收藏,关注????支持博主。如果发现有问题的地方欢迎❀大家在评论区指正

本期学习:什么是信号和槽,自定义槽函数和信号函数,信号和槽的传参,断开,对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 或其子类(如 QWidgetQMainWindow 等),这样类才能支持信号和槽。

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_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    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_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    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倍(这是定位信号的接收对象所需的开销;遍历所有关联;编组/解组传递的参数;多线程时,信号可能需要排队),这种调⽤速度对性能要求不是 ⾮常⾼的场景是可以忽略的,是可以满⾜绝⼤部分场景。

上一篇:[C++][数据结构]二叉搜索树:介绍和实现


下一篇:深度学习+计算机视觉