【Qt】信号和槽机制

目录

一、认识信号和槽

二、connect函数

三、自定义槽函数

四、自定义信号

五、带参数的信号和槽

六、信号和槽断开连接

七、信号和槽存在的意义

八、Lambda表达式定义槽函数


一、认识信号和槽

概述

在Qt中,用户和控件的每次交互过程称为一个事件。如"用户点击按钮"是一个事件,"用户关闭窗口"也是一个事件。每个事件都会发出一个信号,如用户点击按钮会发出"按钮被点击"的信号,用户关闭窗口会发出"窗口被关闭"的信号

Qt中的所有控件都具有接收信号的能力,⼀个控件还可以接收多个不同的信号。对于接收到的每个信号,控件都会做出相应的响应动作。如:按钮所在的窗口接收到"按钮被点击"的信号后,会做出"关闭自己"的响应动作;再如:输入框自己接收到"输入框被点击"的信号后,会做出"显示闪烁的光标,等待用户输入数据"的响应动作。在Qt中,对信号做出的响应动作就称之为槽

信号和槽是Qt特有的消息传输机制,它能将相互独立的控件关联起来。如:"按钮"和"窗口"本身是两个独立的控件,点击"按钮"并不会对"窗口"造成任何影响。通过信号和槽机制,可以将"按钮"和"窗口"关联起来,实现"点击按钮会使窗口关闭"的效果

信号的本质

信号是由于用户对窗口或控件进行了某些操作,导致窗口或控件产生了某个特定事件,这时Qt对应的窗口类会发出某个信号,以此对用户的操作做出反应。因此,信号的本质就是事件

槽的本质

槽(Slot)就是对信号响应的函数。槽就是⼀个函数,与一般的C++函数是一样的,可以定义在类的任何位置(public、protected或private),可以具有任何参数,可以被重载,也可以被直接调用(但是不能有默认参数)。槽函数与一般的函数不同的是:槽函数可以与信号关联,当信号被发射时,关联的槽函数被自动执行

说明

  • 信号和槽机制底层是通过函数间的相互调用实现的。每个信号都可以用函数来表示,即信号函数;每个槽也可以用函数表示,即槽函数
  • 信号函数和槽函数通常位于某个类中,和普通的成员函数相比,它们的特别之处在于:

信号函数用signals关键字修饰,槽函数用public slots、protected slots或者private slots修饰(使用普通成员函数的方式修饰也可)。signals和slots是Qt在C++的基础上扩展的关键字,专门用来指明信号函数和槽函数;信号函数只需要声明,不需要定义(实现),而槽函数需要定义(实现)

Q_OBJECT

若一个类要使用信号和槽机制,必须在类中添加Q_OBJECT这个宏

二、connect函数

在Qt中,QObject类提供了一个静态成员函数connect(),该函数专门用来关联指定的信号函数和槽

函数。QObject是Qt内置的父类,Qt中提供的很多类都是直接或者间接继承自QObject

Qt Assistant中connect函数原型:

cpp 复制代码
//旧版
connect (const QObject *sender,
    const char * signal ,
    const QObject * receiver ,
    const char * method ,
    Qt::ConnectionType type = Qt::AutoConnection )
  • sender:信号的发送者
  • signal:发送的信号(信号函数)
  • receiver:信号的接收者
  • method:接收信号的槽函数
  • type:用于指定关联方式,默认的关联方式为Qt::AutoConnection,通常不需要手动设定

但是C++不允许使用两个不同的指针类型相互赋值,使用const char*明显是不行的。因为Qt Assistant中的函数声明,是以前旧版本的Qt的connect函数的声明

在以前的版本中,给信号参数传参需要要搭配一个SIGNAL宏,给槽参数传参需要搭配一个SLOT宏。从Qt5开始,对上述写法进行了简化,给connect重载版本,第二个参数和第四个参数成了泛型函数,允许传入任意参数了

此时connect函数就带有了一定的参数检查的功能,若传入的第一个参数和第二个参数,或者第三个参数和第四个参数不匹配,代码出现编译错误。不匹配是指:2、4参数传入的函数指针,不是1、3参数的成员函数的指针

connect函数使用案例:点击按钮关闭窗口

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent), ui(new Ui::Widget)
{
    ui->setupUi(this);
    QPushButton* btn = new QPushButton("按钮", this);
    btn->move(200, 200);
    connect(btn, &QPushButton::clicked, this, &QWidget::close);
}

Widget::~Widget()
{
    delete ui;
}

三、自定义槽函数

  • 早期的Qt版本要求槽函数必须写到"public slots"下,但是现在高级版本的Qt允许写到类的"public"作用域中或者全局下
  • 返回值为void,需要声明,也需要实现
  • 可以有参数,可以发生重载

代码编写槽函数

widget.h:

cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QPushButton>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
    void HandleClicked();//槽函数声明

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H

widget.cpp

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QPushButton* btn = new QPushButton("按钮", this);
    btn->move(200, 200);
    connect(btn, &QPushButton::clicked, this, &Widget::HandleClicked);
}

Widget::~Widget()
{
    delete ui;
}

//槽函数定义
void Widget::HandleClicked()
{
    setWindowTitle("按钮已按下");
}

ui创建槽函数

