关于QT信号、槽、槽函数的讲解

        也是好久没有发帖子了,最近博主主要还是在边学QT边完成任务,所以进度很慢,但确实在这几天对于QT自身槽和信号这类特殊的机制有了一定简单的理解,所以还是想记录下来,如果有初学者看到帖子对他有一定的帮助,那自然也会让我感到十分荣幸

一. 什么是信号与槽

        首先按照专业术语的描述是,QT的信号与槽机制是一种对象间通信机制。直观的可以表现出一个对象发出信号,另一个对象通过槽函数响应这个信号,从而实现多模块之间的解耦通信。这个也是属于Qt框架最强大的特性之一,也是尤其适合GUI事件响应和模块间协作。

        通过举一个简单的例子,家门口有一个门铃按钮(就是等同于我们的信号),我们按了一下,屋里的电铃声就响了(槽函数)。
        按门铃 = 发出信号  电铃声响了 = 槽函数被触发 按下和响铃之间 = connect()建立连接。

二. 信号/槽基本概念

        信号(Signal)是QObject的子类在特定事件发生时自动发出的“通知”,就是一个事件的广播。
 

        而槽(Slot)可以与信号连接的普通成员函数,专门用于响应通知,也就是“接收广播后做事”的函数

        

        connect(),Qt提供的函数,用于建立信号与槽之间的绑定关系,“把信号线插进槽口”

        emit,是一个关键字,用于触发自定义信号,也就是程序员手动“发出广播”

        QObject,QT所有支持信号、槽的类的基类,信号槽必须用它派生出来的类

三. 广播机制

        在QT的信号与槽机制种,广播机制指的是。信号一旦发出,系统会自动通知所有已连接的槽函数执行,无需显示调用,且发送者不关心接收者的存在与行为。

        这种机制体现了一种松耦合、发布-订阅式的通信模型,是Qt框架中组件之间高效协作的核心基础。

        解耦(Decoupling),信号发送者和接收者之间并没有直接依赖关系。发送者只负责发出信号,而不需要知道是否有接收者,更不需知道接收者是谁或要做什么。这样可以使模块之间保持独立,便于维护或拓展。

        一对多连接,一个信号可以同时连接多个槽函数。当信号被发出时,所有已经连接的槽将会被系统一次调用,按连接顺序一次执行。这样可以使一个事件可以触发多个处理逻辑,从而实现模块联动

        多对一连接:多个 不同的信号可以连接到同一个槽函数。当任意一个信号被发出时,该槽函数都会被执行。这使得不同来源的事件可以集中处理

        自动调用:当信号发出时,所有与之连接的槽函数会被自动执行,对于我们开发者来说并不需要手动调用。Qt框架内部通过事件系统和元对象机制完成槽函数的自动触发,我们只需要专注于逻辑实现

        在Qt中信号是广播式的,信号本身只是简单的发出,并没有指定特定的接收者。接收者(槽函数)会通过connect()来“订阅”这个信号。
        可以理解为信号式“广播”出去的,它不会知道谁会接收到它,接收者通过connect()函数明确指定自己要接受哪个信号,所以如果没有明确指定的接收者,那么这个函数就无法触发任何行为,因为没有对象响应找个信号

        在代码中可以这么理解,nullptr表示没有指定接收者,那么即使按钮被点击,信号也不会触发任何函数

