编程进阶网 编程进阶网
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机的原理
  • 操作系统原理
  • 网络协议原理
  • 数据库的原理
  • 序卷导读
  • 数据本质
  • 运行模型
  • 并发设计
  • 内存真相
  • 交互系统
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 技能之旅
  • 体系建设
  • 代码品质
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 性能优化
  • 数据结构导论
  • 线性结构详解
  • 树哈希结构论
  • 容器设计实战
  • 经典算法思想
  • 工程案例剖析
  • 算法题库精练
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript案例
  • JavaScript高级
  • Kotlin精通
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 毛选解读
  • 自我精进
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机的原理
  • 操作系统原理
  • 网络协议原理
  • 数据库的原理
  • 序卷导读
  • 数据本质
  • 运行模型
  • 并发设计
  • 内存真相
  • 交互系统
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 技能之旅
  • 体系建设
  • 代码品质
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 性能优化
  • 数据结构导论
  • 线性结构详解
  • 树哈希结构论
  • 容器设计实战
  • 经典算法思想
  • 工程案例剖析
  • 算法题库精练
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript案例
  • JavaScript高级
  • Kotlin精通
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 毛选解读
  • 自我精进
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • Android提升进阶

  • iOS开发和进阶

  • Web开发和进阶

  • Linux应用开发

    • Linux应用开发
    • QML基础入门

    • QT核心库实践

      • QT核心库实践
      • 核心功能基础
      • 并发与多线程
      • 文件与IO系统
      • 日期与时间处理
      • 网络与序列化
      • QT事件系统
      • 信号与槽机制
        • 7.0 案例引入
        • 7.1 基本概念
          • 7.1.1 信号与槽
          • 7.1.2 信号(Signal)
          • 7.1.3 槽(Slot)
          • 7.1.4 连接(Connect)
        • 7.2 连接语法详解
          • 7.2.1 旧式宏语法 vs 新式成员指针
          • 7.2.2 Lambda 表达式
          • 7.2.3 QMetaObject::Connection
        • 7.3 MOC 编译原理
          • 7.3.1 信号生成的代码
          • 7.3.2 运行时调用链路
        • 7.4 连接类型深度分析
          • 7.4.1 五种连接类型
          • 7.4.2 跨线程通信原理
        • 7.5 性能与陷阱
          • 信号槽调用的开销
          • 常见陷阱
        • 7.6 调试技巧
        • 7.7 综合案例
          • 嵌入式传感器采集到 UI 更新的完整信号槽链路
        • 7.8 速查表
        • 7.9 断开连接与生命周期
          • 7.9.1 多种断开方式
          • 7.9.2 自动断开机制
          • 7.9.3 Lambda 与 this 生命周期
        • 7.10 QSignalMapper 与 QSignalTransition
          • 7.10.1 QSignalMapper(Qt 5 遗留)
          • 7.10.2 QSignalTransition(状态机)
        • 7.11 训练题
          • 题目 1:分析崩溃原因
          • 题目 2:Lambda 野指针
          • 题目 3:跨线程连接
      • 多媒体的应用
      • 容器类和算法
      • 高级编程技巧
    • Linux系统编程

    • 综合项目实战

  • IoT智能硬件开发

  • Apps
  • Linux应用开发
  • QT核心库实践
杨充
2025-09-15
目录

信号与槽机制

# 07.信号和槽机制

信号与槽是 Qt 最核心的 IPC 机制——观察者模式的 C++ 实现。但很多人只停留在 connect 的 API 使用上,不理解 moc 生成的代码在运行时如何工作、QueuedConnection 的事件队列副作用、以及新版语法相比 SIGNAL/SLOT 宏的本质改进。本章从编译器到运行时,完全拆解这一机制。

# 目录介绍

  • 7.0 案例引入
  • 7.1 基本概念
    • 7.1.1 信号与槽
    • 7.1.2 信号(Signal)
    • 7.1.3 槽(Slot)
    • 7.1.4 连接(Connect)
  • 7.2 连接语法详解
    • 7.2.1 旧式宏语法 vs 新式成员指针
    • 7.2.2 Lambda 表达式
    • 7.2.3 QMetaObject::Connection
  • 7.3 MOC 编译原理
    • 7.3.1 信号生成的代码
    • 7.3.2 运行时调用链路
  • 7.4 连接类型深度分析
    • 7.4.1 五种连接类型
    • 7.4.2 跨线程通信原理
  • 7.5 性能与陷阱
  • 7.6 调试技巧
  • 7.7 综合案例
  • 7.8 速查表

