• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
sn-genuine
博客园    首页    新随笔    联系   管理    订阅  订阅

Qt信号与槽

Qt信号与槽学习笔记

核心:信号槽是Qt实现观察者模式(发布‑订阅)的核心机制,依靠Moc元对象编译器实现。

1. 信号和槽概述

1.1 信号的本质

信号本质就是事件:按钮点击、鼠标移动、键盘输入、窗口刷新等。

  • 用户操作控件,控件检测到事件,Qt框架就会发出对应信号。
  • 信号是以函数形式呈现;事件产生,框架调用信号函数,通知使用者。
  • 信号的发出者必须是实例化后的QObject对象。

通俗比喻:女朋友肚子饿了,发出“饿了”这个信号。

1.2 槽的本质

槽函数是专门用来处理信号的函数,可以当作普通成员函数调用。

  • 信号发生,绑定的槽函数自动执行。
  • 槽函数所属对象,也必须是实例化的QObject派生对象。

通俗比喻:我收到“饿了”信号,执行“带她吃饭”这个槽处理动作。

1.3 信号槽的关系

信号与槽本身互相独立,需要使用 connect() 函数建立关联,如同喜鹊搭桥。

connect函数原型(Qt5函数指针版本)

QMetaObject::Connection QObject::connect(
    const QObject *sender,        // 发出信号的对象
    PointerToMemberFunction signal,// 信号函数地址(函数指针)
    const QObject *receiver,      // 接收信号的对象
    PointerToMemberFunction method,// 槽函数地址
    Qt::ConnectionType type = Qt::AutoConnection
);
  • sender、receiver都必须实例化,否则连接无效。
  • connect只是注册回调关系,不是立刻执行;只有信号触发,槽才执行。

2. 标准信号槽使用

Qt内置控件自带大量标准信号、标准槽。

  • 标准信号:控件检测用户事件之后,Qt内部自动发出。
  • 标准槽:Qt类内置,可用来响应信号。

查找信号槽:Qt帮助文档,输入类名,查看signals部分;很多信号继承自父类,例如QPushButton信号来自父类QAbstractButton。

示例:按钮点击触发窗口关闭

connect(ui->closeBtn,&QPushButton::clicked,this,&MainWindow::close);

3. 自定义信号槽使用

当Qt自带信号无法满足业务,就自己定义信号和槽,依然使用connect绑定。

使用自定义信号槽前置条件

  1. 类必须直接/间接继承QObject
  2. 头文件内部添加宏:Q_OBJECT

⚠添加Q_OBJECT之后,CMake/qmake需要重新构建,否则编译报错。

class GirlFriend : public QObject
{
    Q_OBJECT //❗必须写
public:
    explicit GirlFriend(QObject *parent = nullptr);

// 自定义信号
signals:
    void hungry();
    void hungry(QString foodName); //信号支持重载
};

3.1 自定义信号规则

  1. 信号是类的成员函数,放在signals:下
  2. 返回值必须是void
  3. 只写声明,不需要手写函数实现,由Moc自动生成
  4. 支持重载,可以带参数;参数用于传递数据给槽函数
  5. 发射信号:调用信号函数;习惯加emit关键字,emit是空宏,只起提示作用
m_girl->hungry();
emit m_girl->hungry("意大利面"); // emit可省略

3.2 自定义槽函数规则

槽本质就是普通函数,Qt5以后slots:关键字可以省略。

  1. 返回值一般void
  2. 支持重载,可以有参数
  3. 参数匹配规则:
    • 槽参数个数、类型,尽量和信号一一对应
    • 信号参数 ≥ 槽参数,多余信号参数会被忽略
    • 槽不能比信号参数更多
  4. 槽可以是:类成员函数、全局函数、静态函数、Lambda表达式
class Me : public QObject
{
    Q_OBJECT
public:
    explicit Me(QObject *parent = nullptr);
public slots:
    void eat();
    void eat(QString msg);
};

me.cpp

#include "me.h"
#include <QDebug>

Me::Me(QObject *parent)
    : QObject{parent}
{}

void Me::eat()
{
    qDebug()<<"我带你去吃麻辣烫...";
}

void Me::eat(QString msg)
{
    qDebug()<<"我带你去吃:"<<msg;
}

⚠信号重载坑:同一个信号名多个版本,直接写&类::函数名编译器分不清,需要qOverload指定版本。

4. 信号槽拓展

4.1 使用拓展特性

  1. 一个信号绑定多个槽函数
    • 写多个connect;执行顺序不保证和connect顺序一致。
    • 接收者可以是不同对象。
  2. 一个槽绑定多个信号
    • 多个不同信号,触发同一个处理逻辑,写多条connect。
  3. 信号可以连接信号
    • 接收方不写业务处理,直接在槽/lamdba内部发射新信号,做信号转发。