// 如果没有指定接收者,信号会发出,但没有接收者来处理它
QObject::connect(&button, &QPushButton::clicked, nullptr, nullptr);

        刚开始我将Qt的广播形式和网络广播的形式弄混淆了,这里还是想做一个简单的澄清,是两个不同的概念

        网络广播在网络中,广播通常指的是将信息发送到网络上的所有设备,而不仅仅是发送给某一个指定的接收者。常见的网络广播是无差别地将信息发送给网络上的所有设别,而接收这些广播的设备通常是主动应答的,
        例如UDP广播,数据包被发送到一个特定的广播地址(如255.255.255.255),网络上的所有设备都有机会接收到这个广播。然后,每个接收到广播信息的设备可以选择是否回应(例如一般就是发送一个响应报文)

        

        在Qt中,信号的“广播”并不是指将消息发送给所有对象,而是一个信号可以连接到多个槽函数,因此多个接收者可以对同一个信号做出响应。信号本身并不主动去寻找接收者,而是通过connect()函数明确指定了哪些接收者应该处理这个信号

        信号发出:信号并不是想网络广播那样发送给所有设备,而是只从一个对象发出。可以想像是在说“我发生了某个事件,看下是否会有人响应我”

        “接收者”:只有通过connect()函数明确指定了接收者(即槽函数)后,信号才会被“接收”,并且接收者会执行相应的操作。这个过程并不需要接收者主动应答,而是由QT框架自动处理的

四. 信号与与槽的连接方式及适用场景

        我们将他分为一下五种

        系统信号(Qt自带) + 系统槽(如QAPPlication::quit)这种组合方式可以快速调用系统功能。
        信号部分是&QPushButton::clicked,这是Qt框架内置的标准信号,属于系统控件QPushButton,当按钮被点击时自动发出。所有这是系统信号(也就是由Qt控件自带)

        槽部分 &QApplication::quit,这是Qt提供的一个系统级槽函数,作用是关闭应用程序主事件循环,相当于”退出应用“所以是系统槽(由Qt提供)

#include "mainwindow.h"#include <QApplication>
#include <QPushButton>int main(int argc, char *argv[])
{QApplication a(argc, argv);MainWindow w;QPushButton button("Quit",&w);button.move(100,100);QObject::connect(&button, &QPushButton::clicked,&a,&QApplication::quit);w.show();return a.exec();
}

        系统信号 + 自定义槽函数 是目前较为常见的方式,如按键触发逻辑。那么同样的代码逻辑如何在系统信号 + 自定义槽函数中体现。

        在下面这段代码中系统信号自然是,Qt自带的信号,如QPushButton::clicked(),表示按钮被点击。
        而自定义的槽函数,是我们写在类中的处理函数,在这里是MainWindow::onButtonClicked()

        所以简单来理解“系统信号 + 自定义槽函数”就是——,按钮这种系统组件发出信号,用户自己写的函数去接收并响应
        

        QpushButton内部有个信号:void clicked(bool checked = false);当我们用connect()把这个信号和函数onButtonClicked()连接在一起,当我们点击按钮的时候,按钮会发出clickced信号(也就是前面提到的广播机制)。Qt自动调用我们自定义的槽函数onButtonClicked来处理事件

//在MainWindow.cpp中MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);QPushButton *button =new QPushButton("Quit",this);button->move(100,100);connect(button, &QPushButton::clicked, this, &MainWindow::onButtonClicked);}MainWindow::~MainWindow()
{delete ui;
}void MainWindow::onButtonClicked()
{qDebug() << "按钮被点击";
}//在MainWindow.h中#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr);~MainWindow();private:Ui::MainWindow *ui;private slots:void onButtonClicked();
};
#endif // MAINWINDOW_H

        自定义信号 + 系统槽 一般适用于自定义行为影响系统行为

        我们通过程序执行的顺序来理解代码,首先第一步显示从连接按钮点击信号开始,对应mainwindows.cpp,connect(),当button被点击的时候,调用MainWindow的onButtonclicked()方法。button是发出信号的对象,&QPushButton::Clicked()是系统信号(点击按钮后发出)。

        但是这里就要有疑惑了,明明是使用我们的自定义信号,为什么还是是系统信号呢?先不着急我们先分析完,之后再进行解释

        this是当前窗口对象(MainWindow),&Mainwindow::onButtonClicked是我们自定义的槽函数,响应按钮点击

        在按钮点击过后,在void MainWindow这个我们自己编写的槽函数中,在这个函数中我们有发出了另外一个信号quitApp(),这是我们在头文件中signals:定义的自定义信号
        也就是说,刚开始按钮被点击后发出系统信号,触发了我们自己的函数onButtonClicked(自定义槽),然后onButtonClicked中又调用了emit quitApp(发出我们的自定义信号)

        总结一下我们代码中完成的工作是,我们点击按钮触发系统信号,然后Qt自动调用我们定义的槽函数,我们的槽函数里发出quitApp(),里面是我们自定义的信号