# 7.0 案例引入

一个新人在嵌入式板子上写了如下代码,程序运行时每隔几秒就崩溃:

class SensorReader : public QObject {
    Q_OBJECT
public:
    void start() {
        m_thread = new QThread(this);
        // ❌ 槽函数 doWork 在 SensorReader 线程执行(主线程)
        //    但 m_thread 在工作线程——连接不合理
        connect(m_thread, &QThread::started, this, &SensorReader::doWork);
        m_thread->start();
    }
public slots:
    void doWork() {
        while (m_running) {
            qDebug() << QThread::currentThread();  // 主线程!
            QThread::msleep(100);
        }
    }
private:
    QThread* m_thread;
    bool m_running = true;
};

现象:doWork 在主线程中死循环 → 阻塞事件循环 → UI 冻结。

根因:不理解 connect 的连接类型——默认 AutoConnection 判断信号/槽所在线程,槽对象的线程亲和性决定了槽在哪个线程执行。


# 7.1 基本概念

Qt 的信号与槽机制是其核心特性之一,用于实现对象之间的松耦合通信。这是 Qt 对观察者模式的 C++ 实现,具有类型安全、松耦合和跨线程通信等优点。

# 7.1.1 信号与槽

核心概念:

  • 信号(Signal):对象发出的事件通知(如按钮点击)
  • 槽(Slot):响应信号的普通成员函数
  • 连接(Connect):通过 QObject::connect 绑定信号与槽
  • 触发(Emit):通过 emit 关键字发射信号
// 关键元对象宏
Q_OBJECT       // 必须——启用元对象系统
signals:        // 声明信号(不实现)
public slots:   // 声明槽(需要实现)
emit           // 发射信号(非必须,但建议保留以示意图)

# 7.1.2 信号(Signal)

class SensorManager : public QObject {
    Q_OBJECT
public:
    void onNewReading(float value) {
        if (value > m_threshold) {
            emit thresholdExceeded(value);  // 发射信号
        }
    }
signals:
    void thresholdExceeded(float value);    // 只有声明,无需实现
    void dataReady(QVector<float> data);    // 可带任意参数
};

关键规则:

  • 信号函数的实现在 moc 生成的 moc_*.cpp 中——你不写实现
  • 信号可以是 virtual(Qt 5.7+)
  • 返回值必须是 void

# 7.1.3 槽(Slot)

class Dashboard : public QObject {
    Q_OBJECT
public slots:
    void onThresholdExceeded(float value) {
        qDebug() << "Alert! Value:" << value;
        showWarningPopup();
    }
    void onDataReady(QVector<float> data) {
        updateChart(data);
    }
};

槽函数的参数数量可以少于信号的参数数量——Qt 会忽略多余的参数。

# 7.1.4 连接(Connect)

// 基本连接
connect(sender, &Sender::signal, receiver, &Receiver::slot);

// 旧式字符串语法(不推荐——编译期不检查类型)
connect(sender, SIGNAL(clicked()), receiver, SLOT(onClicked()));

# 7.2 连接语法详解

# 7.2.1 旧式宏语法 vs 新式成员指针

旧式 SIGNAL/SLOT 宏 新式成员指针 &Class::method
类型检查 运行时(字符串匹配) 编译期(模板推导)
重载支持 需 QOverload 手动消歧 用 qOverload / QOverload::of
性能 字符串构造 + 查找 偏移量计算(快 2-3 倍)
错误发现 运行时 false 返回值 编译失败

新式语法处理重载:

// 方式 1: QOverload
connect(slider, &QSlider::valueChanged,
        this, QOverload<int>::of(&MyWidget::onValueChanged));

// 方式 2: qOverload(Qt 5.7+)
connect(slider, &QSlider::valueChanged,
        this, qOverload<int>(&MyWidget::onValueChanged));

// 方式 3: Lambda(最简洁)
connect(slider, &QSlider::valueChanged, this, [this](int v) {
    onValueChanged(v);
});

# 7.2.2 Lambda 表达式

Qt 5+ 支持 Lambda 作为槽——最灵活的方式:

