Qt信号与槽高级特性与项目实战:原理剖析与工程化应用指南

news/2025/3/31 9:14:05/

文章目录

    • 五、信号与槽的高级特性
      • 1. 信号与信号的连接
        • (1) 实现信号的转发
        • (2) 信号与信号连接的应用场景
      • 2. 带参数的信号与槽
        • (1) 带参数的信号声明与使用
        • (2) 参数类型的匹配问题
      • 3. 信号与槽的断开连接
        • (1) 为什么需要断开连接
        • (2) 如何使用 `QObject::disconnect()`
      • 小结
    • 六、信号与槽机制的底层原理
      • 1. 元对象系统(Meta-Object System)
        • (1)元对象系统的作用
        • (2)moc(Meta-Object Compiler)的工作流程
        • (3)`Q_OBJECT` 宏的重要性
      • 2. 信号与槽的调用流程
        • (1)信号发出后的处理流程
        • (2)槽函数的调用过程
        • (3)一个简单示例
      • 小结
    • 七、信号与槽调试实战:从踩坑到填坑指南
      • 1. 信号不响应的五大元凶
        • 🕵️‍♂️ 场景1:忘记`Q_OBJECT`宏
        • 🔌 场景2:连接参数不匹配
        • 🌀 场景3:事件循环未启动
      • 2. 连接失败的排查工具箱
        • 🛠️ 方法1:查看所有连接
        • 🧪 方法2:使用QSignalSpy测试信号
        • 📝 方法3:对象树检查
      • 3. 高效调试技巧
        • 🔍 技巧1:信号断点追踪
        • 📌 技巧2:日志埋点
        • 🧩 技巧3:参数验证宏
      • 4. 典型案例分析
        • 📦 案例:跨线程崩溃问题
        • 🔄 案例:信号循环触发
      • 5. 调试备忘录(速查表)
    • 八、信号与槽机制在项目中的综合应用
    • 1. 小型项目示例:简易计算器
      • (1)示例功能说明
      • (2)主要类与文件
      • (3)关键代码示例
        • (4)关键点说明
    • 2. 项目架构设计
      • (1)分层思想
      • (2)在项目中合理运用信号与槽
      • (3)示例:在加法计算器中分离逻辑
      • 总结

接着上一篇文章我们继续讲解

五、信号与槽的高级特性

1. 信号与信号的连接

在某些场景中,我们可能希望当一个对象发射某个信号时,自动触发另一个对象的信号,而不是直接触发槽函数。也就是说,可以把一个信号“转发”成另一个信号。这样做可以让某些逻辑更清晰:上层只关心“信号 -> 信号”这一对照关系,而不必显式地调用槽函数。

(1) 实现信号的转发
  • 基本思路:将发送者对象的信号 signalA 与接收者对象的“信号” signalB 连接起来。
  • 语法:和连接信号与槽一样,只是把“槽函数”换成另一个信号。

示例代码

#include <QApplication>
#include <QObject>
#include <QDebug>// A 对象:发射信号A
class SenderA : public QObject
{Q_OBJECT
public:explicit SenderA(QObject *parent = nullptr) : QObject(parent) {}signals:void signalA(); // 用于演示的信号public slots:void emitSignalA(){qDebug() << "[SenderA] emit signalA()";emit signalA();}
};// B 对象:也有自己的信号B
class SenderB : public QObject
{Q_OBJECT
public:explicit SenderB(QObject *parent = nullptr) : QObject(parent) {}signals:void signalB(); // 转发用的信号// 注意这里没有定义slot,B纯粹做一个转发者也可以
};int main(int argc, char *argv[])
{QApplication app(argc, argv);SenderA a;SenderB b;// 将a的signalA 与 b的signalB 连接,这就相当于 signalA -> signalBQObject::connect(&a, &SenderA::signalA,&b, &SenderB::signalB);// 还可以再把 b 的signalB 连接到其他对象的槽函数,或再连接到其他信号QObject::connect(&b, &SenderB::signalB,[](/*可带参数*/){qDebug() << "[Lambda] Received signalB from b!";});// 触发A的信号 -> 导致B也发射signalB -> 导致Lambda被调用a.emitSignalA();return 0;
}#include "main.moc"

