QT 信号(Signal)与槽(Slot)机制

上学后,人们问我长大了要做什么,我写下“快乐”。他们告诉我,我理解错了题目,我告诉他们,他们理解错了人生。

——约翰·列侬

一、信号(signal)与槽(slot)

在QT中,信号(signal)与槽(slot)机制是一种用于对象间通信的重要机制。它允许一个对象发出信号,而其他对象可以通过连接到该信号的槽来接收并处理这个信号。

信号是由QObject派生类中声明的特殊函数,用于表示某个事件的发生。例如,当按钮被点击时,QPushButton类会发出一个clicked()信号。每个信号都有一个唯一的名称,并且可以带有参数。

是普通的成员函数,用于接收和处理信号。槽函数可以在任何QObject派生类中定义,但必须使用特殊的宏进行声明。槽函数可以执行任意操作,包括修改对象的状态、调用其他函数等。

连接(connection)是将信号与槽关联起来的过程。通过连接,当信号发出时,与之连接的槽函数将被自动调用。连接可以在代码中手动创建,也可以使用Qt提供的可视化工具进行创建。

信号与槽机制的优势在于它们实现了松耦合的对象间通信。通过信号与槽,不同对象之间可以进行灵活的交互,而无需显式地引用彼此。这种机制使得代码更加模块化、可维护性更高,并且能够方便地实现事件驱动的程序设计。

二、元对象系统

在Qt中,元对象(Meta Object)是一种特殊的对象,用于存储类的元信息,包括类名、父类名、信号和槽等。每个继承自QObject的类都有一个对应的元对象。

元对象系统允许Qt实现一些高级功能,比如信号与槽机制、属性系统、动态属性以及对象反射等。通过元对象系统,Qt可以在运行时获取类的元信息,并且能够动态地操作这些信息。

元对象系统的核心是使用元对象宏(Q_OBJECT)来声明一个类为元对象类。当一个类被声明为元对象类时,Qt的元对象编译器(MOC)会生成额外的代码,用于处理信号与槽、属性等元信息。

通过元对象系统,Qt可以实现诸如信号与槽的自动连接、动态属性的添加与修改、对象类型的判断等功能。这使得Qt具有很强的灵活性和可扩展性,能够支持更加动态和智能的编程模式。

三、实现原理

在Qt中,信号(signal)与槽(slot)机制是元对象系统的核心组成部分。

首先,元对象系统通过使用元对象宏(Q_OBJECT)来声明一个类为元对象类。当一个类被声明为元对象类时,Qt的元对象编译器(MOC)会生成额外的代码,用于处理信号与槽、属性等元信息。

然后,信号与槽机制是元对象系统的一种实现方式。通过元对象系统,Qt可以在运行时获取类的元信息,并且能够动态地操作这些信息。而信号与槽机制则是利用了元对象系统的特性,实现了对象间的松耦合通信。

具体来说,当一个类声明了信号时,MOC会将信号的相关信息添加到该类的元对象中。而当一个类声明了槽时,MOC会将槽的相关信息添加到该类的元对象中。通过连接信号与槽,可以在运行时实现信号的发出和槽的自动调用。

因此,信号与槽机制依赖于元对象系统,通过元对象系统提供的元信息,实现了对象间的动态通信。元对象系统为信号与槽机制提供了基础,使得信号与槽能够在运行时进行连接和调用。

四、设计模式

在Qt中,信号与槽机制的底层实现使用了观察者模式(Observer Pattern)和命令模式(Command Pattern)。

观察者模式用于实现信号与槽之间的连接和通信。在观察者模式中,信号充当了被观察者(Subject),而槽函数充当了观察者(Observer)。当信号发出时,所有连接到该信号的槽函数都会被调用,实现了对象间的松耦合通信。

命令模式用于将信号与槽的调用进行封装。在命令模式中,信号的发出相当于命令的发出,而槽函数相当于命令的接收者。通过将信号与槽的调用封装成命令对象,可以实现对调用的延迟、撤销等操作。

除了观察者模式和命令模式,Qt中还使用了其他设计模式来支持信号与槽机制的实现。例如,Qt中的元对象系统使用了元数据模式(Metadata Pattern)来存储和处理类的元信息。此外,Qt还使用了工厂模式(Factory Pattern)来创建信号与槽的连接。