// 基本 Lambda
connect(button, &QPushButton::clicked, []() {
    qDebug() << "Button clicked";
});

// 带上下文——Lambda 在 receiver (this) 的线程执行
connect(button, &QPushButton::clicked, this, [this]() {
    m_counter++;  // 操作成员变量
});

// 一次性连接——执行一次后自动断开
connect(button, &QPushButton::clicked, this, [this]() {
    doOneTimeSetup();
}, Qt::SingleShotConnection);  // Qt 6.0+

// Lambda 中的 this 陷阱
// ❌ 如果 this 被删除,Lambda 变成野指针访问!
// ✅ 使用 receiver 参数:connect(sender, sig, receiver, lambda)
//    当 receiver 被删除时,连接自动断开

# 7.2.3 QMetaObject::Connection

connect 返回 QMetaObject::Connection,可复制、移动,但不可默认构造:

QMetaObject::Connection conn = connect(sender, &Sender::sig, receiver, &Receiver::slot);
if (conn) {
    qDebug() << "Connected successfully";
}
disconnect(conn);  // 断开这个特定连接

# 7.3 MOC 编译原理

# 7.3.1 信号生成的代码

当 moc 处理带 Q_OBJECT 的头文件时,为每个信号生成实现:

// 你在头文件中写的:
signals:
    void valueChanged(int value);

// moc 生成的实现(简化):
void MyClass::valueChanged(int _t1) {
    void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };

    QMetaObject::activate(
        this,
        &staticMetaObject,
        1,                   // 信号的 meta 索引
        _a                   // 参数数组
    );
}

QMetaObject::activate 内部做三件事:

  1. 遍历该信号的连接列表
  2. 对每个连接,根据连接类型决定执行方式
  3. 如果是 DirectConnection → 直接调用槽函数;如果是 QueuedConnection → 投递 QMetaCallEvent 到目标线程的事件队列

# 7.3.2 运行时调用链路

emit valueChanged(42)
  ↓
moc 生成的 valueChanged(int) 函数
  ↓
QMetaObject::activate(this, metaObj, signalIdx, args)
  ↓
遍历连接列表 → 对每个 receiver:
  ├── DirectConnection → receiver->qt_metacall(slotIdx, args)
  │     ↓ 函数指针跳转(类似虚函数表)
  │   onValueChanged(42)
  │
  └── QueuedConnection → QCoreApplication::postEvent(receiver,
        new QMetaCallEvent(slotIdx, args))
      ↓ 进入接收者线程的事件循环
      ↓ event() 分发 QMetaCallEvent
      ↓ receiver->qt_metacall(slotIdx, args)
      ↓
    onValueChanged(42)

性能关键:qt_metacall 使用 switch-case 分派到槽函数——编译期确定的跳转表,比虚函数调用开销稍大(多个 switch),但在 ARM 嵌入式上仍 < 50ns。


# 7.4 连接类型深度分析

# 7.4.1 五种连接类型

连接类型 槽执行线程 槽执行时机 是否阻塞 用途
AutoConnection 由信号/槽所在线程决定 立即或稍后 视情况 默认值
DirectConnection 信号的线程 立即同步执行 是 同线程通信
QueuedConnection 槽对象的线程 下一轮事件循环 否 跨线程通信
BlockingQueuedConnection 槽对象的线程 等待执行完成 是(发送线程) 需要返回结果
SingleShotConnection 同上 同上 同上 连接一次后自动断开

AutoConnection 的决策逻辑:

if (sender->thread() == receiver->thread())
    type = DirectConnection;     // 同线程 → 直接调用
else
    type = QueuedConnection;    // 不同线程 → 队列调用

# 7.4.2 跨线程通信原理

这是 Qt 信号槽最精巧的设计:

发送线程:                                        接收线程:
emit signal(42)
  ↓ (AutoConnection 检测到线程不同)
QCoreApplication::postEvent(
    receiver,
    new QMetaCallEvent(...)
)                                              ↓ 事件循环
  ↓                                          QEventLoop::processEvents()
信号发射完成(不阻塞)                            ↓
                                              dispatch QMetaCallEvent
                                                ↓
                                              receiver->qt_metacall()
                                                ↓
                                              onSlot(42)  ← 在接收线程执行