//信号连接信号示例
connect(ui->hungry,&QPushButton::clicked,m_girl,&GirlFriend::hungry);
  1. 断开连接:disconnect(),解除信号‑槽绑定。

4.2 Lambda表达式(Qt最常用)

Lambda就是C++11匿名函数,非常适合写简单槽逻辑,不需要额外写成员函数。

语法格式:

[capture](params) opt -> ret { body; };
  1. 捕获列表 capture(重点)
    |写法|含义|
    |----|----|
    |[]|不捕获任何变量|
    |[&]|全部外部变量,按引用捕获(可修改原变量)|
    |[=]|全部外部变量,值拷贝捕获,lambda内部只读副本|
    |[=,&foo]|全部值拷贝,唯独foo按引用捕获|
    |[bar]|只值拷贝bar这一个变量|
    |[&bar]|只引用捕获bar|
    |[this]|捕获类this指针,可以访问类成员|

mutable选项:值拷贝模式下,允许修改lambda内部的副本,不会修改外部原始变量。

  1. params:参数列表;opt可选(mutable、异常);ret返回值可省略自动推导;body函数体。

示例,connect中使用Lambda:

connect(ui->hungry,&QPushButton::clicked,this,[=](){
    m_girl->hungry();
    m_girl->hungry("意大利面");
});

4.3 qOverload处理重载信号

当信号存在重载,编译器无法自动识别取哪个版本的函数指针,使用qOverload<参数类型列表>(&类名::信号名)。

// 无参版本 void hungry()
connect(m_girl, qOverload<>(&GirlFriend::hungry), this, [=](){
    qDebug() << "收到无参hungry信号!";
});

// QString参数版本 void hungry(QString)
connect(m_girl, qOverload<QString>(&GirlFriend::hungry), this, [=](QString msg){
    qDebug() << "收到带参hungry:" << msg;
});

底层等价于手写冗长的static_cast函数指针强制转换;Qt封装简化写法。

完整MainWindow示例代码

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class GirlFriend;
class Me;

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private slots:
    void hungrySlot();
    void eatSlot();
private:
    Ui::MainWindow *ui;
    GirlFriend *m_girl;
    Me *m_me;
};
#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include "GirlFriend.h"
#include "me.h"
#include <QDebug>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    m_me = new Me;
    m_girl = new GirlFriend;

    connect(ui->hungry,&QPushButton::clicked,this,[=](){
        // 发射自定义信号
        m_girl->hungry();
        m_girl->hungry("意大利面");
    });

    // 接收无参 hungry()
    connect(m_girl, qOverload<>(&GirlFriend::hungry), this, [=](){
        qDebug() << "收到无参hungry信号!";
    });

    // 接收带参 hungry(QString)
    connect(m_girl, qOverload<QString>(&GirlFriend::hungry), this, [=](QString msg){
        qDebug() << "收到带参hungry:" << msg;
    });

    connect(ui->closeBtn,&QPushButton::clicked,this,&MainWindow::close);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::hungrySlot()
{
    // 发射自定义信号
    m_girl->hungry();
}

void MainWindow::eatSlot()
{
    qDebug()<<"我带你去吃海鲜...";
}

GirlFriend.h

#ifndef GIRLFRIEND_H
#define GIRLFRIEND_H

#include <QObject>

class GirlFriend : public QObject
{
    Q_OBJECT
public:
    explicit GirlFriend(QObject *parent = nullptr);
signals:
    void hungry();
    void hungry(QString foodName);
};

#endif // GIRLFRIEND_H

GirlFriend.cpp

#include "GirlFriend.h"

GirlFriend::GirlFriend(QObject *parent)
    : QObject{parent}
{}

me.h

#ifndef ME_H
#define ME_H

#include <QObject>

class Me : public QObject
{
    Q_OBJECT
public:
    explicit Me(QObject *parent = nullptr);
public slots:
    void eat();
    void eat(QString msg);
};

#endif // ME_H

me.cpp

#include "me.h"
#include <QDebug>

Me::Me(QObject *parent)
    : QObject{parent}
{}

void Me::eat()
{
    qDebug()<<"我带你去吃麻辣烫...";
}

void Me::eat(QString msg)
{
    qDebug()<<"我带你去吃:"<<msg;
}

面试高频总结

  1. 使用自定义信号槽两大硬性条件:继承QObject、头文件写Q_OBJECT。
  2. 信号:只声明,不许手写实现,Moc工具自动生成;返回值只能void。
  3. emit是空宏,只做提示,可省略。
  4. 信号重载:必须用qOverload挑选版本,否则编译报错。
  5. 槽参数:槽的参数数量不能多于信号;多余信号参数自动丢弃。
  6. Lambda捕获:[=]值拷贝只读副本,[&]引用捕获修改外部原始变量,[this]访问类成员。
  7. connect只是注册关系;发射信号才触发槽;只发射信号,没有connect绑定,不会执行任何逻辑。
posted on 2026-08-29 22:00  SobNov  阅读(12)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3