//在mainwindow.h#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr);~MainWindow();private:Ui::MainWindow *ui;private slots:void onButtonClicked();//自定义信号
signals:void quitApp();};
#endif // MAINWINDOW_H//在mainwindow.cpp中#include "mainwindow.h"
#include "ui_mainwindow.h"#include "QPushButton"
#include "QDebug"MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);QPushButton *button =new QPushButton("Quit",this);button->move(100,100);connect(button, &QPushButton::clicked, this, &MainWindow::onButtonClicked);connect(this,&MainWindow::quitApp,qApp,&QApplication::quit);
}MainWindow::~MainWindow()
{delete ui;
}void MainWindow::onButtonClicked()
{emit quitApp();
}

        现在我们来解释一下,为什么整个程序执行的流程始终是系统信号。那为什么又还要发出自定义信号,博主后面了解到,Qt设计的很巧妙的地方就在于,信号链的“解耦”和“转发”能力。

        例如我们需要在点击按钮的时候退出程序,还想要加入记录日志、播放音效、保存设置等,如果全部写在onButtonclicked()里,那么这个槽函数就会越来越大,越来月耦合。
        那么我们在遇到这样的情况下就可以采取,在onButtonClicked()里发出一个自定义信号quitApp(),然后让其他模块自由决定是否接收这个信号,或者怎么处理信号
        例如如下情况,把“按钮点击的事件”,拆成了“点击后发出quitApp”信号,然后让其他的函数也来订阅这个信号

connect(this, &MainWindow::quitApp, qApp, &QApplication::quit);           // 退出程序
connect(this, &MainWindow::quitApp, logger, &Logger::writeQuitLog);       // 写日志
connect(this, &MainWindow::quitApp, soundPlayer, &Player::playExitSound); // 播放音效

        我们再举一个实际的例子,如果我们把多个行为都写再onButtonClicked中

void MainWindow::onButtonClicked() {qApp->quit();                             // 退出程序settingsManager->save();                  // 保存设置logger->log("用户点击退出");              // 写日志soundPlayer->play("bye.wav");             // 播放音效
}

        我们将所有的行为都写在了一个函数中,主窗口知道太多别的类(Logger、Player、SettingManager),如果将来我们选哟将其中的功能进行改变,要修改的地方就有很多,而且假如其他的地方也需要退出行为,还要复制代码到其他地方

// MainWindow.h
signals:void quitApp();  // ⛳️ 只发信号,不处理具体细节
void MainWindow::onButtonClicked() {emit quitApp();  // ⛳️ 你们谁爱处理谁处理,我不管
}

        在程序初始化的时候就可以通过信号连接不同的行为,所以总结就是,如果在一个槽函数中做了太多和“自己职责无关的事”,就可以考虑广播事件,让其他人来听

connect(mainWindow, &MainWindow::quitApp, qApp, &QApplication::quit);
connect(mainWindow, &MainWindow::quitApp, logger, &Logger::logQuit);
connect(mainWindow, &MainWindow::quitApp, settings, &Settings::save);
connect(mainWindow, &MainWindow::quitApp, soundPlayer, &Player::playExitSound);

        自定义信号 自定义槽函数 一般常用于模块解耦、自主通信,如果针对于自定义槽函数和信号,肯定会有更高的自由度,我们还是结合代码来分析