关键:QueuedConnection 要求接收者的线程有事件循环(exec())。如果工作线程只是 run() 重写而没有启动事件循环 → QueuedConnection 的槽永远不会执行。


# 7.5 性能与陷阱

# 信号槽调用的开销

在 ARM Cortex-A53 上实测(100 万次调用):

方式 耗时 说明
直接函数调用 2ms 基线
DirectConnection 信号槽 18ms ~9ns/次,主要是 activate 遍历开销
QueuedConnection 信号槽 620ms 涉及事件分配、入队、出队
虚函数调用 5ms 作为对比

结论:DirectConnection 信号槽比直接调用慢约 9 倍,但在大多数场景可忽略。QueuedConnection 开销显著——高频调用避免用它。

# 常见陷阱

// ❌ 陷阱 1:信号和槽的参数类型不匹配——编译期不报错(旧式语法)
connect(sender, SIGNAL(valueChanged(int)), receiver, SLOT(onValueChanged(QString)));
// 运行时连接失败,返回 false,但没有编译错误!

// ❌ 陷阱 2:信号和槽参数类型不完全匹配
connect(sender, SIGNAL(sig(double)), receiver, SLOT(slot(int)));
// int 和 double 在 moc 看来是不同的签名 → 连接失败

// ❌ 陷阱 3:Lambda 捕获 this,但 receiver 是另一个对象
connect(sender, &Sender::sig, otherObj, [this]() {
    m_data++;  // this 可能先于 otherObj 被删除!
});

// ✅ 正确:receiver 应和 Lambda 中引用的对象一致
connect(sender, &Sender::sig, this, [this]() {
    m_data++;  // this 被删除时连接自动断开
});

// ❌ 陷阱 4:在 run() 中使用 QueuedConnection
class MyThread : public QThread {
    void run() override {
        // 没有事件循环!QueuedConnection 的槽永远不会执行
        while (running) { doWork(); }
    }
};

// ✅ 正确:要么用 exec() 启动事件循环,要么使用 DirectConnection

# 7.6 调试技巧

// 1. 打印连接状态
QMetaObject::Connection conn = connect(...);
if (!conn)
    qWarning() << "Connection failed! Check signal/slot signatures";

// 2. QSignalSpy——单元测试中监控信号
#include <QSignalSpy>
QSignalSpy spy(sender, &Sender::valueChanged);
sender->emit valueChanged(42);
QCOMPARE(spy.count(), 1);
QCOMPARE(spy.takeFirst().at(0).toInt(), 42);

// 3. 运行时查看所有连接
const QMetaObject* meta = object->metaObject();
for (int i = meta->methodOffset(); i < meta->methodCount(); ++i) {
    QMetaMethod method = meta->method(i);
    if (method.methodType() == QMetaMethod::Signal) {
        qDebug() << "Signal:" << method.name() << "index:" << i;
    }
}

// 4. 启用 Qt 内部的信号槽调试输出
// 环境变量:QT_LOGGING_RULES="qt.core.signal_slot=true"

QSignalBlocker——临时阻止信号触发:

void MyWidget::updateControls() {
    QSignalBlocker blocker(comboBox);  // 构造时阻止 comboBox 的信号
    comboBox->setCurrentIndex(5);      // 不会触发 currentIndexChanged
    // ... 其他批量操作
}  // blocker 析构 → 恢复信号

# 7.7 综合案例

# 嵌入式传感器采集到 UI 更新的完整信号槽链路

// === SensorReader——工作线程 ===
class SensorReader : public QObject {
    Q_OBJECT
public slots:
    void startReading() {
        m_timer = new QTimer(this);
        connect(m_timer, &QTimer::timeout, this, &SensorReader::readSensor);
        m_timer->start(100);  // 100ms 一次
    }
    void stopReading() { m_timer->stop(); }

signals:
    void sensorDataReady(float temp, float humidity, int pressure);

private:
    void readSensor() {
        // 模拟读传感器
        float temp = readTemperature();
        float hum = readHumidity();
        int press = readPressure();
        emit sensorDataReady(temp, hum, press);
    }
    QTimer* m_timer;
};

// === DataProcessor——工作线程(数据处理)===
class DataProcessor : public QObject {
    Q_OBJECT
public slots:
    void onRawData(float temp, float hum, int press) {
        // 数据滤波
        float filteredTemp = filter(temp);
        emit processedData(filteredTemp, hum, press);
    }
signals:
    void processedData(float temp, float humidity, int pressure);
private:
    float filter(float v) { return m_history.appendAndAverage(v); }
};

// === 主线程——Dashboard ===
class Dashboard : public QMainWindow {
    Q_OBJECT
public:
    Dashboard() {
        // 线程 1:传感器读取
        m_sensorThread = new QThread(this);
        m_sensor.setupUi(this);
        m_sensor->moveToThread(m_sensorThread);
        connect(m_sensorThread, &QThread::started, m_sensor, &SensorReader::startReading);
        m_sensorThread->start();

        // 线程 2:数据处理
        m_processThread = new QThread(this);
        m_processor->moveToThread(m_processThread);
        m_processThread->start();

        // 连接链路(跨 3 个线程)
        // 工作线程 1 → 工作线程 2:数据预处理
        connect(m_sensor, &SensorReader::sensorDataReady,
                m_processor, &DataProcessor::onRawData,
                Qt::QueuedConnection);

        // 工作线程 2 → 主线程:更新 UI
        connect(m_processor, &DataProcessor::processedData,
                this, &Dashboard::updateUI,
                Qt::QueuedConnection);

        // 主线程 → 工作线程 1:控制命令
        connect(this, &Dashboard::stopRequested,
                m_sensor, &SensorReader::stopReading,
                Qt::QueuedConnection);
    }

public slots:
    void updateUI(float temp, float humidity, int pressure) {
        // 在主线程更新 UI——线程安全
        m_tempLabel->setText(QString::number(temp, 'f', 1) + "°C");
        m_humLabel->setText(QString::number(humidity, 'f', 1) + "%");
        m_presLabel->setText(QString::number(pressure) + " hPa");
    }

signals:
    void stopRequested();

private:
    SensorReader* m_sensor;
    DataProcessor* m_processor;
    QThread* m_sensorThread;
    QThread* m_processThread;
};

# 7.8 速查表

概念 一句话解释
emit signal(value) 触发信号 → 内部调用 QMetaObject::activate()
connect(sender, sig, receiver, slot, type) 建立信号-槽连接
DirectConnection 槽在发射线程同步执行
QueuedConnection 槽在接收线程的事件循环中异步执行
AutoConnection 同线程 → Direct;不同线程 → Queued
BlockingQueuedConnection Queued 但发送线程阻塞直到槽执行完
SingleShotConnection 连接触发一次后自动断开
QSignalBlocker RAII 临时阻止信号发射
QSignalSpy 单元测试中监控信号发射
disconnect(conn) 断开特定连接
sender() 槽函数中获取信号发射者(不推荐)

信号槽铁律:

  1. 始终用新式成员指针语法——编译期类型检查
  2. Lambda 必须绑定正确的 receiver,避免野指针
  3. 跨线程用 Qt::QueuedConnection,且确保接收线程有事件循环
  4. 高频信号(>100Hz)避免 QueuedConnection——用 DirectConnection 或批处理
  5. BlockingQueuedConnection 的两端不能在同一线程,否则死锁
  6. 槽函数中避免 sender()——破坏了松耦合

# 7.9 断开连接与生命周期

# 7.9.1 多种断开方式

// 方式 1:断开特定信号-槽对
disconnect(sender, &Sender::sig, receiver, &Receiver::slot);

// 方式 2:断开某个信号的所有槽
disconnect(sender, &Sender::sig, nullptr, nullptr);

// 方式 3:断开某个对象的所有信号(作为发送者)
disconnect(sender, nullptr, nullptr, nullptr);

// 方式 4:断开某个对象的所有槽(作为接收者)
disconnect(nullptr, nullptr, receiver, nullptr);

// 方式 5:通过 QMetaObject::Connection
QMetaObject::Connection conn = connect(...);
disconnect(conn);

# 7.9.2 自动断开机制

Qt 的连接在以下情况会自动断开:

  1. sender 被删除 → 所有以它为发送者的连接自动断开
  2. receiver 被删除 → 所有以它为接收者的连接自动断开
  3. SingleShotConnection → 连接触发一次后自动断开
