
欢迎来到【Qt开发宝典】微信公众号【知识宝库】!
👉欢迎关注【Qt开发宝典】微信公众号
🚀定期分享更多Linux C++/Qt等技术干货!!!
一、【工程项目运行】
【 运行界面】

Qt 的信号与槽机制是 Qt 对象模型的核心能力,本质上是一套基于元对象系统(Meta-Object System)的类型安全回调机制。它既可以用于同一线程内对象之间的通信,也可以用于跨线程异步通信。
1. 元对象系统
Qt 的信号与槽依赖以下几个核心组件:
-Object:所有支持信号与槽的类通常都继承自 QObject。
-Q_OBJECT:启用 Qt 元对象能力的宏。
-moc:Meta-Object Compiler,元对象编译器。
-QMetaObject:保存类名、信号、槽、属性等元信息的数据结构。
当一个类中使用了 Q_OBJECT 宏后,Qt 的 moc 工具会在编译前扫描头文件,并生成额外的 C++ 代码,例如:
- 类的元对象信息。
- 信号索引。
- 槽函数索引。
- 动态调用入口。
- 类型信息和参数描述。
因此,信号与槽并不是普通 C++ 语言原生提供的能力,而是 Qt 通过预编译工具和运行时元对象系统扩展出来的机制。
2. Q_OBJECT 的作用Q_OBJECT 宏会为类注入 Qt 元对象系统所需的声明,使该类具备以下能力:
- 支持信号与槽。
- 支持运行时类型信息。
- 支持动态属性系统。
- 支持 qobject_cast。
- 支持 QMetaObject::invokeMethod。
- 支持对象间的自动连接和反射调用。
如果类中声明了 signals 或 slots,但没有写 Q_OBJECT,通常会导致链接错误或信号与槽无法正常工作。
3. moc 生成代码的基本思想
以本项目中的 Worker 为例:
classWorker : publicQObject {
Q_OBJECT
public slots:
voiddoWork();
signals:
voidprogress(intvalue);
voidfinished();
};
moc 会为它生成类似如下功能的辅助代码:
voidWorker::progress(intvalue) {
void*args[] ={ nullptr,&value };
QMetaObject::activate(this,&staticMetaObject, signal_index, args);
}
这段伪代码说明:
- 发射信号本质上会调用 QMetaObject::activate()。
- Qt 根据当前对象、信号索引查找所有已连接的槽。
- 然后按照连接类型决定是立即调用槽函数,还是把调用封装为事件投递到目标线程。
4. connect 的底层含义
项目中有如下连接:
connect(this,&MainWindow::startTask, worker,&Worker::doWork, Qt::QueuedConnection);
connect(worker,&Worker::progress,this,&MainWindow::onTaskProgress, Qt::QueuedConnection);
connect(worker,&Worker::finished,this,&MainWindow::onTaskFinished, Qt::QueuedConnection);
connect 的作用是建立一条连接记录,大致包含:
- 发送者对象指针。
- 信号的元信息或函数指针。
- 接收者对象指针。
- 槽函数的元信息或函数指针。
- 连接类型。
这些连接关系会被 Qt 保存在发送者对象内部的连接列表中。当信号发射时,Qt 会遍历这些连接记录,并依次触发对应的槽函数。
5. 信号发射时发生了什么
当执行:
emitprogress(i);
需要注意:emit 不是 C++ 关键字,它在 Qt 中通常只是一个空宏,用于增强代码可读性。也就是说:
emitprogress(i);
从 C++ 编译角度看,近似等价于:
progress(i);
真正起作用的是 moc 为 progress(int) 生成的函数体。该函数内部会调用 Qt 元对象系统,找到所有连接到 progress(int) 的槽函数并触发它们。
6. 连接类型
Qt 常见连接类型包括:
Qt::AutoConnection默认类型:若发送者和接收者在同一线程,则直接调用;否则使用队列调用。
Qt::DirectConnection:立即在信号发射线程中调用槽函数。
Qt::QueuedConnection:将调用封装成事件,投递到接收者所属线程的事件队列中。
Qt::BlockingQueuedConnection:队列调用,但发送线程会阻塞等待槽函数执行完成。
Qt::UniqueConnection:避免重复建立相同连接。
Qt::SingleShotConnection:槽函数执行一次后自动断开连接。
本项目中显式使用了 Qt::QueuedConnection,原因是主窗口对象和 Worker 对象位于不同线程。这样可以确保:
-Worker::doWork() 在工作线程中执行。
-MainWindow::onTaskProgress() 在主线程中执行。
- UI 控件始终由主线程更新。
7. 直接连接与队列连接的区别
直接连接
如果使用 Qt::DirectConnection,槽函数会在信号发射的线程中立即执行。
示意:
线程 A 发射信号↓线程 A 立即执行槽函数
优点是调用开销较低,执行顺序直观。缺点是跨线程时容易误操作其他线程中的对象,尤其是 UI 控件。
队列连接
如果使用 Qt::QueuedConnection,Qt 不会立即调用槽函数,而是将调用信息包装成事件,投递到接收者对象所属线程的事件队列。
示意:
线程 A 发射信号↓Qt 创建调用事件↓投递到线程 B 的事件队列↓线程 B 的事件循环取出事件↓线程 B 执行槽函数
这种方式适合跨线程通信,也是 Qt 多线程 UI 程序中最常见、最安全的方式。
8. 跨线程通信为什么安全
Qt 中每个 QObject 都有线程归属,也就是 thread() 返回的线程对象。
当调用:
worker->moveToThread(workerThread);
worker 的线程归属变为 workerThread。之后,如果使用队列连接触发 Worker::doWork(),该槽函数会在 workerThread 的事件循环中执行。
对于本项目:
MainWindow 属于 GUI 主线程
Worker 属于后台工作线程
因此:
- 主线程发出 startTask()。
- Qt 将 Worker::doWork() 调用投递到工作线程。
- 工作线程执行任务并发出 progress(int)。
- Qt 将 MainWindow::onTaskProgress(int) 调用投递回主线程。
- 主线程更新进度条和状态文本。
这样既避免了界面阻塞,又避免了后台线程直接操作 UI。
9. 参数传递机制
信号与槽传参时,Qt 会根据连接类型采用不同方式:
- 直接连接:参数通常以普通函数调用方式传递。
- 队列连接:参数需要被复制或移动到事件对象中,随后在线程事件循环中再取出调用。
因此,跨线程队列连接传递自定义类型时,通常需要使用:
Q_DECLARE_METATYPE(MyType)
qRegisterMetaType<MyType>("MyType");
本项目中的信号参数是 int,属于 Qt 已知基础类型,所以不需要额外注册。
10. 自动断开连接
Qt 的信号与槽连接具有对象生命周期感知能力:
- 当发送者对象销毁时,相关连接会自动失效。
- 当接收者对象销毁时,相关连接也会自动断开。
这降低了悬空回调指针的风险,也是 Qt 信号与槽相比传统函数指针回调更安全的原因之一。
- 使用 QMainWindow 构建标准桌面应用框架。
- 使用菜单栏和工具栏触发后台任务。
- 使用 QThread 承载后台任务对象。
- 使用信号与槽跨线程传递任务进度。
- 使用 QProgressBar 实时展示任务进度。
- 使用 QStatusBar 显示当前任务状态。
- 使用 Qt 样式表实现简洁现代的界面效果。
- 使用 Qt 资源文件加载窗口和菜单图标。
主窗口类,负责应用的整体组织:
- 创建中央控件。
- 创建菜单栏和工具栏。
- 设置窗口样式和状态栏。
- 初始化工作线程。
- 发送启动任务信号。
- 接收后台任务进度并更新界面。
关键通信流程:
用户点击“启动后台任务”↓MainWindow::onStartTask()↓emit startTask()↓Worker::doWork()↓emit progress(value)↓MainWindow::onTaskProgress(value)↓CentralWidget::updateProgress(value)
3. CentralWidget
中央内容区域,负责显示任务状态:
- 标题文本:后台任务执行进度。
- 进度条:显示 0 到 100 的任务进度。
- 状态标签:显示当前进度百分比。
- 提示文本:提示用户通过菜单或工具栏启动任务。
4. Worker
后台任务类,继承自 QObject,用于模拟耗时任务:
- doWork() 在工作线程中执行。
- 每隔 50ms 发送一次进度更新信号。
- 进度从 0 增加到 100。
- 任务完成后发送 finished() 信号。
// main.cpp - Qt应用程序入口文件// 包含主窗口类的头文件#include"mainwindow.h"// 包含Qt应用程序核心头文件#include<QApplication>// 程序主入口函数intmain(int argc, char *argv[]){// 创建Qt应用程序对象,管理整个应用程序的生命周期// argc 和 argv 是命令行参数,Qt可能使用它们初始化某些设置QApplication a(argc, argv);// 设置全局应用程序样式为Fusion风格(可选)// Fusion是Qt提供的一个现代跨平台样式,比原生样式更统一美观QApplication::setStyle("Fusion");// 创建主窗口对象MainWindow w;// 设置应用程序窗口图标// 注意:这里使用了Qt资源系统路径语法(:/开头)// 需要确保项目资源文件中已正确添加logo2.ico文件// 格式:":/[资源前缀]/[目录结构]/文件名.扩展名"w.setWindowIcon(QIcon(":/new/prefix1/images/logo2.ico"));// 显示主窗口w.show();// 进入Qt主事件循环,等待用户交互// exec()会阻塞直到应用程序退出(窗口关闭)return a.exec();}
#ifndef MAINWINDOW_H#define MAINWINDOW_H#include<QMainWindow>#include<QAction>#include<QStatusBar>#include<QThread>#include"./CentralWidget/CentralWidget.h"#include"./Worker/Worker.h"class MainWindow : public QMainWindow {Q_OBJECTpublic:explicitMainWindow(QWidget *parent = nullptr);~MainWindow();signals: // 添加信号声明voidstartTask();private slots:voidonStartTask(); // 启动任务voidonTaskProgress(int); // 更新进度voidonTaskFinished(); // 任务完成private:voidcreateMenu(); // 创建菜单voidcreateToolBar(); // 创建工具栏voidapplyStyle(); // 应用整体样式voidinitWorkerThread(); // 初始化工作线程// 成员变量CentralWidget *centralWidget;QAction *startTaskAction;QThread *workerThread;Worker *worker;};#endif// MAINWINDOW_H
#include"MainWindow.h"#include<QMenuBar>#include<QToolBar>#include<QMessageBox>#include<QStatusBar>#include<QFont>MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {setWindowTitle("Qt6——工业级信号与槽架构工程项目");resize(900, 560);// 创建中央控件centralWidget = new CentralWidget(this);setCentralWidget(centralWidget);// 初始化界面createMenu();createToolBar();applyStyle();statusBar()->showMessage("准备就绪");// 初始化工作线程initWorkerThread();}MainWindow::~MainWindow() {workerThread->quit();workerThread->wait();delete worker;}voidMainWindow::createMenu(){QMenuBar *bar = menuBar();QMenu *taskMenu = bar->addMenu("任务(&T)");startTaskAction = new QAction(QIcon(":/new/prefix1/images/logo.ico"), "启动后台任务", this);startTaskAction->setShortcut(QKeySequence("Ctrl+R"));startTaskAction->setStatusTip("启动一个模拟的耗时后台任务,并在界面中实时显示进度");taskMenu->addAction(startTaskAction);connect(startTaskAction, &QAction::triggered, this, &MainWindow::onStartTask);}voidMainWindow::createToolBar(){QToolBar *toolBar = addToolBar("操作");toolBar->setMovable(false);toolBar->setIconSize(QSize(24, 24));toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);toolBar->addAction(startTaskAction);}voidMainWindow::applyStyle(){// 主窗口整体配色与控件样式setStyleSheet("QMainWindow {"" background: transparent;""}""QMenuBar {"" background: #ffffff;"" border-bottom: 1px solid #dfe4ec;"" padding: 4px 8px;"" font: 12px 'Microsoft YaHei';""}""QMenuBar::item {"" padding: 4px 10px;"" margin: 0 2px;"" border-radius: 4px;""}""QMenuBar::item:selected {"" background: #e6f0ff;""}""QMenu {"" background: #ffffff;"" border: 1px solid #d0d7e2;"" padding: 6px 0;"" font: 12px 'Microsoft YaHei';""}""QMenu::item {"" padding: 6px 24px;""}""QMenu::item:selected {"" background: #e6f0ff;""}""QToolBar {"" background: #f7f9fc;"" border-bottom: 1px solid #dfe4ec;"" padding: 4px 8px;""}""QToolButton {"" padding: 6px 10px;"" border-radius: 6px;"" color: #2c3e50;"" font: 12px 'Microsoft YaHei';""}""QToolButton:hover {"" background: #e6f0ff;""}""QToolButton:pressed {"" background: #d0e0ff;""}""QStatusBar {"" background: #ffffff;"" border-top: 1px solid #dfe4ec;"" font: 11px 'Microsoft YaHei';"" color: #7f8c8d;""}");// 状态栏附加一段品牌/提示文本QLabel *brand = new QLabel("Qt C++ 架构实践 · Signals/Slots Demo", this);QFont brandFont("Microsoft YaHei", 9);brand->setFont(brandFont);brand->setStyleSheet("color: #95a5a6;");statusBar()->addPermanentWidget(brand);}voidMainWindow::initWorkerThread(){workerThread = new QThread(this);worker = new Worker();worker->moveToThread(workerThread);// 连接信号与槽(跨线程必须使用 QueuedConnection)connect(this, &MainWindow::startTask, worker, &Worker::doWork, Qt::QueuedConnection);connect(worker, &Worker::progress, this, &MainWindow::onTaskProgress, Qt::QueuedConnection);connect(worker, &Worker::finished, this, &MainWindow::onTaskFinished, Qt::QueuedConnection);workerThread->start();}voidMainWindow::onStartTask(){statusBar()->showMessage("任务运行中...");emit startTask(); // 触发工作线程}voidMainWindow::onTaskProgress(int value){centralWidget->updateProgress(value);}voidMainWindow::onTaskFinished(){statusBar()->showMessage("任务完成");QMessageBox::information(this, "提示", "耗时任务已完成!");}
夜雨聆风