解码信号与槽(含 QTimer 应用)
信号与槽机制(Qt 核心通信方式)
信号与槽是 Qt 独有的对象间通信机制,由元对象系统(MOC)实现,无需依赖回调函数,能灵活实现事件响应和跨对象交互。

基本概念
- 信号(Signal):QObject 子类中声明的 “事件通知”,仅声明不实现,当特定事件触发时(如按钮点击、定时器超时),通过
emit发出。 - 槽(Slot):QObject 子类中声明并实现的 “响应函数”,可与信号绑定,信号发出时自动执行。
- 关联规则:一个信号可绑定多个槽(一个事件触发多个响应),多个信号可绑定同一个槽(多个事件触发同一个响应)。
- 核心前提:所有使用信号与槽的类必须继承
QObject,且类内必须添加Q_OBJECT宏(否则元对象系统无法识别信号 / 槽)。
信号与槽的定义
#include <QObject>
/**
* 信号发送者类
* @brief 仅声明信号,无需实现;必须继承QObject并添加Q_OBJECT宏
*/
class Sender : public QObject{
Q_OBJECT // 必须添加,用于启用元对象系统(信号/槽、动态属性等)
public:
/**
* @brief 构造函数
* @param parent 父对象(Qt父子对象机制,自动管理内存)
*/
explicit Sender(QObject *parent = nullptr) : QObject(parent) {}
signals: // signals关键字:声明信号,仅需声明,Qt MOC工具自动生成实现
/**
* @brief 自定义信号:无参数示例
* @note 信号返回值必须是void,不能有访问修饰符(默认public)
*/
void signalName();
};
/**
* 信号接收者类
* @brief 声明并实现槽函数,响应信号
*/
class Receiver : public QObject{
Q_OBJECT
public:
explicit Receiver(QObject *parent = nullptr) : QObject(parent) {}
public slots: // slots关键字:声明槽函数(Qt5+也可直接用public/protected/private普通函数)
/**
* @brief 自定义槽函数:无参数示例
* @note 槽函数必须实现,否则编译报错;参数需与绑定的信号匹配
*/
void slotName() {
// 槽函数具体逻辑(如打印日志、修改UI、调用其他函数)
qDebug() << "信号触发,槽函数执行";
}
};
explicit关键字作用是禁止编译器对该构造函数进行 “隐式类型转换”,强制要求程序员显式调用构造函数创建对象
信号与槽的连接(QObject::connect)
Qt5 + 推荐使用函数指针方式连接(类型安全,编译期检查错误),旧版SIGNAL/SLOT宏(字符串匹配,运行期检查)仅用于兼容旧代码。
核心函数:QObject::connect
/**
* @brief 连接信号与槽的核心函数(Qt5+推荐方式)
* @param sender 信号发送者对象指针(必须是QObject子类实例)
* @param signal 信号函数指针(格式:&类名::信号名)
* @param receiver 槽函数接收者对象指针(必须是QObject子类实例)
* @param slot 槽函数指针(格式:&类名::槽名)
* @param type 连接类型(默认Qt::AutoConnection)
* @return QMetaObject::Connection 连接对象,可用于断开连接(disconnect)
* @note 1. 发送者/接收者不能为nullptr;2. 信号/槽函数签名需匹配;3. 支持跨线程连接
*/
QMetaObject::Connection QObject::connect(
const QObject *sender,
PointerToMemberFunction signal,
const QObject *receiver,
PointerToMemberFunction slot,
Qt::ConnectionType type = Qt::AutoConnection
);
// 连接示例
Sender *sender = new Sender();
Receiver *receiver = new Receiver();
// 连接sender的signalName信号到receiver的slotName槽,设置唯一连接(避免重复绑定)
QObject::connect(sender, &Sender::signalName, receiver, &Receiver::slotName, Qt::UniqueConnection);
注:非静态成员用 :::不能直接调用,但可以用来「取成员函数指针」(这正是 Qt 信号槽里的场景)&类名::函数名 是 C++ 中获取「类成员函数指针」的标准语法
连接类型(Qt::ConnectionType)
| 连接类型 | 中文名称 | 核心逻辑 | 适用场景 |
|---|---|---|---|
| Qt::DirectConnection | 直接连接 | 信号发出时立即执行槽函数,与信号在同一线程执行 | 同线程通信(响应最快) |
| Qt::QueuedConnection | 排队连接 | 信号事件放入接收者的事件队列,事件循环处理时执行 | 跨线程通信(避免线程安全问题) |
| Qt::BlockingQueuedConnection | 阻塞排队连接 | 跨线程执行槽函数,发送线程阻塞直到槽函数执行完成 | 需等待槽函数执行结果的跨线程场景(注意避免死锁) |
| Qt::AutoConnection | 自动连接 | 同线程→直接连接,跨线程→排队连接 | 默认推荐(自适应线程) |
| Qt::UniqueConnection | 唯一连接 | 若已存在相同的信号 - 槽连接,不创建新连接 | 避免重复绑定导致槽函数多次执行 |
信号的发送(emit 关键字)
信号通过emit关键字触发,语法为emit 信号名(参数),触发后所有绑定的槽函数会按连接类型执行。
// 触发Sender类的signalName信号
Sender sender;
emit sender.signalName(); // 信号发出后,绑定的slotName会自动执行
信号与槽的参数规则
信号可携带参数传递数据,槽函数需按规则匹配参数,否则无法正常连接或执行。
信号参数
基础规则
- 信号参数类型:支持 Qt 基础类型(int/double/QString/QDate/QTime/QColor 等),自定义类型需先注册。
- 参数数量:可携带任意数量参数,返回值必须为 void。
- 默认值:信号参数可设置默认值,发送时未传参则使用默认值。
自定义类型注册
若信号需传递自定义类型(如 Person 类),需通过qRegisterMetaType向 Qt 元对象系统注册,否则跨线程传递会失败。
// 自定义类型示例
class Person {
public:
QString name;
int age;
};
// 程序入口注册自定义类型
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
/**
* @brief 注册自定义类型
* @param "Person" 类型别名(需与信号参数类型名一致)
* @note 注册需在信号槽连接前执行,通常放在main函数开头
*/
qRegisterMetaType<Person>("Person");
// 后续可正常使用Person作为信号参数
return app.exec();
}
带参数 + 默认值的信号示例
#include <QWidget>
#include <QDebug>
class MyWidget : public QWidget{
Q_OBJECT
signals:
/**
* @brief 带参数的信号:支持默认值
* @param message 字符串参数(无默认值)
* @param value 整数参数(默认值0)
* @note 有默认值的参数需放在参数列表末尾
*/
void mySignal(QString message, int value = 0);
public:
MyWidget(QWidget *parent = nullptr) : QWidget(parent) {}
public slots:
/**
* @brief 匹配mySignal的槽函数
* @param message 接收信号的字符串参数
* @param value 接收信号的整数参数
*/
void mySlot(QString message, int value) {
qDebug() << "接收参数:" << message << "," << value;
}
};
// 连接与发送示例
MyWidget *widget = new MyWidget();
// 连接信号与槽
QObject::connect(widget, &MyWidget::mySignal, widget, &MyWidget::mySlot);
// 发送信号(传2个参数)
emit widget->mySignal("Hello Qt", 123); // 输出:接收参数:"Hello Qt" ,123
// 发送信号(仅传1个参数,使用默认值0)
emit widget->mySignal("Hello Default"); // 输出:接收参数:"Hello Default" ,0
槽函数参数匹配规则
槽函数参数必须满足以下规则,否则编译 / 运行报错:
- 类型一致:槽函数参数类型必须与信号对应位置的参数类型完全匹配(如信号是
QString,槽不能是int)。 - 顺序一致:槽函数参数顺序必须与信号参数顺序一致(如信号是
(QString, int),槽不能是(int, QString))。 - 数量兼容:信号参数数量 ≥ 槽函数参数数量(槽可忽略信号的部分末尾参数)。
正确 / 错误示例对比
class MyWidget : public QWidget{
Q_OBJECT
signals:
// 信号:2个参数(QString + int)
void mySignal(QString message, int value);
public:
MyWidget(QWidget *parent = nullptr) : QWidget(parent) {}
public slots:
// 正确:参数类型、顺序一致,数量相等
void slot1(QString message, int value) {
qDebug() << message << value;
}
// 正确:参数类型、顺序一致,数量少于信号(忽略末尾的int)
void slot2(QString message) {
qDebug() << message;
}
// 错误:类型不匹配(int vs QString)
// void slot3(int message, int value) {}
// 错误:顺序不一致(int在前,QString在后)
// void slot4(int value, QString message) {}
// 错误:数量多于信号(信号只有2个参数,槽要3个)
// void slot5(QString message, int value, bool flag) {}
};
信号重载的处理
若类中有多个同名但参数不同的信号(重载),需用QOverload明确指定要连接的信号版本。
class MyWidget : public QWidget{
Q_OBJECT
signals:
// 信号重载1:int参数
void mySignal(int value);
// 信号重载2:QString参数
void mySignal(QString message);
public:
MyWidget(QWidget *parent = nullptr) : QWidget(parent) {}
public slots:
void slotInt(int value) {
qDebug() << "Int参数:" << value;
}
void slotStr(QString message) {
qDebug() << "String参数:" << message;
}
};
// 连接重载信号示例
MyWidget *widget = new MyWidget();
// 连接int版本的mySignal到slotInt
QObject::connect(widget, QOverload<int>::of(&MyWidget::mySignal), widget, &MyWidget::slotInt);
// 连接QString版本的mySignal到slotStr
QObject::connect(widget, QOverload<QString>::of(&MyWidget::mySignal), widget, &MyWidget::slotStr);
// 发送重载信号
emit widget->mySignal(123); // 执行slotInt,输出:Int参数:123
emit widget->mySignal("abc"); // 执行slotStr,输出:String参数:"abc"
QtCreator 可视化编辑信号与槽
除代码连接外,QtCreator 的 UI 设计器可可视化配置信号与槽,无需手写 connect 代码。
信号与槽编辑模式