关键点

  • connect(&a, &SenderA::signalA, &b, &SenderB::signalB):此时 signalB 并不是槽函数,而是一个信号。Qt 允许这样做。
  • a 发射 signalA() 时,b 会收到这个信号并立刻转发 signalB()
  • 其他对象若对 bsignalB() 感兴趣,可以继续 connect 到自己的槽函数或信号。
(2) 信号与信号连接的应用场景
  • 分层设计:有时我们希望中间对象不关心具体逻辑,只转发信号给更上层或其他模块。
  • 解耦:如果直接 signalA -> slotX 会耦合到槽函数实现;signalA -> signalB -> slotX 可以让 B 模块灵活替换或增加逻辑,而无需改动 A 或 X。
  • 事件聚合或分发:在复杂系统里,可用信号之间的转发来进行统一管理、路由或过滤。

2. 带参数的信号与槽

在很多情况下,信号和槽需要携带信息进行通信。例如:当按钮被点击时,需要把当前的坐标传给槽函数,或者当数据更新时,需要把新的数值发给槽函数。

(1) 带参数的信号声明与使用
  • 在类的 signals: 区域中声明一个带参数的信号,如 void dataChanged(int newVal, QString desc);
  • 在类的 slots: 或任意可用的函数(包括lambda)中编写与之匹配的形参列表,如 void onDataChanged(int val, const QString &str).
  • connect() 时,信号与槽的参数类型、顺序必须对应,否则连接会失效或出现警告。

示例代码

