QT_QT信号与槽机制

文章目录

QT_QT信号与槽机制

信号与槽是 Qt 框架最核心的通信机制,用于替代传统的 C++ 回调函数,实现对象间松耦合的事件通信。简单来说:信号是"发生了什么",槽是"对此做什么"。


QT信号与槽机制原理(signal & slot)

基本概念

  • 信号(signal):信号就是观察者模式,信号本质是时间,信号展现方式就是函数,当某一件事发生后,则会发出一个信号signal
  • 槽(slot):就是对信号响应的函数,槽就是一个函数,槽函数与普通函数区别:槽函数可以与一个信号管理,当信号发射的时候,关联的槽函数被自动执行处理,信号与槽关联时使用QObject::connect()函数进行实现。

    信号函数只需要声明,不需要定义实现,而槽函数需要定义实现,信号和槽机制是通过函数之间进行相互吊用实现的,相当于每个信号都可以用函数来表示,称为信号函数,每个槽也可以用函数表示,称为槽函数。槽函数可以使用public slots/protected slots/private slot修饰,signalslotsQT开发当中在c++语言基础上扩展的关键词,专门用于指明信号函数和槽函数。

底层原理

信号与槽的实现依赖于 Qt 的元对象系统(Meta-Object System):

  • 类中用 Q_OBJECT 宏标记后,moc 预编译器会扫描头文件,自动生成 moc_xxx.cpp 中间代码。
  • 生成的代码维护一张信号与槽的映射哈希表,connect() 的本质就是向这张表注册映射关系。
  • 信号触发时,Qt 通过哈希表查找所有绑定的槽函数,逐个调用。

QObject::connect()

QObject::connect()Qt信号与槽机制的核心入口,用于建立信号发送者与槽函数之间的关联。

参数

参数 说明
sender 信号发送者,必须是 QObject 子类实例的指针
signal 要监听的信号,Qt5 用 &类名::信号名,Qt4 用 SIGNAL(信号名())
receiver 信号接收者,同样必须是 QObject 子类实例指针(Lambda 版本可省略)
slot / method 响应信号的槽函数,或 Lambda / 可调用对象
type 连接类型,默认 Qt::AutoConnection

返回值

connect() 返回一个 QMetaObject::Connection 句柄,可用于后续判断连接是否成功或断开连接:

cpp 复制代码
auto conn = QObject::connect(btn, &QPushButton::clicked, this, &Widget::onClick);
if (!conn) {
    qDebug() << "连接失败!";
}

将句柄转为 bool即可判断有效性。连接失败的原因通常是信号或槽不存在、参数签名不兼容等。

信号与槽机制连接方式

  • 常用的连接方式

    cpp 复制代码
    conect(object1,SIGNAL(signale),object2,SLOT(slot));
  • 一个信号可以跟另一个信号相连

    一个信号可以连接到另一个信号,当第一个信号被触发时,第二个信号也会自动发射。常用于事件层层向上传递,比如子控件把事件转发给父窗口。信号连接信号时,参数类型必须兼容(发射信号的参数可以比接收信号的参数多,但不能少)。

    cpp 复制代码
    // 子控件的信号 → 父窗口的信号
    connect(childWidget, &ChildWidget::dataReady,
    			parentWidget, &ParentWidget::dataReady);
  • 同一个信号可以跟多个槽连接

    同一个信号可以连接到多个槽函数,信号触发时所有槽按连接顺序依次执行。

    cpp 复制代码
    connect(btn, &QPushButton::clicked, this, &Widget::onClicked_A);
    connect(btn, &QPushButton::clicked, this, &Widget::onClicked_B);
    connect(btn, &QPushButton::clicked, this, &Widget::onClicked_C);

    槽的执行顺序取决于connect()的调用顺序,不应依赖这个顺序来做关键逻辑判断。

  • 同一个槽可以响应多个信号

    多个不同的信号可以连接到同一个槽函数,任何一个信号触发都会执行该槽。常用于多个入口统一处理。

    cpp 复制代码
    	connect(btn1, &QPushButton::clicked, this, &Widget::onAction);
    	connect(btn2, &QPushButton::clicked, this, &Widget::onAction);
    	connect(btn3, &QPushButton::clicked, this, &Widget::onAction);

    在槽函数中可以通过 sender() 判断是哪个对象发出的信号:

    cpp 复制代码
    void Widget::onAction() {
        QPushButton *btn = qobject_cast<QPushButton *>(sender());
        if (btn == btn1) {
            qDebug() << "按钮1被点击";
        } else if (btn == btn2) {
            qDebug() << "按钮2被点击";
        }
    }

    sender() 只能在槽函数内部使用,且跨线程场景下不可靠。