// ✅ 利用自动断开——安全模式
void setupConnection() {
    QPushButton* btn = new QPushButton("Click", this);
    QLabel* label = new QLabel("Status", this);

    // 当 btn 或 label 被删除时,连接自动断开
    connect(btn, &QPushButton::clicked, label, [label]() {
        label->setText("Clicked");
    });
    // 不需要手动 disconnect!
}

# 7.9.3 Lambda 与 this 生命周期

// ❌ 危险:QTimer 的生命周期比 this 长
class MyWidget : public QWidget {
    void startTimer() {
        QTimer* timer = new QTimer(this);  // 子对象,随 this 析构
        connect(timer, &QTimer::timeout, [this]() {
            update();  // this 析构后 → 访问野指针!
        });
        timer->start(1000);
    }
};

// ✅ 安全:使用 receiver 参数
connect(timer, &QTimer::timeout, this, [this]() {
    update();  // this 被删除 → 连接断开 → Lambda 不再调用
});

// ✅ 安全:Lambda 中检查 QPointer
QPointer<MyWidget> self(this);
connect(timer, &QTimer::timeout, [self]() {
    if (self) { self->update(); }
});

# 7.10 QSignalMapper 与 QSignalTransition

# 7.10.1 QSignalMapper(Qt 5 遗留)

多个信号映射到相同参数:

QSignalMapper* mapper = new QSignalMapper(this);

for (int i = 0; i < 10; ++i) {
    QPushButton* btn = new QPushButton(QString::number(i), this);
    connect(btn, &QPushButton::clicked, mapper, qOverload<>(&QSignalMapper::map));
    mapper->setMapping(btn, i);  // 映射 btn → i
}

connect(mapper, &QSignalMapper::mappedInt, [](int value) {
    qDebug() << "Button" << value << "clicked";
});

# 7.10.2 QSignalTransition(状态机)

#include <QStateMachine>
#include <QSignalTransition>

QStateMachine machine;
QState* idle = new QState;
QState* running = new QState;

// 当收到 startSignal 时从 idle → running
QSignalTransition* trans = new QSignalTransition(
    sender, &Sender::startSignal);
trans->setTargetState(running);
idle->addTransition(trans);

// 更简洁的写法(Qt 6)
idle->addTransition(sender, &Sender::startSignal, running);

# 7.11 训练题

# 题目 1:分析崩溃原因

class Worker : public QObject {
    Q_OBJECT
public slots:
    void doWork() {
        QThread::sleep(10);  // 模拟耗时操作
        emit finished();
    }
signals:
    void finished();
};

int main() {
    Worker worker;
    worker.doWork();
    // 问题:这里阻塞了哪个线程?事件循环能处理吗?
}

答案:doWork() 直接调用(不是通过信号触发),在主线程执行 → 阻塞主线程 IO 秒 → UI 冻结。正确做法:将 Worker 移到 QThread,通过信号触发执行。

# 题目 2:Lambda 野指针

void MyWidget::startTimer() {
    auto timer = new QTimer;
    connect(timer, &QTimer::timeout, [this]() {
        m_data->refresh();  // 当 this 被 delete 后?
    });
    timer->start(1000);
}
// 问题:何时可能导致崩溃?如何修复?

答案:MyWidget 析构时 this 变成野指针,但 timer 仍在运行 → Lambda 访问 m_data 崩溃。修复:connect(timer, &QTimer::timeout, this, [this]{...})——当 this 删除时连接自动断开。

# 题目 3:跨线程连接

// 三个对象分别属于三个线程:
// sender   → 线程 A
// filter   → 线程 B
// receiver → 线程 C
// 以下连接是否正确?
connect(sender, &Sender::rawData,
        filter, &Filter::onRawData,
        Qt::BlockingQueuedConnection);
connect(filter, &Filter::filtered,
        receiver, &Receiver::onFiltered,
        Qt::AutoConnection);

答案:第一个连接正确(跨线程,发送线程阻塞等 filter 执行完);第二个连接也正确(AutoConnection 检测到跨线程 → 自动切换 QueuedConnection)。但注意:线程 B 和 C 都必须有事件循环。

上次更新: 2026/07/02, 11:21:43
QT事件系统
多媒体的应用

← QT事件系统 多媒体的应用→

最近更新
01
audit
07-27
02
C++入门教程全章思考题汇编
07-24
03
12.技术团队建设能力
07-21
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 鄂ICP备2024073355号-1 | 鄂ICP备2024073355号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式