class DataObject : public QObject
{Q_OBJECT
public:explicit DataObject(QObject *parent = nullptr) : QObject(parent) {}void setData(int v, const QString &desc){if (m_val != v || m_desc != desc) {m_val = v;m_desc = desc;// 发射带参数的信号emit dataChanged(m_val, m_desc);}}signals:void dataChanged(int newVal, const QString &info);private:int m_val = 0;QString m_desc;
};// 槽函数示例
class Handler : public QObject
{Q_OBJECT
public slots:void handleDataChanged(int val, const QString &str){qDebug() << "[Handler] data changed => value:" << val << ", info:" << str;}
};// 使用
DataObject dataObj;
Handler handler;
QObject::connect(&dataObj, &DataObject::dataChanged,&handler, &Handler::handleDataChanged);dataObj.setData(100, "Temperature");
  • dataObj.setData(100, "Temperature") 执行时,若值有变化,就会 emit dataChanged(100, "Temperature")
  • handler 收到这个信号,执行 handleDataChanged(100, "Temperature")
(2) 参数类型的匹配问题
  • 顺序:信号和槽的参数列表顺序必须一致,类型必须兼容。
  • 数量:槽函数的参数数量可以小于等于信号的参数数量(前提是前面部分类型一致)。多出的参数会被忽略。
    • 例如,信号 void someSignal(int, QString) 可以连接到槽 void someSlot(int).
  • 引用/常量:最好使用 const QString & 等方式避免拷贝,提高效率。

3. 信号与槽的断开连接

在某些情况下,我们需要“取消”某个信号与槽的绑定,避免重复调用或在对象销毁前后发生意外访问。

(1) 为什么需要断开连接
  • 对象销毁:若对象 A 仍在发射信号,但对象 B 已经被销毁,如果没有及时断开,可能导致对无效内存的访问(不过 Qt 在对象析构时也会自动断开和它相关的连接,这通常能避免崩溃)。
  • 逻辑调整:有时程序需要动态地切换槽函数或暂时停止接收信号。
  • 性能:如果有多个槽监听同一个信号,但不再需要某些槽,可断开以减少执行开销。
(2) 如何使用 QObject::disconnect()
  • 基本用法:与 connect() 对应,disconnect() 也有多种重载形式,可以指定发送者、信号、接收者、槽来断开指定的连接。
  • 完全断开:若不指定信号与槽,只写 disconnect(&objSender, nullptr, &objReceiver, nullptr),则会断开 objSenderobjReceiver 之间的所有连接。

示例代码

QObject::disconnect(senderObj, &SenderClass::someSignal,receiverObj, &ReceiverClass::someSlot);
  • 仅在发送者、信号、接收者、槽都与原先的 connect() 相匹配时才会断开对应的那条连接。
  • 如果需要断开多个连接,需要调用多次 disconnect() 或使用更广泛的断开方式。

使用场景

  • 动态切换:先 disconnect()connect() 到新的槽。
  • 清理阶段:在某些复杂逻辑中,手动保证连接的解除,以免后续误调用。

小结

  1. 信号转信号
    • 允许将一个对象的信号转发成另一个对象的信号,形成“信号 -> 信号 -> 槽”链路;
    • 应用场景是转发、分层或解耦,让中间模块只关心转发逻辑,不必直接调用槽。
  2. 带参数的信号与槽
    • 在类声明里使用 signals: 定义带参数的信号;
    • 在槽函数中确保形参类型、顺序与信号匹配;
    • 可以让信号携带更多信息给槽函数,提高可扩展性和可读性。
  3. 断开连接
    • 使用 QObject::disconnect() 手动解除某个信号与槽的关联;
    • 常见于对象销毁前或需要临时停止接收信号的场景;
    • 注意只要对象存在并未自动销毁连接,正常情况下 Qt 会在对象析构时自动断开相关连接。

通过对高级特性的学习,你可以在更复杂的场景下使用信号与槽:不仅可以实现多对象的信号转发、携带多种类型的参数,还能根据需要灵活地断开或重新连接,进一步提升程序的灵活度与可维护性。在实际开发中,如果你的信号与槽链路非常复杂,可以考虑使用注释、类图或文档来记录,以免日后调试时混淆。

六、信号与槽机制的底层原理

在深入使用信号与槽之前,了解一下底层原理能帮助我们更好地理解它的工作方式。主要涉及 Qt 的元对象系统(Meta-Object System)以及信号与槽在运行时的调用流程。


1. 元对象系统(Meta-Object System)

(1)元对象系统的作用

Qt 之所以能够提供信号与槽、属性系统、对象反射等特性,根本原因在于它的元对象系统。这个系统可以理解为一种“记录和管理类信息的机制”,包括:

  • 识别类中包含 Q_OBJECT 宏的部分
  • 记录类名、信号、槽、属性等信息
  • 支持运行时查询和调用(如根据对象指针和信号名找到已连接的槽函数)

通过这种机制,Qt 为每个使用 Q_OBJECT 宏的类生成相应的元数据,帮助完成信号与槽、属性读写等功能。

(2)moc(Meta-Object Compiler)的工作流程

当我们在类中使用 Q_OBJECT 宏并包含信号或槽时,Qt 的元对象编译器(moc)会进行额外的处理:

  1. 扫描头文件,找到 Q_OBJECT 宏所在的类。
  2. 自动生成一个额外的 C++ 源文件(例如 moc_MyClass.cpp)。
  3. 这个文件包含以下内容:
    • 类的元数据(如类名、信号列表、槽列表)
    • 供运行时使用的辅助函数(比如信号发射后的槽查找)
  4. 最终与普通源文件一起编译,成为应用程序的一部分。

简而言之,moc 会把你的类中声明的信号、槽等信息收集起来,编进程序,使得在运行时能够进行“对象 + 信号”到“槽函数”的查找和调用。

(3)Q_OBJECT 宏的重要性

Q_OBJECT 是让类具备“Qt 元对象能力”的关键标志。如果在类中声明了信号和槽,却没有写 Q_OBJECT,那么 moc 就不会生成对应的元数据,也就无法完成真正的信号与槽连接。


2. 信号与槽的调用流程

当我们在代码中使用 emit 关键字发射一个信号时,Qt 内部会通过元对象系统找到与该信号相连的所有槽,并按照一定规则去调用它们。可以分为以下几个阶段:

(1)信号发出后的处理流程
  1. 对象发射信号
    例如 emit someSignal(123);。虽然 emit 本质上是个空宏,但它能让代码更直观,告诉大家这里是在发射信号。
  2. 查找连接列表
    Qt 在内部维护一个连接信息表,记录“对象指针 + 信号”对应了哪些“对象指针 + 槽函数”。当某个信号被发射时,会检索该列表,找到所有匹配的槽。
  3. 根据连接类型调用槽
    • DirectConnection:信号发射处立即调用槽函数(当前线程、当前调用栈)。
    • QueuedConnection:将信号调用打包成事件,投递到接收者所在线程的事件循环,然后在该线程下一个事件循环周期里执行槽函数。
    • AutoConnection:如果发送者与接收者处于同一线程,则相当于 Direct;否则就变成 Queued。
  4. 依次执行所有槽
    连接信息里可能存在多个槽,Qt 会依次调用。每个槽接收到的参数与发射信号时传入的一致。
(2)槽函数的调用过程
  • DirectConnection(或同线程下的 AutoConnection)
    信号发射后,会直接在发射的那一瞬间调用槽函数,等全部槽函数执行完才返回到原处。
  • QueuedConnection(或跨线程的 AutoConnection)
    发射信号时,Qt 会将参数进行序列化,然后将这个调用信息封装成事件,推送到目标线程的事件队列。当目标线程处理事件时,才会反序列化参数并调用槽函数。
(3)一个简单示例

假设我们写了:

connect(senderObj, &SenderClass::valueChanged,receiverObj, &ReceiverClass::updateValue);

并在 senderObj 内部做了:

void SenderClass::setValue(int v)
{if (m_value != v) {m_value = v;emit valueChanged(v); // 发射信号}
}

setValue(10) 被调用后:

  1. 触发 emit valueChanged(10);
  2. Qt 内部查找 (senderObj, "valueChanged") 所有的槽函数列表
  3. 找到 (receiverObj, "updateValue(int)")
  4. 如果是直接连接,就立即在当前线程调用 receiverObj->updateValue(10)。如果是队列连接,就投递事件给接收者线程,下次事件循环执行。

小结

  1. 元对象系统
    Qt 通过 moc 工具来扫描带有 Q_OBJECT 宏的类,自动生成元数据,记录信号、槽、属性等信息,为信号与槽机制提供支持。
  2. 信号与槽调用
    当对象发射信号时,Qt 会根据连接信息查找并调用所有相关槽。连接类型决定了槽函数的调用时机和所在线程。
  3. 线程间通信
    如果发送者与接收者在不同线程中,AutoConnection 会自动采用队列连接的方式,确保线程安全。
  4. Q_OBJECT 必须加
    如果类中要使用信号、槽或 Qt 的高级特性,就一定要包含 Q_OBJECT 宏,否则 moc 无法生成对应的元对象代码。

通过这些底层原理,我们就明白为什么必须有 Q_OBJECT 宏,也理解了 Qt 信号与槽机制是如何在运行时完成“对象之间通信”的。日常开发中,写完 connect(...) 就能实现功能,几乎不用关心底层逻辑,但遇到复杂情况(如多线程、跨模块等),了解原理能帮助我们排查问题、做更合理的设计。


七、信号与槽调试实战:从踩坑到填坑指南

作为Qt开发者,你一定遇到过这样的抓狂时刻:明明写了connect,但点击按钮死活没反应!别慌,这里总结了我多年踩坑经验,手把手教你如何快速定位问题。


1. 信号不响应的五大元凶

🕵️‍♂️ 场景1:忘记Q_OBJECT
// 错误示例:漏掉Q_OBJECT宏
class MyWidget : public QWidget {
// 没有Q_OBJECT
signals:void mySignal();
};

症状
• 编译无报错,但信号无法触发
• 元对象系统未启用,moc不生成代码

修复方案

class MyWidget : public QWidget {Q_OBJECT  // 关键!
signals:void mySignal();
};
🔌 场景2:连接参数不匹配
// 信号:void valueChanged(int)
// 错误连接:槽参数为QString
connect(obj, &MyClass::valueChanged, label, &QLabel::setText); // 槽参数类型不匹配

症状
• 编译无报错,运行时无反应
• 连接失败,但程序不崩溃

调试技巧

// 检查connect返回值(Qt5+)
QMetaObject::Connection conn = connect(...);
if (!conn) {qDebug() << "连接失败!";
}
🌀 场景3:事件循环未启动
QPushButton button;
button.show();
// 直接退出,事件循环未执行
return 0; 

症状
• 窗口一闪而过,所有信号无响应

正确写法

QApplication app(argc, argv);
QPushButton button;
button.show();
return app.exec(); // 启动事件循环

2. 连接失败的排查工具箱

🛠️ 方法1:查看所有连接

在调试状态下,使用Qt Creator的调试对象信息

  1. 在调试模式暂停程序
  2. 右键点击QObject对象
  3. 选择「显示信号/槽连接」
🧪 方法2:使用QSignalSpy测试信号
// 测试信号是否触发
QSignalSpy spy(button, &QPushButton::clicked);
button.click(); // 模拟点击
QVERIFY(spy.count() == 1); // 验证信号触发次数
📝 方法3:对象树检查
// 查看对象父子关系
parentWidget->dumpObjectTree(); // 输出示例:
// QWidget::MyWidget
//     QPushButton::confirmButton
//     QLabel::statusLabel

确保接收者对象未被提前销毁!


3. 高效调试技巧

🔍 技巧1:信号断点追踪

在Qt Creator中设置信号断点

  1. 点击「调试」→「添加信号/槽断点」
  2. 输入信号签名(如QPushButton::clicked()
  3. 触发信号时自动暂停
📌 技巧2:日志埋点

在关键位置添加调试输出:

// 在槽函数中
void MyClass::handleClick() {qDebug() << "槽函数被调用!线程ID:" << QThread::currentThreadId();
}// 在信号触发处
emit dataReady(info);
qDebug() << "信号已触发:" << info;
🧩 技巧3:参数验证宏
// 检查参数有效性
connect(obj, &MyClass::dataReady, [](const QString &data) {Q_ASSERT_X(!data.isEmpty(), "槽函数", "数据不能为空");// 业务逻辑...
});

4. 典型案例分析

📦 案例:跨线程崩溃问题
// 错误代码:在线程间传递非QObject对象
connect(workerThread, &Worker::resultReady, this, [=](std::vector<int> result) { // 非QObject派生类型// 处理数据...
});

解决方案

  1. 使用Q_DECLARE_METATYPE注册自定义类型
  2. 使用QVariant封装数据
🔄 案例:信号循环触发
// 错误代码:信号互锁
connect(spinBox, &QSpinBox::valueChanged, slider, &QSlider::setValue);
connect(slider, &QSlider::valueChanged, spinBox, &QSpinBox::setValue);

现象:修改任意控件导致无限循环

修复方案

// 修改时暂时断开连接
void updateValue(int val) {disconnect(...);spinBox->setValue(val);slider->setValue(val);connect(...);
}

5. 调试备忘录(速查表)

问题现象优先检查点
所有信号无响应1. Q_OBJECT宏 2. 事件循环
部分信号不触发1. 连接返回值 2. 参数匹配
随机崩溃1. 跨线程连接类型 2. 对象生命周期
信号延迟响应1. 事件队列堆积 2. 耗时槽函数

掌握这些调试技巧后,相信你一定能快速解决各种信号槽问题。记住:好的开发者不是不写BUG,而是能用最快的速度解决BUG!如果遇到其他诡异问题,欢迎在评论区留言讨论~

八、信号与槽机制在项目中的综合应用

在完成对信号与槽机制基本概念与原理的学习后,很多开发者会问:“如何将这一套机制真正运用到实际项目中?” 本节将通过一个简易计算器示例来演示信号与槽的综合用法,并简单探讨在较大项目中如何利用信号与槽来优化架构设计。


1. 小型项目示例:简易计算器

下面是一个非常简化的“加法计算器”示例,用来说明如何在界面中使用信号与槽来处理按钮点击和数据计算。

(1)示例功能说明

  • 有两个输入框,用户可以在其中输入数字。
  • 有一个按钮,用来触发加法运算。
  • 结果在一个标签(QLabel)中显示。

整个界面示例(逻辑示意):

┌───────────────────────────────┐
│     [ QLineEdit: number1 ]    │
│     [ QLineEdit: number2 ]    │
│ [QPushButton: 计算]  [QLabel: 结果] │
└───────────────────────────────┘

(2)主要类与文件

  • MainWindow(或直接使用一个QWidget)
    • 包含界面控件的声明与布局
    • connect() 用于连接按钮的 clicked() 信号与对应的槽函数
  • main.cpp
    • 程序入口,初始化并显示主窗口

示例仅展示关键代码片段,省略了工程文件与部分样板代码。

(3)关键代码示例

main.cpp:

#include <QApplication>
#include "mainwindow.h"int main(int argc, char *argv[])
{QApplication app(argc, argv);MainWindow w;w.show();return app.exec();
}

mainwindow.h:

#pragma once#include <QMainWindow>QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECT  // 注意:使用信号与槽必须有 Q_OBJECT 宏public:explicit MainWindow(QWidget *parent = nullptr);~MainWindow();private slots:void onCalculateClicked();  // 槽函数:处理“计算”按钮点击private:Ui::MainWindow *ui;
};

mainwindow.cpp:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMessageBox>MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);// 假设在 Qt Designer 中已经放置了 lineEdit_number1, lineEdit_number2, // pushButton_calculate, label_result 等控件// 这里直接用 connect() 将按钮点击信号与槽函数 onCalculateClicked() 关联connect(ui->pushButton_calculate, &QPushButton::clicked,this, &MainWindow::onCalculateClicked);
}MainWindow::~MainWindow()
{delete ui;
}void MainWindow::onCalculateClicked()
{// 1. 读取输入框内容QString strNum1 = ui->lineEdit_number1->text();QString strNum2 = ui->lineEdit_number2->text();// 2. 转换为数字并进行加法bool ok1 = false, ok2 = false;double num1 = strNum1.toDouble(&ok1);double num2 = strNum2.toDouble(&ok2);// 3. 检查转换是否成功if(!ok1 || !ok2) {QMessageBox::warning(this, "警告", "请输入合法的数字!");return;}double result = num1 + num2;// 4. 显示结果ui->label_result->setText(QString::number(result));
}
(4)关键点说明
  1. MainWindow 类中使用 Q_OBJECT
    这样才可使用信号与槽机制,否则编译器不会生成必要的元对象代码。
  2. 信号与槽的连接
    • QPushButton::clicked 是按钮的内置信号
    • MainWindow::onCalculateClicked 是我们自定义的槽
    • 通过 connect() 函数将二者连接起来,实现按钮点击时自动调用槽函数。
  3. 数据处理
    • 在槽函数中完成对输入数据的读取、计算和结果显示。
    • 如果需要更复杂的逻辑,可再拆分到专门的业务逻辑类里,只在槽函数中调用业务类的接口。

这样,一个简单的计算器就完成了“界面 + 信号槽 + 逻辑”的基本模式。


2. 项目架构设计

随着项目规模增大,直接把所有逻辑都写在槽函数里,可能导致代码臃肿、不易维护。此时就要考虑如何更好地利用信号与槽,来进行更清晰的架构设计。

(1)分层思想