信号与槽的优势

特性 信号与槽 传统回调函数
耦合度 低(松耦合) 高(硬编码依赖)
类型检查 编译期(Qt5+) 编译期,但灵活性差
多线程安全 内置支持 需手动加锁
生命周期管理 自动清理 手动注销
一对多/多对一 原生支持 需自行实现
代码可读性 中等

信号与槽的劣势

缺点 影响程度 规避方式
性能开销 高频场景改用直接调用
调试困难 善用 qDebug 日志、断点追踪
无法返回值 用额外信号或 std::function 回调替代
循环触发 使用 blockSignals() 阻断
重载写法繁琐 qOverload 简化
跨线程易出错 明确线程归属,显式指定连接类型
逻辑碎片化 控制信号链长度,复杂流程用状态机

总结

信号与槽本质上是对观察者模式的标准化封装,把"谁通知谁、怎么通知、线程安全、内存管理"这些复杂问题全部内化到框架中,让开发者只需关注业务逻辑本身。


QT信号与槽机制案例

** 计算圆的体积**

dialog.h代码:

cpp 复制代码
#ifndef DIALOG_H
#define DIALOG_H

#include <QDialog>

// 引入标签,命令按钮等对应头文件
#include <qlabel.h>
#include <qpushbutton.h>
#include <qlineedit.h>

class Dialog : public QDialog
{
    Q_OBJECT

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

private:
    QLabel *label1,*label2;
    QLine *lEdit;
    QPushButton *pbt;

private slots:
    void CalcBallVolume(); // 计算圆球的体积

};
#endif // DIALOG_H

dialog.cpp代码:

cpp 复制代码
#include "dialog.h"

const static double PI = 3.1415;

// QGridLayout表格布局
#include <QGridLayout>

Dialog::Dialog(QWidget *parent)
    : QDialog(parent)
{
    // 创建第一个标签,提示用户输入圆球的半径
    label1 = new QLabel(this);
    label1->setText(tr("请输入圆球的半径"));
    // 创建第二个标签(专门用来显示圆球体积的结果)
    label2 = new QLabel(this);
    // 创建一个编辑框控件(专门用于接收用户的输入圆球半径的值)
    lEdit = new QLineEdit(this);
    // 创建命令按钮
    pbt = new QPushButton(this);
    pbt->setText(tr("计算圆球体积"));

    // 表格布局
    QGridLayout *mlay = new QGridLayout(this);
    mlay->addWidget(label1,0,0);
    mlay->addWidget(lEdit,0,1);
    mlay->addWidget(label2,1,0);
    mlay->addWidget(pbt,1,1);

    // 绑定槽函数
    connect(lEdit,SIGNAL(textChanged(QString)),this,SLOT(CalcBallVolume()));
}

Dialog::~Dialog()
{
}

// 计算圆球的体积
void Dialog::CalcBallVolume(){
    bool isLoop;
    QString tempStr;
    QString valueStr = lEdit->text();

    int valueInt = valueStr.toInt(&isLoop);
    double dVsum = 4.0/3.0*PI*valueInt*valueInt*valueInt;
    label2->setText(tempStr.setNum(dVsum));

}

效果:

相关推荐
辞旧 lekkk1 小时前
【Qt】 系统相关:事件与定时器
开发语言·qt·学习·萌新
Wang's Blog1 小时前
Java框架快速入门: Spring Bean核心配置与实例化详解
java·开发语言·spring
HAPPY酷1 小时前
【ROS2】VMware + Docker 运行 Gazebo 崩溃与“假死”排查实录
qt·docker·容器
aaPIXa6221 小时前
C++ TensorRT Edge-LLM 边缘推理框架:从原理到实战
开发语言·c++·edge
阿pin2 小时前
Java随笔-GC收集器工作机制
java·开发语言·jvm·gc
2601_962440842 小时前
计算机毕业设计之jsp旅游网站管理系统
java·开发语言·算法·课程设计·旅游·推荐算法
DFT计算杂谈2 小时前
vasp如何计算金属的铁电极化强度?
数据库
默默敲代码的徐哥儿2 小时前
八股文整理——后端
java·开发语言·spring boot·后端·学习
EterNity_TiMe_2 小时前
别让告警变成噪音:用Prometheus和Alertmanager搭一套可远程查看的监控系统
数据库·人工智能·ai·postgresql·prometheus·cpolar