【Qt】信号与槽

目录

一、信号与槽概述

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

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

[1.3 槽的本质](#1.3 槽的本质)
二、信号与槽的基本使用

[2.1 connect函数连接信号与槽](#2.1 connect函数连接信号与槽)

[2.2 Qt Creator可视化生成信号槽](#2.2 Qt Creator可视化生成信号槽)
三、自定义信号与槽

[3.1 自定义信号与槽的语法规则](#3.1 自定义信号与槽的语法规则)

[3.2 基础自定义示例](#3.2 基础自定义示例)

[3.3 带参数的信号与槽(含重载)](#3.3 带参数的信号与槽(含重载))
四、信号与槽的连接方式

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

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

[4.3 多对一连接](#4.3 多对一连接)
五、信号与槽的高级用法

[5.1 信号与槽的断开(disconnect)](#5.1 信号与槽的断开(disconnect))

[5.2 Lambda表达式作为槽函数](#5.2 Lambda表达式作为槽函数)
六、信号与槽的优缺点


一、信号与槽概述

信号与槽是Qt特有的消息传输机制,用于解决控件间的通信问题。在传统GUI编程中,通常使用回调函数实现控件交互,但回调函数存在耦合度高、可读性差等问题。Qt的信号槽机制通过松耦合设计,让相互独立的控件能够灵活关联。

1.1 核心概念

  • 事件:用户与控件的交互行为(如点击按钮、关闭窗口、键盘输入等)。
  • 信号 :事件触发时,控件发出的"通知"(如按钮被点击时发出clicked()信号)。
  • :接收信号并做出响应的函数(如窗口接收clicked()信号后执行close()函数关闭自身)。
  • 关联 :通过connect()函数将信号发送者、信号、接收者、槽函数绑定,实现"信号触发→槽函数执行"的逻辑。

核心价值:信号发送者无需知道接收者的存在,接收者也无需知道哪些信号触发自己,完全解耦。例如"按钮"和"窗口"是独立控件,通过信号槽关联后,点击按钮即可关闭窗口。

1.2 信号的本质

信号的本质是事件的抽象表示,当用户操作控件(或系统触发事件)时,Qt对应的类会自动发出对应的信号。

关键特性:
  1. 信号由实例化的类对象发出(如QPushButton实例发出clicked()信号)。
  2. 信号的呈现形式是函数(称为信号函数),无需开发者实现,Qt通过元编程(Meta Programming)自动生成。
  3. 信号函数用signals关键字修饰,只需要声明,不需要定义。
  4. 常见信号场景:
    • 控件交互:按钮点击(clicked())、输入框文本变化(textChanged())。
    • 系统事件:窗口刷新(paintEvent())、鼠标移动(mouseMoveEvent())、键盘按下(keyPressEvent())。

1.3 槽的本质

槽(Slot)是响应信号的普通C++函数 ,与常规函数的区别仅在于:槽函数可以通过connect()与信号关联,当信号触发时自动执行。

关键特性:
  1. 槽函数可以定义在publicprotectedprivate作用域,支持参数和重载,但不能有默认参数
  2. 槽函数需要显式实现(与普通函数一致),用public slots/protected slots/private slots修饰(Qt5后可直接放在public等作用域,无需slots关键字)。
  3. 槽函数可直接调用(不依赖信号触发)。
  4. 底层逻辑:信号槽机制本质是函数调用,例如"点击按钮关闭窗口"就是clicked()信号函数调用close()槽函数。

二、信号与槽的基本使用

信号与槽的核心操作是"连接",Qt通过QObject类提供的静态函数connect()实现关联。QObject是Qt中绝大多数类的父类,所有支持信号槽的类都必须继承自QObject并添加Q_OBJECT宏。

2.1 connect函数连接信号与槽

函数原型(Qt5):
cpp 复制代码
connect(
    const QObject *sender,    // 信号发送者(如按钮对象)
    const char *signal,       // 发送的信号(信号函数)
    const QObject *receiver,  // 信号接收者(如窗口对象)
    const char *method,       // 响应的槽函数
    Qt::ConnectionType type = Qt::AutoConnection  // 连接方式(默认自动)
);
现代Qt(Qt5+)推荐语法(类型安全):
cpp 复制代码
connect(sender, &SenderClass::signalFunc, receiver, &ReceiverClass::slotFunc);
示例:点击按钮关闭窗口
cpp 复制代码
#include "widget.h"
#include <QPushButton>

Widget::Widget(QWidget *parent) : QWidget(parent)
{
    // 创建按钮(父对象为当前窗口)
    QPushButton *btn = new QPushButton("关闭窗口", this);
    // 调整窗口大小
    resize(800, 600);
    
    // 连接信号与槽:按钮点击信号 → 窗口关闭槽函数
    connect(btn, &QPushButton::clicked, this, &QWidget::close);
}

Widget::~Widget() {}

注意

  1. 发送者和接收者必须是QObject子类实例(或间接继承)。
  2. 信号函数和槽函数的参数列表需匹配(信号参数个数可≥槽函数,反之不行)。
  3. 若类中使用信号槽,必须在类声明中添加Q_OBJECT宏。

2.2 Qt Creator可视化生成信号槽

Qt Creator提供UI设计器,可通过拖拽控件+可视化操作生成信号槽代码,无需手动编写connect()

操作步骤:
  1. 新建Qt Widgets项目,勾选"生成UI设计文件"(.ui文件)。
  2. 双击widget.ui进入UI设计界面,拖拽一个PushButton控件到窗口。
  3. 选中按钮 → 右键 → 转到槽(Go to Slot)→ 选择clicked()信号 → 点击OK。
  4. Qt Creator自动生成槽函数声明(在widget.h)和定义(在widget.cpp)。
  5. 在槽函数中添加业务逻辑(如关闭窗口)。



自动生成的代码:
  • widget.h(头文件声明):

    cpp 复制代码
    #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();
    
    private slots:
        // 自动生成的槽函数(命名规则:on_对象名_信号名)
        void on_pushButton_clicked();
    
    private:
        Ui::Widget *ui;
    };
    #endif // WIDGET_H
  • widget.cpp(源文件实现):

    cpp 复制代码
    #include "widget.h"
    #include "ui_widget.h"
    
    Widget::Widget(QWidget *parent)
        : QWidget(parent)
        , ui(new Ui::Widget)
    {
        ui->setupUi(this);
    }
    
    Widget::~Widget()
    {
        delete ui;
    }
    
    // 实现关闭窗口功能
    void Widget::on_pushButton_clicked()
    {
        this->close();  // 窗口关闭
    }

命名规则说明

自动生成的槽函数名格式为on_XXX_SSS,其中:

  • XXX:控件的objectName属性(默认如pushButton)。
  • SSS:信号名(如clicked)。
    遵循该规则的槽函数会被Qt自动关联信号,无需手动connect(),但推荐显式使用connect()以提高可读性。

三、自定义信号与槽

Qt允许开发者自定义信号和槽,满足复杂业务场景(如自定义控件交互、跨类通信等)。自定义需遵循严格的语法规则。

3.1 自定义信号与槽的语法规则

1. 自定义信号规则
  • 必须在signals关键字下声明(无需public等修饰符)。
  • 返回值必须为void,只声明不实现(Qt自动生成实现)。
  • 支持参数和重载。
  • 发送信号时使用emit关键字(可选,仅用于代码可读性)。
2. 自定义槽函数规则
  • Qt5+:可声明在public/protected/private作用域(无需slots关键字);Qt4及之前必须在public slots等下声明。
  • 返回值必须为void,需要显式实现。
  • 支持参数和重载,参数列表需与关联的信号匹配(信号参数个数≥槽函数)。
3. 核心要求
  • 信号和槽的类必须继承自QObject并添加Q_OBJECT宏。
  • 关联信号与槽时,需先完成connect(),再发送信号(否则槽函数不响应)。

3.2 基础自定义示例

场景:老师发出"上课了"信号,学生响应"回到座位学习"
  1. 新建Teacher类(信号发送者)和Student类(信号接收者),均继承自QObject
步骤1:声明类(头文件)
  • teacher.h(老师类,发送信号):

    cpp 复制代码
    #ifndef TEACHER_H
    #define TEACHER_H
    
    #include <QObject>
    
    class Teacher : public QObject
    {
        Q_OBJECT
    public:
        explicit Teacher(QObject *parent = nullptr);
    
    signals:
        // 自定义信号:上课了
        void classBegin();
    };
    
    #endif // TEACHER_H
  • student.h(学生类,接收信号):

    cpp 复制代码
    #ifndef STUDENT_H
    #define STUDENT_H
    
    #include <QObject>
    #include <QDebug>
    
    class Student : public QObject
    {
        Q_OBJECT
    public:
        explicit Student(QObject *parent = nullptr);
    
    public slots:
        // 自定义槽函数:响应上课信号
        void goToClass();
    };
    
    #endif // STUDENT_H
  • widget.h(主窗口,实例化对象并关联):

    cpp 复制代码
    #ifndef WIDGET_H
    #define WIDGET_H
    
    #include <QWidget>
    #include "teacher.h"
    #include "student.h"
    
    class Widget : public QWidget
    {
        Q_OBJECT
    
    public:
        Widget(QWidget *parent = nullptr);
        ~Widget();
    
    private:
        Teacher *teacher;  // 信号发送者
        Student *student;  // 信号接收者
    };
    #endif // WIDGET_H
步骤2:实现类(源文件)
  • teacher.cpp

    cpp 复制代码
    #include "teacher.h"
    
    Teacher::Teacher(QObject *parent) : QObject(parent) {}
  • student.cpp

    cpp 复制代码
    #include "student.h"
    
    Student::Student(QObject *parent) : QObject(parent) {}
    
    // 槽函数实现:回到座位学习
    void Student::goToClass()
    {
        qDebug() << "学生:回到座位,开始学习!";
    }
  • widget.cpp(关联信号与槽并发送信号):

    cpp 复制代码
    #include "widget.h"
    #include <QPushButton>
    
    Widget::Widget(QWidget *parent) : QWidget(parent)
    {
        // 实例化对象
        teacher = new Teacher(this);
        student = new Student(this);
    
        // 关联信号与槽:老师的classBegin信号 → 学生的goToClass槽函数
        connect(teacher, &Teacher::classBegin, student, &Student::goToClass);
    
        // 创建按钮触发信号
        QPushButton *btn = new QPushButton("上课铃响", this);
        btn->move(100, 100);
        resize(800, 600);
    
        // 按钮点击 → 发送老师的classBegin信号
        connect(btn, &QPushButton::clicked, this, [=]() {
            emit teacher->classBegin();  // 发送自定义信号
        });
    }
    
    Widget::~Widget() {}
运行结果:

3.3 带参数的信号与槽(含重载)

信号和槽支持参数传递,核心规则:信号的参数列表必须与槽函数的参数列表兼容(信号参数个数≥槽函数,参数类型一致)。

场景:老师点名(传递学生姓名),学生应答
步骤1:重载信号与槽
  • teacher.h(重载带参数的信号):

    cpp 复制代码
    signals:
        void callStudent();          // 无参信号
        void callStudent(QString name);  // 带参数信号(传递学生姓名)
  • student.h(重载带参数的槽函数):

    cpp 复制代码
    public slots:
        void answer();               // 无参槽函数
        void answer(QString name);   // 带参数槽函数
步骤2:实现槽函数
  • student.cpp

    cpp 复制代码
    // 无参应答
    void Student::answer()
    {
        qDebug() << "学生:到!";
    }
    
    // 带参数应答(接收学生姓名)
    void Student::answer(QString name)
    {
        qDebug() << "学生" << name << ":到!";
    }
步骤3:关联重载信号与槽(需用函数指针指定)

由于重载函数存在歧义,需通过函数指针明确指定关联的信号和槽:

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

Widget::Widget(QWidget *parent) : QWidget(parent)
{
    teacher = new Teacher(this);
    student = new Student(this);

    // 1. 关联无参信号与槽(用函数指针消除歧义)
    void (Teacher::*signalNoParam)() = &Teacher::callStudent;
    void (Student::*slotNoParam)() = &Student::answer;
    connect(teacher, signalNoParam, student, slotNoParam);

    // 2. 关联带参数信号与槽(用函数指针消除歧义)
    void (Teacher::*signalWithParam)(QString) = &Teacher::callStudent;
    void (Student::*slotWithParam)(QString) = &Student::answer;
    connect(teacher, signalWithParam, student, slotWithParam);

    // 按钮1:触发无参信号
    QPushButton *btn1 = new QPushButton("点名(无参)", this);
    btn1->move(100, 100);

    // 按钮2:触发带参数信号
    QPushButton *btn2 = new QPushButton("点名(带参数)", this);
    btn2->move(100, 150);

    resize(800, 600);

    // 绑定按钮点击事件
    connect(btn1, &QPushButton::clicked, this, [=]() {
        emit teacher->callStudent();  // 发送无参信号
    });

    connect(btn2, &QPushButton::clicked, this, [=]() {
        emit teacher->callStudent("小明");  // 发送带参数信号
    });
}
运行结果:

参数匹配规则

  1. 信号参数个数可以多于槽函数(多余参数会被忽略)。
  2. 槽函数参数个数不能多于信号(否则无法匹配,编译报错)。
  3. 参数类型必须一致(如信号是int,槽函数不能是QString)。

四、信号与槽的连接方式

信号与槽支持多种连接模式,满足不同场景下的通信需求,核心分为一对一、一对多、多对一三种。

4.1 一对一连接

两种形式:
  1. 一个信号连接一个槽函数。
  2. 一个信号连接另一个信号(信号转发)。
形式1:信号→槽
cpp 复制代码
// 按钮点击 → 窗口关闭(一对一)
connect(btn, &QPushButton::clicked, this, &QWidget::close);
形式2:信号→信号(转发)
cpp 复制代码
// 按钮点击 → 转发为老师的classBegin信号
connect(btn, &QPushButton::clicked, teacher, &Teacher::classBegin);
// 老师的classBegin信号 → 学生的goToClass槽函数
connect(teacher, &Teacher::classBegin, student, &Student::goToClass);

场景:需要多层信号传递时(如子控件信号转发给父控件)。

4.2 一对多连接

一个信号触发多个槽函数,槽函数执行顺序与connect()调用顺序一致。

示例:点击按钮触发三个槽函数
cpp 复制代码
#include "widget.h"
#include <QPushButton>
#include <QDebug>

class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget *parent = nullptr) : QWidget(parent)
    {
        QPushButton *btn = new QPushButton("触发多个槽", this);
        btn->move(100, 100);
        resize(800, 600);

        // 一个信号连接三个槽函数
        connect(btn, &QPushButton::clicked, this, &Widget::slot1);
        connect(btn, &QPushButton::clicked, this, &Widget::slot2);
        connect(btn, &QPushButton::clicked, this, &Widget::slot3);
    }

private slots:
    void slot1() { qDebug() << "执行槽函数1"; }
    void slot2() { qDebug() << "执行槽函数2"; }
    void slot3() { qDebug() << "执行槽函数3"; }
};
运行结果:

4.3 多对一连接

多个信号触发同一个槽函数,任何一个信号触发都会执行该槽。

示例:两个按钮+一个键盘信号,共同触发"打印信息"槽函数
cpp 复制代码
#include "widget.h"
#include <QPushButton>
#include <QKeyEvent>
#include <QDebug>

class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget *parent = nullptr) : QWidget(parent)
    {
        // 按钮1
        QPushButton *btn1 = new QPushButton("按钮1", this);
        btn1->move(100, 100);
        // 按钮2
        QPushButton *btn2 = new QPushButton("按钮2", this);
        btn2->move(100, 150);

        resize(800, 600);
        setFocusPolicy(Qt::StrongFocus);  // 启用键盘焦点

        // 三个信号连接同一个槽函数
        connect(btn1, &QPushButton::clicked, this, &Widget::slotPrint);
        connect(btn2, &QPushButton::clicked, this, &Widget::slotPrint);
        // 键盘按下信号(需重写keyPressEvent)
    }

protected:
    // 重写键盘按下事件,发送自定义信号
    void keyPressEvent(QKeyEvent *event) override
    {
        if (event->key() == Qt::Key_Space) {
            emit spacePressed();  // 空格键按下信号
        }
    }

signals:
    void spacePressed();  // 自定义键盘信号

private slots:
    void slotPrint() { qDebug() << "触发共同槽函数!"; }
};
运行结果:

点击按钮1、按钮2或按下空格键,均输出:触发共同槽函数!

五、信号与槽的高级用法

5.1 信号与槽的断开(disconnect)

当不需要信号与槽的关联时,可使用disconnect()函数断开连接。用法与connect()完全一致。

示例:动态连接与断开
cpp 复制代码
#include "widget.h"
#include <QPushButton>
#include <QDebug>

class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget *parent = nullptr) : QWidget(parent)
    {
        btn1 = new QPushButton("连接槽函数", this);
        btn1->move(100, 100);
        btn2 = new QPushButton("断开槽函数", this);
        btn2->move(100, 150);

        resize(800, 600);

        // 按钮1:连接信号槽
        connect(btn1, &QPushButton::clicked, this, [=]() {
            connect(btn1, &QPushButton::clicked, this, &Widget::slotPrint);
            qDebug() << "已连接槽函数";
        });

        // 按钮2:断开信号槽
        connect(btn2, &QPushButton::clicked, this, [=]() {
            disconnect(btn1, &QPushButton::clicked, this, &Widget::slotPrint);
            qDebug() << "已断开槽函数";
        });
    }

private slots:
    void slotPrint() { qDebug() << "槽函数执行"; }

private:
    QPushButton *btn1;
    QPushButton *btn2;
};
注意事项:
  1. disconnect()的参数必须与connect()完全一致(包括发送者、信号、接收者、槽函数)。
  2. 若发送者或接收者被销毁,Qt会自动断开关联,无需手动处理。

5.2 Lambda表达式作为槽函数

Qt5支持使用Lambda表达式作为槽函数,无需显式声明槽函数。

Lambda表达式语法:
cpp 复制代码
[捕获列表](参数列表) 选项 -> 返回值类型 { 函数体 };
核心捕获列表说明:
捕获符号 含义
[] 不捕获任何外部变量
[=] 值传递捕获所有外部变量(副本,只读)
[&] 引用传递捕获所有外部变量
[this] 捕获当前类的this指针(可访问成员)
[a, &b] 值传递捕获a,引用传递捕获b
示例:Lambda作为槽函数
cpp 复制代码
#include "widget.h"
#include <QPushButton>

Widget::Widget(QWidget *parent) : QWidget(parent)
{
    QPushButton *btn = new QPushButton("关闭窗口", this);
    resize(800, 600);

    // Lambda表达式作为槽函数
    connect(btn, &QPushButton::clicked, this, [=]() {
        this->close();  // [=]捕获所有外部变量(值传递)
    });
}

注意

  1. Qt5+默认启用C++11,无需额外配置;Qt4需在.pro文件添加CONFIG += C++11
  2. 避免使用[&]捕获局部变量(局部变量销毁后,Lambda可能访问悬空引用)。

六、信号与槽的优缺点

优点:

  1. 松耦合:信号发送者和接收者无直接依赖,无需知道对方存在,便于维护和扩展。
  2. 灵活性高:支持一对一、一对多、多对一连接,支持参数传递和重载。
  3. 可读性强 :通过connect()直接关联信号与槽,逻辑清晰,无需复杂回调链。

缺点:

  1. 效率略低:相比直接函数调用,信号槽需要遍历关联列表、参数编组等操作。
  2. 调试难度大:复杂连接关系下,信号传递路径难以追踪(可通过Qt Creator的"信号槽调试器"辅助)。
  3. 语法限制 :需继承QObject并添加Q_OBJECT宏,不支持非QObject子类使用。
相关推荐
爱学习的阿磊2 小时前
模板代码跨编译器兼容
开发语言·c++·算法
带鱼吃猫2 小时前
C++STL:从 0 到 1 手写 C++ string以及高频易错点复盘
开发语言·c++
u0109272712 小时前
代码覆盖率工具实战
开发语言·c++·算法
码云数智-大飞2 小时前
零拷贝 IPC:用内存映射文件打造 .NET 高性能进程间通信队列
java·开发语言·网络
懈尘2 小时前
深入理解Java的HashMap扩容机制
java·开发语言·数据结构
Beginner x_u2 小时前
JavaScript 核心知识索引(面试向)
开发语言·javascript·面试·八股
yqd6662 小时前
RabbitMQ用法和面试题
java·开发语言·面试
白日梦想家6812 小时前
JavaScript性能优化实战系列(三篇完整版)
开发语言·javascript·性能优化
请注意这个女生叫小美2 小时前
C语言 实例20 25
c语言·开发语言·算法