  1. UI 层
    主要负责界面绘制、界面输入和事件捕获。
    • 当界面上发生某些操作(如按钮点击、文本变化),发射对应的信号。
    • 槽函数里只做简单的转发或数据检查,不进行复杂业务处理。
  2. 业务逻辑层
    封装核心业务逻辑和数据处理,比如:
    • 订单系统中的下单、支付逻辑
    • 计算器项目中的运算逻辑
    • 音乐播放器中的播放控制等
      该层可以通过普通函数或自身的信号与槽与 UI 交互。
  3. 数据层 / 模型层
    • 提供数据的存储、读取、更新机制
    • 当数据发生变化时,可以发射“数据更新”的信号,通知上层 UI 刷新

(2)在项目中合理运用信号与槽

  1. 对象间解耦
    当一个对象的数据变化可能影响多个界面时,可以让对象发射 dataChanged() 信号,而各界面对象各自 connect 到这个信号,槽函数中进行更新,这样避免了在数据对象中硬编码对界面控件的调用。
  2. 模块间通信
    不同模块或子系统之间的调用,也可通过信号与槽的形式实现松耦合。例如:
    • 网络模块 完成数据下载后,发射 downloadFinished(QString filePath) 信号
    • UI 模块 槽函数中接收该路径并显示下载结果
  3. 灵活扩展
    当需要新增功能或界面时,只需额外 connect 对应的信号与槽,不必修改已有类的内部实现。提高可扩展性。

(3)示例:在加法计算器中分离逻辑

