Qt 信号与槽深度解析:connect 用法、自定义信号槽与 Lambda 实战

🔥小叶-duck个人主页

❄️个人专栏《Data-Structure-Learning》《C++入门到进阶&自我学习过程记录》
《Linux操作系统从入门到实践》《Qt从入门到实践》
《算法题讲解指南》--优选算法
《算法题讲解指南》--递归、搜索与回溯算法
《算法题讲解指南》--动态规划算法

未择之路,不须回头
已择之路,纵是荆棘遍野,亦作花海遨游


目录

前言

一、信号与槽的本质

[1.1 核心概念](#1.1 核心概念)

[1.1.1 信号的本质](#1.1.1 信号的本质)

[1.1.2 槽的本质](#1.1.2 槽的本质)

[1.2 关键特性](#1.2 关键特性)

[二、connect 基础用法](#二、connect 基础用法)

[2.1 connect 函数原型与参数](#2.1 connect 函数原型与参数)

[2.2 实战案例:点击按钮关闭窗口](#2.2 实战案例:点击按钮关闭窗口)

[2.3 如何查看内置信号与槽的相关信息?](#2.3 如何查看内置信号与槽的相关信息?)

三、实现自定义槽函数

[3.1 基础自定义槽函数(手动实现)](#3.1 基础自定义槽函数(手动实现))

[3.2 可视化关联:Qt Creator 快速生成信号槽](#3.2 可视化关联:Qt Creator 快速生成信号槽)

四、构建自定义信号

[4.1 自定义规则](#4.1 自定义规则)

[4.2 自定义信号基础](#4.2 自定义信号基础)

五、带参信号槽的实现

[5.1 基础参数传递](#5.1 基础参数传递)

[5.2 多参数使用规则](#5.2 多参数使用规则)

[5.3 对 Q_OBJECT 作用的认识](#5.3 对 Q_OBJECT 作用的认识)

六、信号与槽的连接方式:多场景适配

[6.1 一对一连接](#6.1 一对一连接)

[6.2 一对多连接](#6.2 一对多连接)

[6.3 多对一连接](#6.3 多对一连接)

[6.4 信号和槽断开连接](#6.4 信号和槽断开连接)

[七、Lambda 表达式进阶:简化槽函数](#七、Lambda 表达式进阶:简化槽函数)

[7.1 Lambda 表达式语法](#7.1 Lambda 表达式语法)

[7.2 实战示例:Lambda 作为槽函数](#7.2 实战示例:Lambda 作为槽函数)

[八、Qt4 与 Qt5 信号槽写法对比及常见避坑指南](#八、Qt4 与 Qt5 信号槽写法对比及常见避坑指南)

[8.1 信号槽写法对比](#8.1 信号槽写法对比)

[8.2 常见避坑指南](#8.2 常见避坑指南)

九、信号与槽的优缺点及总结

[9.1 信号与槽的优点](#9.1 信号与槽的优点)

[9.2 信号与槽的缺点](#9.2 信号与槽的缺点)

[9.3 信号与槽总结](#9.3 信号与槽总结)

结束语


前言

信号与槽 是 Qt 框架最核心、最具特色的通信机制,也是 Qt 界面开发的灵魂所在。无论是点击按钮关闭窗口,还是自定义事件响应,信号与槽都能轻松实现。本系列内容围绕信号和槽展开系统学习,从信号与槽的本质入手,逐步讲解connect函数的多种用法、自定义信号槽,到 Lambda 表达式进阶、连接方式拓展,层层递进拆解核心逻辑,搭配实战代码和避坑指南,帮你彻底掌握这一 Qt 核心技术。

一、信号与槽的本质

1.1 核心概念

1.1.1 信号的本质

信号(Signal) 是由于用户对窗口或控件进行了某些操作,导致窗口或控件产生了某个特定事件,这时 Qt 对应的窗口类会发出某个信号,以此对用户的操作做出反应。因此,信号的本质就是事件。如:

  • 按钮单击、双击
  • 窗口刷新
  • 鼠标移动、鼠标按下、鼠标释放
  • 键盘输入

那么在 Qt 中信号是通过什么形式呈现给使用者的呢?

  • 我们对哪个窗口进行操作,哪个窗口就可以捕捉到这些被触发的事件。
  • 对于使用者来说触发了一个事件我们就可以得到 Qt 框架给我们发出的某个特定信号。
  • 信号的呈现形式就是函数,也就是说某个事件产生了,Qt 框架就会调用某个对应的信号函数,通知使用者。

在 Qt 中信号的发出者是某个实例化的类对象。

1.1.2 槽的本质

槽(Slot) 就是对信号响应的函数槽就是一个函数,与一般的 C++ 函数是一样的,可以定义在类的任何位置(public、protected 或 private),可以具有任何参数,可以被重载,也可以被直接调用(但是不能有默认参数)。槽函数与一般的函数不同的是:槽函数可以与一个信号关联,当信号被发射时,关联的槽函数被自动执行。

1.2 关键特性

  • 信号与槽均需依赖 QObject 类(Qt 所有核心类的父类),且类中必须添加 Q_OBJECT 宏;
  • 信号仅需在 signals 关键字下声明,无需实现;槽需在 public slots/protected slots 下声明并实现;
  • 支持多对多关联(一个信号连多个槽,多个信号连一个槽),灵活度极高。

二、connect 基础用法

Qt 自带大量预定义信号和槽(如按钮的clicked()信号、窗口的close()槽),通过QObject::connect()函数即可关联,无需自定义。

2.1 connect 函数原型与参数

cpp 复制代码
// 静态函数,用于关联信号和槽
QObject::connect(
    const QObject *sender,    // 信号发送者(如按钮)
    const char *signal,      // 发送的信号(如clicked())
    const QObject *receiver,  // 信号接收者(如窗口)
    const char *method,      // 响应的槽函数(如close())
    Qt::ConnectionType type = Qt::AutoConnection  // 连接方式(默认自动)
);

2.2 实战案例:点击按钮关闭窗口

cpp 复制代码
//widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>

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

    QPushButton *button = new QPushButton(this);
    button->setText("关闭");
    int x = (this->width() - button->width()) / 2;
    int y = (this->height() - button->height()) / 2;
    button->move(x, y);

    connect(button, &QPushButton::clicked, this, &Widget::close);
    //close 是 QWidget 内置的槽函数,Widget继承自QWidget,自然也就继承了父亲的槽函数
    //具体作用就是关闭当前窗口/控件
}

Widget::~Widget()
{
    delete ui;
}
  • 核心逻辑 :按钮(sender)的clicked()信号,关联窗口(receiver)的close()槽;
  • 运行效果:点击按钮,窗口立即关闭。

2.3 如何查看内置信号与槽的相关信息?

通过 Qt 帮助文档查询(光标选中类名按F1):

  • 信号 :查找Signals关键字(如QPushButtonclicked()pressed());
  • :查找Slots关键字(如QWidgetclose()show());
  • 若当前类未找到,需查看其父类,若仍没有则继续查找父类的父类以此类推(如QPushButton的信号在父类QAbstractButton中)。

三、实现自定义槽函数

3.1 基础自定义槽函数(手动实现)

cpp 复制代码
//widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

    void handleClicked();
private:
    Ui::Widget *ui;
};
#endif // WIDGET_H

//widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>

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

    QPushButton *button = new QPushButton(this);
    button->setText("按钮");
    int x = (this->width() - button->width()) / 2;
    int y = (this->height() - button->height()) / 2;
    button->move(x, y);

    connect(button, &QPushButton::clicked, this, &Widget::handleClicked);
}

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

void Widget::handleClicked()
{
    //按下按钮,修改窗口的标题
    this->setWindowTitle("按钮已经按下");
}

3.2 可视化关联:Qt Creator 快速生成信号槽

除了手动写 connect ,Qt Creator 支持可视化生成信号槽代码,高效便捷:

  1. 双击 widget.ui 进入设计模式,拖拽一个 PushButton 到界面;
  2. 选中按钮,右键选择 "转到槽...",在弹出的窗口中选择 clicked() 信号;
  3. Qt Creator 自动生成槽函数声明(在 widget.h)和实现框架(在widget.cpp
  4. 在生成的槽函数中添加逻辑(如关闭窗口)
  • 命名规则 :自动生成的槽函数名格式为 on_对象名_信号名 (如on_pushButton_clicked),Qt 会自动关联,无需手动connect
cpp 复制代码
// widget.h中自动生成的槽函数声明
private slots:
    void on_pushButton_clicked();

// widget.cpp中自动生成的槽函数定义
void Widget::on_pushButton_clicked()
{
    this->setWindowTitle("按钮已经按下");
}

四、构建自定义信号

4.1 自定义规则

(1)信号声明规则

  • 必须在 signals 关键字下声明;
  • 返回值为 void,仅声明无需实现;
  • 支持参数和重载(需注意参数匹配)。

(2)槽声明规则

  • 可在 public slots / protected slots / private slots 下声明(Qt5+ 也可以直接放在 public 下)
  • 返回值为 void ,需声明且实现;
  • 支持参数和重载(需注意参数匹配)。

4.2 自定义信号基础

简单实现一个自定义信号(无参):

cpp 复制代码
//widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

signals:
    void mySignal();

public:
    void handleMySignal();

private slots:
    void on_pushButton_clicked();

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H

//widget.cpp
#include "widget.h"
#include "ui_widget.h"

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

    connect(this, &Widget::mySignal, this, &Widget::handleMySignal);
}

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

void Widget::handleMySignal()
{
    this->setWindowTitle("处理自定义信号");
}

void Widget::on_pushButton_clicked()
{
    //mySignal();
    //即使不写emit,信号也能发出去
    //即使如此,实际开发中,还是建议把emit加上
    //加上代码可读性更高,更明显的标识出,这里是发射自定义的信号了.
    emit mySignal();
    //当点击按钮则会发送自定义的信号
    //发送信号的操作,也可以在任意合适的代码中,不一定非要在构造函数中emit
}

五、带参信号槽的实现

5.1 基础参数传递

cpp 复制代码
//widget.h
class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

signals:
    void mySignal(const QString& text);

public:
    void handleMySignal(const QString& text);

private slots:
    void on_pushButton_clicked();

    void on_pushButton_2_clicked();

private:
    Ui::Widget *ui;
};

//widget.cpp
#include "widget.h"
#include "ui_widget.h"

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

    connect(this, &Widget::mySignal, this, &Widget::handleMySignal);
}

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

void Widget::handleMySignal(const QString& text)
{
    this->setWindowTitle(text);
}


void Widget::on_pushButton_clicked()
{
    //传参可以起到复用代码的效果
    //并且可以在不同的场景中传入不同的参数
    emit mySignal("把标题设置为标题1");
}

void Widget::on_pushButton_2_clicked()
{
    //按下不同的按钮就会调用不同的函数,但发射的信号是同一个只是传入的参数不同
    emit mySignal("把标题设置为标题2");
}

5.2 多参数使用规则

  • 信号参数个数 ≥ 槽参数个数;
  • 信号和槽的参数类型必须一致(如QString对应QString);
  • 示例:信号void func(int, QString)可关联槽void slot(int)void slot(int, QString),但不能关联void slot(QString, int)(类型顺序不匹配)。

5.3 对 Q_OBJECT 作用的认识

复制代码

六、信号与槽的连接方式:多场景适配

信号与槽支持多种关联方式,满足不同场景需求,核心分为三类:

6.1 一对一连接

  • 场景:一个信号关联一个槽,或一个信号关联另一个信号;

示例

cpp 复制代码
// 信号→槽:按钮点击→窗口最小化
connect(btn, &QPushButton::clicked, this, &QWidget::showMinimized);

// 信号→信号:按钮点击→触发自定义信号
connect(btn, &QPushButton::clicked, this, &Widget::mySignal);

6.2 一对多连接

  • 场景:一个信号触发多个槽函数(执行顺序与关联顺序一致);

示例:

cpp 复制代码
// 一个按钮点击,触发三个槽函数
connect(btn, &QPushButton::clicked, this, &Widget::slot1);
connect(btn, &QPushButton::clicked, this, &Widget::slot2);
connect(btn, &QPushButton::clicked, this, &Widget::slot3);

6.3 多对一连接

  • 场景:多个信号触发同一个槽函数;

示例:

cpp 复制代码
// 两个按钮点击,都触发同一个槽函数
connect(btn1, &QPushButton::clicked, this, &Widget::commonSlot);
connect(btn2, &QPushButton::clicked, this, &Widget::commonSlot);

当然,还支持我们前面提到过的多对多连接,这里就不展示了。

6.4 信号和槽断开连接

实战演示:

cpp 复制代码
//widget.h
class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

    void handleClick();

    void handleClick2();

private slots:
    void on_pushButton_2_clicked();

private:
    Ui::Widget *ui;
    int flag = 0;
};

//widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>

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

    connect(ui->pushButton, &QPushButton::clicked, this, &Widget::handleClick);
}

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

void Widget::handleClick()
{
    this->setWindowTitle("修改窗口的标题1");
    qDebug() << "handleClick";
}

void Widget::handleClick2()
{
    this->setWindowTitle("修改窗口的标题2");
    qDebug() << "handleClick2";
}


void Widget::on_pushButton_2_clicked()
{
    if(flag == 0)
    {
        //1、先断开 pushbutton 原来的信号槽
        disconnect(ui->pushButton, &QPushButton::clicked, this, &Widget::handleClick);
        //2、再重新绑定新的信号槽
        connect(ui->pushButton, &QPushButton::clicked, this, &Widget::handleClick2);
        flag = 1;
    }
    else
    {
        disconnect(ui->pushButton, &QPushButton::clicked, this, &Widget::handleClick2);
        connect(ui->pushButton, &QPushButton::clicked, this, &Widget::handleClick);
        flag = 0;
    }
}

七、Lambda 表达式进阶:简化槽函数

Qt5 + 支持使用 Lambda 表达式作为槽函数,无需单独声明和实现槽,代码更简洁,尤其适合简单逻辑。

7.1 Lambda 表达式语法

cpp 复制代码
[捕获列表] (参数列表) 选项 -> 返回值类型 {
    函数体;
}

核心部分说明:

  • 捕获列表:控制 Lambda 能否访问外部变量(关键!):

    • []:不捕获任何外部变量;
    • [=]:值传递捕获所有外部变量(常用,避免悬空引用);
    • [&]:引用传递捕获所有外部变量(慎用,可能出现野指针);
    • [this]:捕获当前类的成员变量和函数;
    • [btn]:值传递捕获指定变量btn;
  • 选项 :常用mutable(允许修改值传递的变量副本);

  • 返回值类型:可省略,编译器自动推导。

7.2 实战示例:Lambda 作为槽函数

cpp 复制代码
//widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
#include <QRandomGenerator> //生成一个随机数
#include <QDebug>

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

    QPushButton *button = new QPushButton(this);
    button->setText("按钮");
    int x = 200;
    int y = 200;
    button->move(x, y);

    //connect(button, &QPushButton::clicked, this, [button, this](){
    connect(button, &QPushButton::clicked, this, [=]()mutable{
        x = QRandomGenerator::global()->bounded(this->width() - button->width());
        y = QRandomGenerator::global()->bounded(this->height() - button->height());
        qDebug() << "lambda 被执行了!";
        button->move(x, y);
        //按一下按钮就会随机出现到界面其他位置
    });
}

Widget::~Widget()
{
    delete ui;
}
  • 注意 :Qt5+ 默认支持 Lambda,若用之前的版本编译报错,需要在**.pro** 文件中添加CONFIG += c++11

八、Qt4 与 Qt5 信号槽写法对比及常见避坑指南

8.1 信号槽写法对比

Qt4 使用 SIGNAL() 和 SLOT() 宏关联,Qt5 推荐使用函数指针,两者差异如下:

特性 Qt4 写法(兼容旧版本) Qt5 写法(推荐)
语法 connect(btn, SIGNAL(clicked()), this, SLOT(close())) connect(btn, &QPushButton::clicked, this, &QWidget::close)
类型检查 无(编译不报错,运行无效) 有(编译时检查类型,减少错误)
重载支持 不支持(无法区分重载信号 / 槽) 支持(需用函数指针指定重载版本)
灵活性 高(支持 Lambda、函数指针)

8.2 常见避坑指南

  • 忘记添加Q_OBJECT:导致信号槽无法生效,编译可能报错 "undefined reference to vtable";
  • 信号 / 槽参数不匹配:信号参数个数少于槽,或类型不匹配,Qt5 会编译报错,Qt4 无提示但运行无效;
  • 关联顺序错误:先发射信号,后关联信号槽,导致信号无法触发槽;
  • Lambda 捕获列表不当 :使用[&]捕获局部变量,变量销毁后 Lambda 未执行,会出现野指针;
  • 未继承QObject :信号槽依赖QObject,自定义类必须直接或间接继承。

九、信号与槽的优缺点及总结

9.1 信号与槽的优点

  • 松散耦合:信号发送者无需知道接收者,接收者无需知道信号来源,修改一方不影响另一方;
  • 灵活多样:支持多对多关联、带参数通信、Lambda 简化写法,适配所有 GUI 场景;
  • 易于维护:代码逻辑清晰,信号与槽的关联关系一目了然。

9.2 信号与槽的缺点

  • 效率略低:相比回调函数,信号槽存在遍历关联、参数编组等开销(日常场景可忽略);
  • 调试难度:多对多关联时,信号触发后多个槽执行,排查问题需明确关联顺序。

9.3 信号与槽总结

结束语

信号与槽是 Qt 开发的核心基石,掌握后可轻松实现控件通信、事件响应等核心功能。本文系统梳理了 Qt 信号与槽的核心原理、各类连接方式及实战技巧,从基础语法到 Lambda 进阶用法,同时对比了 Qt4 与 Qt5 的写法差异,帮大家厘清开发中的常见误区。信号与槽作为 Qt 的核心通信机制,是实现界面交互的关键,熟练掌握后可大幅提升 GUI 开发效率。希望本篇内容能帮助你夯实 Qt 交互开发基础,为后续复杂项目开发做好铺垫。

相关推荐
用户805533698031 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner1 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz6 天前
QML Hello World 入门示例
qt
xcyxiner9 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner10 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner10 天前
DicomViewer (添加模型类)3
qt
xcyxiner11 天前
DicomViewer (目录调整) 2
qt
xcyxiner11 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
LDR00613 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术13 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript