QT_QT信号与槽机制

文章目录

QT_QT信号与槽机制

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


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

基本概念

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

    信号函数只需要声明,不需要定义实现,而槽函数需要定义实现,信号和槽机制是通过函数之间进行相互吊用实现的,相当于每个信号都可以用函数来表示,称为信号函数,每个槽也可以用函数表示,称为槽函数。槽函数可以使用public slots/protected slots/private slot修饰,signal和slots是QT开发当中在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));

}

效果:

相关推荐
白帽攻防录2 小时前
SRC 挖洞:PostScript 打印机驱动双 RCE,打印服务器怎么变成内网跳板
开发语言·网络安全·php·内网安全
聚美智数2 小时前
智能结构化识别-图片结构解析-图像结构化解析-照片结构化解析
android·java·数据库
十年Java程序媛2 小时前
Java枚举Enum完整教程|替代静态常量,开发与面试要点
java·开发语言·spring boot·后端·面试
瀚高PG实验室2 小时前
瀚高数据库常用系统表查询入门指南(附常用SQL)
数据库·sql·postgresql·瀚高数据库
ben9518chen2 小时前
mysql数据库基本操作
数据库·mysql
2603_965148112 小时前
电商选品调研:通过关键词搜索API获取市场热销商品,结合商品详情API分析竞品卖点与定价
开发语言·数据库·人工智能·python·信息可视化
土豆.exe3 小时前
nf_tables 的 verdict 被读了两次:一个十年未闭合的位域错配
java·数据库·网络安全·缓存
xixiaoyunya3 小时前
Python 异步编程入门:用 asyncio 轻松实现并发任务
开发语言·python·php
缓慢更新3 小时前
表单引擎的存储建模:宽表与文档库的取舍
运维·服务器·网络·数据库·低代码·oracle·适配器模式
学逆向的3 小时前
C 循环结构的执行效率分析
c语言·开发语言·数据结构·网络安全