解码信号与槽(含 QTimer 应用)

信号与槽机制(Qt 核心通信方式)

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

基本概念

  • 信号(Signal) :QObject 子类中声明的 "事件通知",仅声明不实现,当特定事件触发时(如按钮点击、定时器超时),通过emit发出。
  • 槽(Slot):QObject 子类中声明并实现的 "响应函数",可与信号绑定,信号发出时自动执行。
  • 关联规则:一个信号可绑定多个槽(一个事件触发多个响应),多个信号可绑定同一个槽(多个事件触发同一个响应)。
  • 核心前提:所有使用信号与槽的类必须继承QObject,且类内必须添加Q_OBJECT宏(否则元对象系统无法识别信号 / 槽)。

信号与槽的定义

cpp 复制代码
#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

cpp 复制代码
/**
 * @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 信号名(参数),触发后所有绑定的槽函数会按连接类型执行。

cpp 复制代码
// 触发Sender类的signalName信号
Sender sender;
emit sender.signalName();  // 信号发出后,绑定的slotName会自动执行

信号与槽的参数规则

信号可携带参数传递数据,槽函数需按规则匹配参数,否则无法正常连接或执行。

信号参数

基础规则

  • 信号参数类型:支持 Qt 基础类型(int/double/QString/QDate/QTime/QColor 等),自定义类型需先注册。
  • 参数数量:可携带任意数量参数,返回值必须为 void。
  • 默认值:信号参数可设置默认值,发送时未传参则使用默认值。

自定义类型注册

若信号需传递自定义类型(如 Person 类),需通过qRegisterMetaType向 Qt 元对象系统注册,否则跨线程传递会失败。

cpp 复制代码
// 自定义类型示例
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();
}

带参数 + 默认值的信号示例

cpp 复制代码
#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))。
  • 数量兼容:信号参数数量 ≥ 槽函数参数数量(槽可忽略信号的部分末尾参数)。

正确 / 错误示例对比

cpp 复制代码
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明确指定要连接的信号版本。

cpp 复制代码
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。

    cpp 复制代码
    // .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 版)

cpp 复制代码
#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)

cpp 复制代码
#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)

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避免同一信号 - 槽被多次绑定,导致槽函数多次执行。
相关推荐
小灰灰搞电子6 小时前
Qt SCXML 模块详解
开发语言·qt
开始了码7 小时前
UDP 协议详解与 Qt 实战应用
qt·网络协议·udp
深蓝海拓21 小时前
PySide6从0开始学习的笔记(三) 布局管理器与尺寸策略
笔记·python·qt·学习·pyqt
꧁坚持很酷꧂1 天前
Windows安装Qt Creator5.15.2(图文详解)
开发语言·windows·qt
淼淼7631 天前
QT表格与数据
开发语言·qt
小灰灰搞电子1 天前
Qt 实现炫酷锁屏源码分享
开发语言·qt·命令模式
追烽少年x1 天前
Qt面试题合集(二)
qt
零小陈上(shouhou6668889)1 天前
YOLOv8+PyQt5玉米病害检测系统(yolov8模型,从图像、视频和摄像头三种路径识别检测)
python·qt·yolo
蓝天智能1 天前
QT实战:qrc资源动态加载
qt