#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr);~MainWindow();private:Ui::MainWindow *ui;private slots:void onButtonClicked(); //自定义槽(用于发送信号)void handClose(); //自定义槽(响应信号关闭窗口)//自定义信号
signals:void closeApp(); //自定义信号};
#endif // MAINWINDOW_H//在mainwindow.cpp中#include "mainwindow.h"
#include "ui_mainwindow.h"#include "QPushButton"
#include "QDebug"MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);QPushButton *button =new QPushButton("Quit",this);button->move(100,100);connect(button, &QPushButton::clicked, this, &MainWindow::onButtonClicked);connect(this,&MainWindow::closeApp,this,&MainWindow::handClose);
}MainWindow::~MainWindow
()
{delete ui;
}void MainWindow::onButtonClicked()
{emit closeApp();
}void MainWindow::handClose(){qDebug() << "收到信号,关闭窗口";this->close();}

        首先还是在头文件总声明一个自定义信号,以及private slots中全是自定义的槽函数,也是在头文件中声明。
        然后在mainwindow中的构造函数中进行连接

,connect(button, &QPushButton::clicked, this, &MainWindow::onButtonClicked);
//这行是系统信号(QPushButton::clicked)
//->自定义槽(onButtonClicked)作用是:当按钮被点击,就会自动调用onButtonClicked()这个槽函数

        connect(this, &MainWindow::closeApp, this, &MainWindow::handleClose);这行是自定义信号(closeApp)->自定义槽(handleClose)作用就是当你发出closeApp信号时,Qt就会自动去调用handleClose()这个函数。

        然后在槽函数中发出信号,这个函数并不会直接关闭窗口。它只是发出一个“广播”,说明我准备关闭了

void MainWindow::onButtonClicked()
{emit closeApp();
}

        最后在响应的槽中执行操作

void MainWindow::handleClose()
{this->close();  // 真正关闭主窗口
}

        以及最后任意信号 + Lambda表达式 通常简洁高效,适合小型逻辑处理。这个机制是Qt信号槽机制中最灵活也是最现代的的用法之一,非常适合简介逻辑、快速响应、小功能点,无需定义额外的槽函数,直接在connect()中用Lambda表达式表达处理逻辑

        Lambda是一种匿名函数(临时的小函数),语法如下,一般用于只想快速响应一下,并不想为了这个操作特地去写某个函数

[]() {// 执行的代码
};

        例如。如果只是临时逻辑、简单调试、小按钮响应等首选Lambda,但是如果逻辑复杂、选哟复用、设计多个模块之间的通信还是尽量选择传统的槽函数。但是如果在大型的项目中涉及到多个对象协作、跨模块、需要事件驱动使用自定义信号 + 自定义槽

QPushButton *button = new QPushButton("点我", this);
button->move(100, 100);// 用 lambda 直接响应点击
connect(button, &QPushButton::clicked, [](){qDebug() << "👋 Lambda:按钮被点击啦!";
});

        博主去查了一下Lambda的具体原理,它是C++ 11引入的一种匿名函数(inline function)是临时写在某个地方的小函数,不需要在别的地方声明或命名,基本语法如下

[capture](parameter_list) -> return_type {// function body
}

        但大部分时候还是选择写成简略的写法

[]() {qDebug() << "Hello Lambda!";
};

        

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/80255.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

YOLOv8 涨点新方案:SlideLoss FocalLoss 优化,小目标检测效果炸裂!

YOLOv8优化秘籍&#xff1a;用SlideLoss和FocalLoss提升小目标检测精度&#xff08;附代码实战&#xff09;​​ ​&#x1f4cc; 核心问题&#xff1a;YOLOv8在检测小物体时效果不够好&#xff1f;​​ YOLOv8虽然是强大的目标检测模型&#xff0c;但在处理小物体或类别不平…

基于cubeMX的hal库STM32实现MQ2烟雾浓度检测

一、任务目标 使用STM32F103C8T6单片机&#xff0c;使用单片机AD模块采集MQ2烟雾传感器的数据&#xff0c;在OLED屏显示检测到的AD值、电压值和浓度值&#xff08;ppm单位&#xff09;。 二、实现过程 1、MQ2烟雾传感器的浓度转化方法 &#xff08;1&#xff09;实验所用的M…