  • CalculatorLogic:只提供 double add(double, double) 等函数
  • MainWindow:UI 层捕获按钮点击 -> 发射一个 doAdd(double, double) 信号 -> 由 CalculatorLogic 的槽接收并计算 -> 再发射一个 addResult(double) 信号 -> 主界面槽函数更新 label

这种双向信号-槽结构,可以让界面层和业务逻辑层之间的依赖降到最低。


总结

  • 在小型项目里,信号与槽最直观的应用就是界面控件逻辑函数之间的事件处理,比如按钮点击做运算、数据更新刷新界面等。
  • 在更复杂的项目里,借助信号与槽可以实现分层、解耦,让不同模块之间的交互更灵活、更易维护。
  • 信号与槽并不是“银弹”,但在 Qt 的项目中几乎是“必不可少”的基础工具——只要合理设计,就能让工程代码更加清晰,扩展性更高。

在这里插入图片描述

  1. 📜 [ 声明 ] 由于作者水平有限,本文有错误和不准确之处在所难免,
  2. 本人也很想知道这些错误,恳望读者批评指正!
  3. 我是:勇敢滴勇~感谢大家的支持!
文章来源:https://blog.csdn.net/2301_76677973/article/details/146461508
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ppmy.cn/news/1583728.html

相关文章

cmd命令查看电脑的CPU、内存、存储量

目录 获取计算机硬件的相关信息的命令分别的功能结果展示结果说明获取计算机硬件的相关信息的命令 wmic cpu get name wmic memorychip get capacity wmic diskdrive get model,size,mediaType分别的功能 获取计算机中央处理器(CPU)的名称 获取计算机内存(RAM)芯片的容量…

贪心算法(12))(java)坏了的计算器