五、使用示例

1、使用可视化工具连接

在 UI 界面中右键按钮控件,选择【Go to slot】,可以看到该按钮控件下的信号,我们选择单击信号【clicked】,编辑器会帮我们自动在头文件、源文件添加对应的槽函数,我们只需要实现处理逻辑,

private slots:
    void on_pushButton_clicked();

2、使用 connect 函数

使用 connect 函数,将按钮 pushButton_2 的单击信号 clicked 连接到本窗口 this 的槽函数 on_pushButton_clicked,因此点击按钮2的效果等同于按钮1。

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // 谁发出信号,发出什么信号,谁接收信号,谁处理信号?
    
    // 使用 connection 与宏
    // connect(ui->pushButton_2,SIGNAL(clicked(bool)),this,SLOT(on_pushButton_clicked()));
    
    // 使用 connection 与指针
    // connect(ui->pushButton_2,&QPushButton::clicked,this,&MainWindow::on_pushButton_clicked);
    
    // 使用 connection 与匿名函数(当信号的处理逻辑非常简单时,使用匿名函数为佳)
    connect(ui->pushButton_2,&QPushButton,[this](){
        qDebug() << "click";
    });
}

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

void MainWindow::on_pushButton_clicked()
{
    qDebug() << "click";
}

3、自定义信号与槽

我们自定义一个遥控器类RemoteControl与空调类AirConditioner,遥控器发出信号,空调根据指令运行,

注意创建类时勾选 Q_OBJECT 宏,接下来添加遥控器的信号,

#ifndef REMOTECONTROL_H
#define REMOTECONTROL_H

#include <QObject>

class RemoteControl : public QObject
{
    Q_OBJECT
signals:
    // 默认制冷
    void run();
    // 制热/抽湿...
    void run(QString mode);
public:
    RemoteControl();
};

#endif // REMOTECONTROL_H

可以看到我们重载了信号 run,默认情况下 run 没有参数,属于制冷模式,其他情况 run 根据参数 mode 运行不同的模式。

然后我们为空调添加槽函数,同样重载了槽函数 exec 实现运行不同模式,注意这里的重载参数必须与信号保持一致,

#ifndef AIRCONDITIONER_H
#define AIRCONDITIONER_H

#include <QObject>

class AirConditioner : public QObject
{
    Q_OBJECT
public:
    AirConditioner();
public slots:
    void exec();
    void exec(QString mode);
};

#endif // AIRCONDITIONER_H
#include "airconditioner.h"
#include <QDebug>

AirConditioner::AirConditioner() {}

void AirConditioner::exec()
{
    qDebug()<<"cold";
}

void AirConditioner::exec(QString mode)
{
    qDebug()<< mode;
}

最后我们在 mainwindow 实例化一个遥控器跟空调对象,将信号与槽函数连接起来,

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
#include "airconditioner.h"
#include "remotecontrol.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    AirConditioner airConditioner;
    RemoteControl remoteControl;
    connect(&remoteControl,SIGNAL(run()),&airConditioner,SLOT(exec()));
    connect(&remoteControl,SIGNAL(run(QString)),&airConditioner,SLOT(exec(QString)));
    // emit 可省略
    emit remoteControl.run();
    remoteControl.run("hot");

}

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

相关推荐

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-26 07:24:12       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-26 07:24:12       100 阅读
  3. 在Django里面运行非项目文件

    2024-03-26 07:24:12       82 阅读
  4. Python语言-面向对象

    2024-03-26 07:24:12       91 阅读

热门阅读

  1. UNDERSTANDING HTML WITH LARGE LANGUAGE MODELS

    2024-03-26 07:24:12       39 阅读
  2. python与excel第七节 拆分工作簿

    2024-03-26 07:24:12       33 阅读
  3. docker简单使用1

    2024-03-26 07:24:12       34 阅读
  4. CentOS配置docker外部访问

    2024-03-26 07:24:12       44 阅读
  5. php 快速入门(四)

    2024-03-26 07:24:12       45 阅读
  6. FastAPI+React全栈开发04 FastAPI概述

    2024-03-26 07:24:12       29 阅读
  7. node.js常用的命令

    2024-03-26 07:24:12       35 阅读