【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函数原型:

//旧版
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函数使用案例:点击按钮关闭窗口

#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: 

#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

#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:

#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:

#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表达式定义槽函数

  • 注意被捕获变量的生命周期
  • 尽量传值捕获,传引用捕获可能会捕获到已经被释放的变量,造成程序崩溃
#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变量即被销毁,造成程序崩溃

  • 23
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Qt信号机制是其最重要的特性之一,它提供了一种灵活而高效的通信方式。信号可以在不同对象之间进行通信,使得这些对象能够相互响应和交互。 信号Qt中定义的一种特殊的函数,用于表示某个事件的发生。一个类可以定义一个或多个信号信号可以在特定的情况下被触发,比如用户点击了一个按钮或者其他的操作发生了一些特定的事件。信号的声明通常位于类的头文件中。 是一个特殊的成员函数,用于接收信号并对其进行响应。一个类可以定义一个或多个函数,用于处理不同的信号函数可以有任意的参数和返回值,但是需要与信号的参数列表和返回值类型匹配。函数的声明通常位于类的头文件或者源文件中。 信号通过Qt的元对象系统来进行连接。当信号被触发时,与之相关联的函数会被调用。信号之间的连接可以通过Qt提供的connect函数来实现,也可以在Qt Creator中通过可视化界面来进行连接。 信号之间的连接是动态的,可以在运行时进行创建、修改和断开。这种机制使得对象之间的通信变得非常灵活,能够很好地支持Qt的事件驱动编程模型。 总结起来,Qt信号机制是一种通过信号函数来实现对象间通信的灵活机制信号用于表示事件的发生,函数用于对信号进行响应。通过Qt的元对象系统,可以在运行时动态地连接、修改和断开信号之间的关联。这种机制使得对象之间的通信变得简单而高效。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GG_Bond20

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值