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 交互开发基础,为后续复杂项目开发做好铺垫。

相关推荐
lsx2024062 小时前
C AI 编程助手:助力开发者高效编程
开发语言
沐知全栈开发2 小时前
Eclipse 编译项目指南
开发语言
无限进步_2 小时前
C++11概览与统一初始化
开发语言·c++
笨蛋不要掉眼泪2 小时前
Java并发编程:内存可见性与synchronized同步机制
java·开发语言·并发
爱喝水的鱼丶2 小时前
SAP-ABAP:数据类型与数据对象(8篇) 第四篇:关系映射篇——从类型定义到对象实例的转化逻辑
开发语言·数据库·学习·sap·abap
水无痕simon2 小时前
1. Guava 介绍
开发语言·python·guava
AI科技星2 小时前
全域数学公理:基于32维超复数与易经卦爻的宇宙大一统理论(整理版)
c语言·开发语言·线性代数·量子计算·agi
之歆2 小时前
DAY_13JavaScript DOM 操作完全指南:实战案例、性能优化与业务价值(下)
开发语言·前端·javascript·性能优化·ecmascript
Brilliantwxx2 小时前
【C++】深度剖析 · 继承 (虚基表+虚函数表)
开发语言·c++