Android之AI自动化测试--Midscene

文章目录 前言一、准备工作1.安装2.准备 API Key3.安装 adb4.连接设备 二、yaml格式自动化脚本1. 脚本案例2.执行结果 三、文件结构变化android 部分 前言 字节 Web Infra团队官宣Midscene 从 v0.15 开始支持 Android 自动化测试&#xff0c;本篇文章介绍yaml方式的Android自动…

类的六个默认成员函数

如果一个类中什么成员都没有&#xff0c;简称为空类。 空类中真的什么都没有吗&#xff1f;并不是&#xff0c;任何类在什么都不写时&#xff0c;编译器会自动生成以下6个默认成员函数。 默认成员函数&#xff1a;用户没有显式实现&#xff0c;编译器会生成的成员函数称为默认…

HarmonyOS Grid 网格列表可长按 item 拖动移动位置

方案一 @Component struct WorkCircleCreatePage {// 存储车控列表的数组@State VehicleDoorArr: IVehicleDoor[] = []// 当前移动的Item索引@State CurrentIndex: number = -1// 拖动时显示的数据@State MoveItem: IVehicleDoor = { title: , icon: }// 拖动时放大倍数@State…

海量数据笔试题--Top K 高频词汇统计

问题描述&#xff1a; 假设你有一个非常大的文本文件&#xff08;例如&#xff0c;100GB&#xff09;&#xff0c;文件内容是按行存储的单词&#xff08;或其他字符串&#xff0c;如 URL、搜索查询词等&#xff09;&#xff0c;单词之间可能由空格或换行符分隔。由于文件巨大&…

【数据结构】Map与Set结构详解

数据结构系列五&#xff1a;Map与Set(一) 一、接口的实现 1.方法上 2.成员上 二、Map的内外双接口结构 1.实现 1.1外部Map接口的实现 1.1.1临摹整体 1.1.2外部类实现整体 1.2内部Entry接口的实现 1.2.1临摹内部 1.2.2内部类实现内部 2.关系 3.意义 3.1逻辑内聚 …

Electron使用WebAssembly实现CRC-32 原理校验

Electron使用WebAssembly实现CRC-32 原理校验 将C/C语言代码&#xff0c;经由WebAssembly编译为库函数&#xff0c;可以在JS语言环境进行调用。这里介绍在Electron工具环境使用WebAssembly调用CRC-32 原理格式校验的方式。 CRC-32 原理校验函数WebAssembly源文件 C语言实现C…

【晶振】晶振的工作原理及其与单片机关系

晶振(晶体振荡器)是电子设备中常见的元件,其核心功能是提供稳定的时钟信号,而单片机(MCU)依赖这一信号来同步内部操作。以下是晶振的工作原理及其与单片机关系的详细说明: 一、晶振的工作原理 压电效应与谐振 晶振的核心是石英晶体,利用其压电效应: 当在晶体两端施加电…

【Oracle专栏】函数中SQL拼接参数 报错处理

Oracle相关文档,希望互相学习,共同进步 风123456789~-CSDN博客 1.背景 最近同事反馈了一个很奇怪的问题,即有一个函数,入参是当前年月,主要作用是通过SQL语句将不合规的数据插入到指定表中,插入数据时带上入参的年月参数。当前问题:单独测试SQL没有问题可以执行成功,…

nodejs之Express-介绍、路由

五、Express 1、express 介绍 express 是一个基于 Node.js 平台的极简、灵活的 WEB 应用开发框架,官方网址: https://www.expressjs.com.cn/ 简单来说,express 是一个封装好的工具包,封装了很多功能,便于我们开发 WEB 应用(HTTP 服务) (1)基本使用 第一步:初始化项目并…

Unicode和 ASCII码以及UTF-8编码的区别和联系

Unicode、ASCII 和 UTF-8 是计算机编码领域的关键概念&#xff0c;它们既有联系又有区别。以下是它们的对比分析&#xff1a; 1. ASCII&#xff08;美国信息交换标准码&#xff09; 诞生时间&#xff1a;1967 年&#xff08;7 位编码&#xff0c;共 128 字符&#xff09;。特点…

STM32F103_HAL库+寄存器学习笔记20 - CAN发送中断+ringbuffer + CAN空闲接收中断+接收所有CAN报文+ringbuffer

导言 如上所示&#xff0c;在[[STM32F103_HAL库寄存器学习笔记19 - CAN发送中断CAN接收中断接收所有CAN报文ringbuffer数据结构]]的基础上&#xff0c;为CAN发送端也引入了ringbuffer&#xff08;环形缓冲区&#xff09;机制。CAN发送有三个发送邮箱&#xff0c;为什么还另外需…

Windows 环境下安装 MariaDB 及 HeidiSQL 使用教程

引言 本报告旨在提供一份详尽的操作指南。内容将覆盖在 Windows 操作系统上安装 MariaDB Community Server 的全过程。我们还将探讨如何利用 HeidiSQL 这款图形用户界面&#xff08;GUI&#xff09;工具&#xff0c;直观地预览和管理我们新安装的数据库。除了安装与配置的步骤…

美团2024年春招第一场笔试 C++

目录 1&#xff0c;小美的平衡矩阵 2&#xff0c;小美的数组询问 3&#xff0c;小美的MT 4&#xff0c;小美的朋友关系 1&#xff0c;小美的平衡矩阵 【题目描述】 给定一个n*n的矩阵&#xff0c;该矩阵只包含数字0和1。对于 每个i(1<i<n)&#xff0c;求在该矩阵中&am…

09-DevOps-Jenkins实现CI持续集成

前面已经把harbor搭建好了&#xff0c;也可以向harbor中推送自定义镜像。 原计划是在Jenkins这台服务器上&#xff0c;完成镜像构建&#xff0c;然后把镜像推送的harbor仓库中。现在改变计划了&#xff0c;Jenkins所在的服务器&#xff08;192.168.1.10&#xff09;不负责镜像…

Postman设置了Cookies但是请求不携带Cookie

1 问题说明 使用Postman工具往往要向本地服务器发送请求携带Cookie便于测试接口&#xff0c;但是在Send下面的Cookies选项中设置域名127.0.0.1&#xff0c;并添加Cookie&#xff0c;发现发送的请求怎么都不会携带Cookie&#xff1a; 通过Fiddler抓包发现并没有Cookie&#xff1…

【unity】Vulkan模式下部分Android机型使用VideoPlayer组件播放视频异常问题

一、问题背景 考虑到Vulkan高性能的优势&#xff0c;项目组决定打包设置为vulkan优先&#xff0c;opengl es次之的方案&#xff1b;但由于部分低端设备或者部分模拟器对Vulkan的兼容性良莠不齐&#xff0c;导致诸如使用VideoPlayer组件无法正常播放视频等问题频发&#xff0c;而…

0802api设计和实战-网络ajax请求1-react-仿低代码平台项目

文章目录 1 API设计1.1 用户功能1.1.1 获取用户信息1.1.2 注册1.1.3 登录 1.2 问卷功能1.2.1 获取单个问卷1.2.2 获取问卷列表1.2.3 创建问卷1.2.4 更新问卷1.2.5 批量彻底删除问卷1.2.6 复制问卷 1.3 小结 2 实战2.1配置axios2.2 封装API和测试2.3 新建问卷2.4 自定义hooks封装…

Android Kotlin AIDL 完整实现与优化指南

本文将详细介绍如何在Android中使用Kotlin实现AIDL&#xff08;Android Interface Definition Language&#xff09;&#xff0c;并提供多种优化方案。 一、基础实现 1. 创建AIDL文件 在src/main/aidl/com/example/myapplication/目录下创建&#xff1a; IMyAidlInterface.…