-
打开 UI 设计器,点击顶部 “编辑信号 / 槽” 按钮(或按 F4)。
-
选中发送者控件(如 PushButton),按住鼠标左键拖到接收者(如窗口)。
-
在弹出的对话框中,选择发送者的信号(如
clicked())和接收者的槽(如close()),点击确定。 -
底部 “信号与槽编辑器” 可查看 / 编辑所有绑定关系,支持删除 / 新增。

转到槽(自动生成槽函数)

-
右键点击控件(如 PushButton),选择 “转到槽”。
-
选择要响应的信号(如
clicked()),点击确定。 -
QtCreator 自动在类的头文件(.h)声明槽函数,在源文件(.cpp)生成空实现:注:该方式遵循 “on_控件名_信号名” 命名规则,Qt 自动关联,无需手写 connect。
// .h文件(自动生成) private slots: void on_pushButton_clicked(); // .cpp文件(自动生成) void MyWidget::on_pushButton_clicked() { // 手动添加槽函数逻辑(如按钮点击后关闭窗口) this->close(); }
QTimer 定时器(信号与槽的典型应用)
QTimer 是 Qt 的定时器类,通过timeout()信号触发定时任务,常用于周期性更新 UI、执行后台任务等。
QTimer 核心特性
- 基于事件循环:需在有 QApplication 事件循环的程序中使用(main 函数需调用
app.exec())。 - 灵活控制:支持单次触发(
setSingleShot(true))、周期性触发,可随时启动 / 停止。 - 线程安全:跨线程使用时,
timeout()信号自动适配线程(默认 Qt::AutoConnection)。
QTimer 核心接口
| 接口 | 作用 |
|---|---|
setInterval(int msec) |
设置定时间隔(单位:毫秒,默认 0) |
start() |
启动定时器(使用已设置的间隔) |
start(int msec) |
启动定时器(临时指定间隔,覆盖原有设置) |
stop() |
停止定时器 |
isActive() |
判断定时器是否正在运行 |
setSingleShot(bool) |
设置是否单次触发(true:触发一次后自动停止) |
示例 1:周期性更新计数器(Lambda 版)
#include <QApplication>
#include <QLabel>
#include <QTimer>
#include <QDebug>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
// 创建显示标签
QLabel *label = new QLabel("计数器:0");
label->show();
// 创建定时器对象
QTimer *timer = new QTimer();
/**
* @brief 设置定时间隔:1000毫秒(1秒)
* @note 间隔为0时,定时器在事件循环空闲时立即触发
*/
timer->setInterval(1000);
/**
* @brief 连接定时器timeout信号到Lambda表达式(简化槽函数)
* @note Lambda捕获[=]表示按值捕获当前作用域变量(label、timer)
*/
QObject::connect(timer, &QTimer::timeout, [=](){
static int count = 0; // 静态变量:保持计数状态
count++;
// 更新标签文本
label->setText(QString("计数器:%1").arg(count));
qDebug() << "当前计数:" << count;
});
// 启动定时器
timer->start();
return app.exec(); // 启动事件循环,否则定时器无法触发
}
示例 2:实时显示系统时间(按钮启停)
步骤 1:定义窗口类(timerwin.h)
#include <QMainWindow>
#include <QTimer>
#include <QTime>
namespace Ui {
class TimerWin; // UI类(由QtCreator自动生成)
}
class TimerWin : public QMainWindow{
Q_OBJECT
public:
explicit TimerWin(QWidget *parent = nullptr);
~TimerWin();
private slots:
/**
* @brief 定时更新系统时间的槽函数
* @note 响应QTimer的timeout信号
*/
void update_time();
/**
* @brief 按钮点击槽函数(启停定时器)
* @note 由“转到槽”自动生成,关联按钮的clicked信号
*/
void on_pushButton_clicked();
private:
Ui::TimerWin *ui; // UI指针(包含label和pushButton)
QTimer mtimer; // 定时器成员变量(无需手动释放,随窗口销毁)
};
步骤 2:实现窗口逻辑(timerwin.cpp)
#include "timerwin.h"
#include "ui_timerwin.h"
TimerWin::TimerWin(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::TimerWin)
{
ui->setupUi(this); // 初始化UI
/**
* @brief 连接定时器timeout信号到update_time槽函数
* @param &mtimer 定时器对象(发送者)
* @param &QTimer::timeout 定时器超时信号
* @param this 接收者(当前窗口)
* @param &TimerWin::update_time 自定义槽函数
*/
QObject::connect(&mtimer, &QTimer::timeout, this, &TimerWin::update_time);
}
TimerWin::~TimerWin()
{
delete ui;
}
void TimerWin::update_time()
{
/**
* @brief 获取当前系统时间并格式化
* @param "hh:mm:ss" 格式:小时(24制):分钟:秒
* @note QTime::currentTime() 返回当前系统时间(本地时间)
*/
QString tstr = QTime::currentTime().toString("hh:mm:ss");
// 更新UI标签显示时间
ui->label->setText(tstr);
}
void TimerWin::on_pushButton_clicked()
{
// 判断定时器是否正在运行
if(mtimer.isActive()){
mtimer.stop(); // 停止定时器
ui->pushButton->setText("启动定时器"); // 修改按钮文本
} else {
mtimer.start(1000); // 启动定时器(间隔1秒)
ui->pushButton->setText("停止定时器"); // 修改按钮文本
}
}
示例说明
- 定时器启动后,每 1 秒触发
timeout()信号,调用update_time()更新系统时间。 - 按钮点击时,通过
isActive()判断定时器状态,实现 “启动 / 停止” 切换。 - UI 中的 label 用于显示时间,pushButton 用于控制定时器,无需手动连接信号槽(遵循 “on_控件名_信号名” 规则)。
常见注意事项
- 信号 / 槽函数签名错误:编译期报错(函数指针方式)或运行期警告(SIGNAL/SLOT 宏方式),需严格匹配参数类型 / 顺序。
- 内存泄漏:发送者 / 接收者被销毁但未断开连接,需利用 Qt 父子对象机制(父对象销毁时自动销毁子对象)。
- 跨线程定时器:QTimer 需在创建线程的事件循环中运行,若线程无事件循环,定时器无法触发。
- 自定义类型传递:必须用
qRegisterMetaType注册,否则跨线程传递时会提示 “未知类型”。 - 重复连接:使用
Qt::UniqueConnection避免同一信号 - 槽被多次绑定,导致槽函数多次执行。

浙公网安备 33010602011771号