题目&#xff1a;在显示着数字 startValue的坏计算器上&#xff0c;我们可以执行以下两种操作&#xff1a; 双倍(Double):将显示屏上的数字乘2; 递减(Decrement):将显示屏上的数字减1. 给定两个整数 startValue 和 target。返回显示数字target所需的最小操作数。 示例1: 输…

el-select 可搜索下拉框 在ios、ipad 无法唤出键盘,造成无法输入

下一篇&#xff1a;el-select 可搜索下拉框&#xff0c;选中选项后&#xff0c;希望立即失去焦点&#xff0c;收起键盘&#xff0c;执行其他逻辑 【效果图】&#xff1a;分组展示选项 >【去界面操作体验】 首先&#xff0c;通过 夸克浏览器的搜索: el-select 在 ipad 输入框…

Docker镜像相关命令(Day2)

文章目录 前言一、问题描述二、相关命令1.查看镜像2.搜索镜像3.拉取镜像4.删除镜像5.镜像的详细信息6.标记镜像 三、验证与总结 前言 Docker 是一个开源的容器化平台&#xff0c;它让开发者能够将应用及其依赖打包到一个标准化的单元&#xff08;容器&#xff09;中运行。在 D…

前端初级面试20道核心题+详细思路解析

一、HTML/CSS 基础篇 ​1. 如何让一个 div 水平居中&#xff1f;至少写出 3 种方法 答题思路&#xff1a; ​方法1&#xff1a;margin: 0 auto; 设置宽度&#xff08;块级元素&#xff09;。​方法2&#xff1a;父级 text-align: center;&#xff0c;子级 display: inline-…

ArkUI之常见基本布局(下)

6.轮播(Swiper) 1.概述 Swiper组件提供滑动轮播显示的能力。Swiper本身是一个容器组件&#xff0c;当设置了多个子组件后&#xff0c;可以对这些子组件进行轮播显示。通常&#xff0c;在一些应用首页显示推荐的内容时&#xff0c;需要用到轮播显示的能力。 针对复杂页面场景…

Modbus协议编程读写流程图大全

读离散量输入 读保持寄存器 读输入寄存器 写单个线圈 写单个寄存器 写多个线圈 写多个寄存器 (0x14) 读文件记录 写文件记录 (0x16) 屏蔽写寄存器 (0x17) 读/写多个寄存器

c++图论(六)之字典序最小欧拉路

要找到字典序最小的欧拉路径,可以采用以下步骤: 确定是否存在欧拉路径: 对于无向图,检查所有顶点的度数。若存在0个奇数度数的顶点,则为欧拉回路;若存在2个奇数度数的顶点,则为欧拉路径,否则不存在。选择起点: 若存在两个奇数度数顶点,选择编号较小的作为起点。若为欧…