自动生成的槽函数的名字是on_pushButton_clicked,其中on是固定的,pushButton是ui中的objectName,clicked写明了是哪种信号。ui自动生成的槽函数不需要connect函数就能在触发信号时被回调。(ui_widget.h中调用了QMetaObject::connectSlotsByName,它会触发自动连接信号槽的规则)

四、自定义信号

自定义信号很少用到。因为在GUI中,用户的操作行为是可以穷举的,Qt内置的信号已经覆盖到了大部分可能的用户操作

  • 信号是一种特殊的函数,程序员只需写出函数声明,并告诉Qt,这是一个信号即可。这个函数的定义,是Qt在编译过程中,自动生成的(无法干预)
  • 信号函数的返回值必须是void,有没有参数都可以,也支持函数重载
  • 信号可以使用emit关键字进行发射(Qt5 emit不写也行)

五、带参数的信号和槽

信号和槽也可以带参数。发射信号时,就可以给信号函数传递实参,这个参数就会被传递到对应的槽函数中。信号和槽函数的参数类型必须一致,个数可以不一致,但是信号的参数个数必须大于槽函数的参数个数(当个数不一致时,就会按顺序拿到信号的前N个参数)

一个信号可以通过connect关联多个槽函数,一个槽函数也能被多个信号关联

六、信号和槽断开连接

使用disconnect断开信号槽的连接(主动断开往往是把信号重新绑定到另一个槽函数上)

widget.cpp:

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    btn = new QPushButton("按钮", this);
    btn->move(200, 200);
    connect(btn, &QPushButton::clicked, this, &Widget::HandleClicked_1);

    QPushButton* changeBtn = new QPushButton("修改按钮功能", this);
    changeBtn->move(200, 400);
    connect(changeBtn, &QPushButton::clicked, this, &Widget::ChangeButtonRole);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::HandleClicked_1() { setWindowTitle("修改窗口标题1"); }

void Widget::HandleClicked_2() { setWindowTitle("修改窗口标题2"); }

void Widget::ChangeButtonRole()
{
    disconnect(btn, &QPushButton::clicked, this, &Widget::HandleClicked_1);
    connect(btn, &QPushButton::clicked, this, &Widget::HandleClicked_2);
    qDebug() << "修改成功";
}

widget.h:

cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QPushButton>
#include <QDebug>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

    void HandleClicked_1();
    void HandleClicked_2();
    void ChangeButtonRole();

private:
    Ui::Widget *ui;
    QPushButton* btn;
};
#endif // WIDGET_H

若这里没有disconnect,会使一个信号绑定两个槽函数,触发点击按钮,同时执行两个槽函数

七、信号和槽存在的意义

  • **解耦合:**信号发送者不需要知道发出的信号被哪个对象的槽函数接收,槽函数也不需要知道哪些信号关联了自己,Qt的信号槽机制保证了信号与槽函数的调用。支持信号槽机制的类或者父类必须继承于QObject类
  • **实现"多对多"的效果:**一个信号可以connect到多个槽函数上,一个槽函数也可以被多个信号connect(实际开发中,这种情况极少)

缺点

与回调函数相比,信号和槽稍微慢⼀些,因为它们提供了更高的灵活性,尽管在实际应用程序中差别不大。通过信号调用的槽函数比直接调用的速度慢约10倍(这是定位信号的接收对象所需的开销;遍历所有关联;编组/解组传递的参数;多线程时,信号可能需要排队),这种调用速度对性能要求不是非常高的场景是可以忽略的,是可以满足绝大部分场景

八、Lambda表达式定义槽函数

  • 注意被捕获变量的生命周期
  • 尽量传值捕获,传引用捕获可能会捕获到已经被释放的变量,造成程序崩溃
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent), ui(new Ui::Widget)
{
    ui->setupUi(this);
    setFixedSize(1000, 1000);

    QPushButton* button = new QPushButton("点击移动", this);
    button->move(400, 800);
    connect(button, &QPushButton::clicked, this, [=](){
        qDebug() << "Lambda";
        button->move(800, 800);
    });
}

Widget::~Widget()
{
    delete ui;
}

上述代码传值捕获没问题,传引用捕获会崩溃。原因是button是局部变量(它指向的空间位于堆区,但其本身是一个局部变量的指针),构造函数结束后button变量即被销毁,造成程序崩溃

相关推荐
Qter_Sean4 小时前
自己动手写Qt Creator插件
开发语言·qt
何曾参静谧4 小时前
「QT」文件类 之 QIODevice 输入输出设备类
开发语言·qt
yyqzjw11 小时前
【qt】控件篇(Enable|geometry)
开发语言·qt
csdn_kike11 小时前
QT Unknown module(s) in QT 以及maintenance tool的更详细用法(qt6.6.0)
开发语言·qt
西西弗Sisyphus11 小时前
Qt 获取当前系统中连接的所有USB设备的信息 lsusb版
qt
kaixin_learn_qt_ing14 小时前
Qt---双缓冲绘图
qt
西西弗Sisyphus18 小时前
Qt 监控USB设备的插入和移除
qt
怀澈12218 小时前
QT之QString常用函数
开发语言·qt
zanglengyu19 小时前
RK3568硬解码并与Qt界面融合显示深入探究
开发语言·qt·ffmpeg·rk3568硬解码rtsp
doll ~CJ20 小时前
定时器(QTimer)与随机数生成器(QRandomGenerator)的应用实践——Qt(C++)
c++·qt·计时器与随机数生成器运用·图片循环播放