Qt 信号与槽:信号产生与处理之间的重要函数

文章目录

    • [1. 信号和槽概述](#1. 信号和槽概述)
    • [2. 信号和槽的使用](#2. 信号和槽的使用)
    • [3. 自定义信号和槽](#3. 自定义信号和槽)
    • 4.信号与槽的连接方式
      • [1. 一对一:一个信号连接一个槽](#1. 一对一:一个信号连接一个槽)
      • [2. 一对多:一个信号连接多个槽](#2. 一对多:一个信号连接多个槽)
      • [3. 多对一:多个信号连接同一个槽](#3. 多对一:多个信号连接同一个槽)
    • [5. 信号和槽的其他说明](#5. 信号和槽的其他说明)
      • [5.1 信号与槽的断开](#5.1 信号与槽的断开)
      • [5.2 使用Lambda表达式定义槽函数](#5.2 使用Lambda表达式定义槽函数)
      • [5.4 信号与槽的优缺点](#5.4 信号与槽的优缺点)

1. 信号和槽概述

  1. 事件:

    就是用户和控件的互动动作,比如:点击按钮、关闭窗口、鼠标移动/按下、键盘输入文字、窗口刷新等,这些都是触发后续反应的"引子"。

  2. 信号:

    事件发生后,Qt控件会主动发信号,本质是把事件变成了可被识别的"通知",而且是以函数的形式呈现。

    • 实例:点击按钮时,按钮会发出 "clicked(被点击)" 信号;关闭窗口时,窗口会发出 "close(要关闭)" 信号;鼠标移到输入框上,输入框会发出 ** "mousePress(被点击)" ** 信号。
    • 注意:信号是"谁触发事件谁发",比如按钮被点就按钮发信号,窗口被关就窗口发信号。
  3. 槽:

    规定收到信号后要做的事,本质是个普通函数,但能和信号"绑定"。

    • 实例:窗口收到按钮的 "clicked" 信号后,执行 "close(关闭自己)" 的槽函数,这就是"点击按钮关窗口";输入框收到自己的 "mousePress" 信号后,执行"显示闪烁光标"的槽函数,这就是"点输入框就准备输文字"。
    • 槽的特点:和普通函数一样,能随便放在类的公有/私有区域,能传参数、能重载,也能直接调用(但不能有默认参数),唯一特别的是能跟信号绑定。
  4. 信号与槽:

    按钮和窗口本来是没关系的,点击按钮不会影响窗口,但信号与槽把两者连起来:

    • 没连线:点按钮只是按钮自己有反应,窗口没动静;
    • 连了线:按钮发"clicked"信号,窗口的"close"槽接收到,就会自动执行关闭操作,实现"点按钮关窗口"这种联动效果。

说明:

  1. 信号和槽机制底层是通过函数间的相互调用实现的。每个信号都可以用函数来表示,称为信号函数;每个槽也可以用函数表示,称为槽函数。例如:"按钮被按下"这个信号可以用clicked()函数表示,"窗口关闭"这个槽可以用close()函数表示,假如使用信号和槽机制实现:"点击按钮会关闭窗口"的功能,其实就是clicked()函数调用close()函数的效果。

  2. 信号函数和槽函数通常位于某个类中,和普通的成员函数相比,它们的特别之处在于:

  • 信号函数用signals关键字修饰,槽函数用public slots、protected slots或者private slots修饰。signals和slots是Qt在C++的基础上扩展的关键字,专门用来指明信号函数和槽函数;
  • 信号函数只需要声明,不需要定义(实现),而槽函数需要定义(实现)。

2. 信号和槽的使用

连接信号和槽

一、前提

connectQObject类的静态成员函数(Qt中大部分控件/类都直接/间接继承QObject),核心作用是 将"信号发送者"的信号,与"接收者"的槽函数绑定,实现"信号触发→槽函数自动执行"的联动。

二、connect函数
1. 函数原型(Qt4)
cpp 复制代码
connect(
    const QObject *sender,    // 信号发送者(控件/对象指针)
    const char *signal,       // 发送的信号(Qt4用SIGNAL宏,Qt5用函数指针)
    const QObject *receiver,  // 信号接收者(控件/对象指针)
    const char *method,       // 响应的槽函数(Qt4用SLOT宏,Qt5用函数指针)
    Qt::ConnectionType type = Qt::AutoConnection  // 关联方式,默认自动
);
2. 函数原型(Qt5)
cpp 复制代码
static QMetaObject::Connection connect(const QObject *sender, const char *signal,
const QObject *receiver, const char *member, Qt::ConnectionType = Qt::AutoConnection);
//Qt4还需要宏进行强转
static inline QMetaObject::Connection connect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, 
const typename QtPrivate::FunctionPointer<Func2>::Object *receiver, Func2 slot, Qt::ConnectionType type = Qt::AutoConnection)
//Qt5更安全的写法,无需SIGNAL/SLOT宏,直接用&类名::信号名/&类名::槽函数名,语法更简洁、编译时能检查错误:
3.(点击按钮关闭窗口)
cpp 复制代码
// 1. 创建发送者:按钮对象(信号发送者)
QPushButton *btn = new QPushButton("关闭窗口", this);
// 2. 手动关联信号和槽
connect(
    btn,                  // 发送者:按钮
    &QPushButton::clicked,// 信号:按钮的"被点击"信号
    this,                 // 接收者:当前窗口(Widget)
    &QWidget::close       // 槽函数:窗口的"关闭自己"函数
);
  • 效果:点击btn按钮,触发clicked信号,窗口接收信号后执行close槽函数,实现窗口关闭。
三、可视化生成

Qt Creator提供可视化操作,能自动生成槽函数和关联关系,适合快速开发,原文详细步骤如下:

1. 核心原理

Qt Creator会根据"槽函数命名规则"自动关联信号和槽,无需手动写connect------槽函数命名必须符合 on_对象名_信号名 规则:

  • on:固定前缀;
  • 对象名:控件的objectName属性(如按钮的objectNamepushButton);
  • 信号名:控件的信号(如clicked)。
2. 点击按钮关闭窗口

新建带UI文件的项目(确保生成widget.ui),打开widget.ui,拖入一个按钮控件,可修改按钮的objectName(默认是pushButton)。

  • 选中按钮 → 鼠标右键 → 选择"转到槽..." → 选择要关联的信号(如clicked())→ 确定;
  • Qt Creator会自动做两件事:
    1. widget.h中添加槽函数声明(符合命名规则):

      cpp 复制代码
      private slots:
          void on_pushButton_clicked();  // on_对象名_信号名
    2. widget.cpp中生成槽函数空实现:

      cpp 复制代码
      void Widget::on_pushButton_clicked()
      {
          // 后续添加功能代码
      }

添加槽函数功能

在自动生成的槽函数中写逻辑(如关闭窗口):

cpp 复制代码
void Widget::on_pushButton_clicked()
{
    this->close();  // 窗口关闭功能
}
  • 效果:无需手动写connect,Qt会自动关联"按钮的clicked信号"和"on_pushButton_clicked槽函数",点击按钮直接触发窗口关闭。
  • Qt 中QMetaObject类的静态函数connectSlotsByName自动匹配并连接信号与槽,如果找不到对应的槽函数,则会报错!

3. 自定义信号和槽

基本语法

在Qt中,允许自定义信号的发送方以及接收方,即可以自定义信号函数和槽函数。但是对于自定义的信号函数和槽函数有一定的书写规范。

1、自定义信号函数书写规范

  1. 自定义信号函数必须写到"signals"下;
  2. 返回值为void只需要声明,不需要实现;
  3. 可以有参数,也可以发生重载;

2、自定义槽函数书写规范

  1. 早期的Qt版本要求槽函数必须写到"public slots"下,但是现在高级版本的Qt允许写到类的"public"作用域中或者全局下;
  2. 返回值为void需要声明,也需要实现;
  3. 可以有参数,可以发生重载;

3、发送信号

使用"emit"关键字发送信号。"emit"是一个空的宏。"emit"其实是可选的,没有什么含义,只是为了提醒开发人员。

结合前面已经创建的button,实例如下:

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

#include <QWidget>
#include "QPushButton"

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

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

    QPushButton *mybutton;
private slots:
    void on_pushButton_clicked();
public slots:
    void handlerclick();
signals:
    void mysignal();

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

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


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

    mybutton = new QPushButton("Helloworld",this);
    mybutton->move(200,200);
    //connect(mybutton,&QPushButton::clicked,this,&Widget::handlerclick);
    connect(this,&Widget::mysignal,this,&Widget::handlerclick);
}

void Widget::handlerclick()
{
    if(mybutton->text()=="Helloworld")
    {
        mybutton->setText("helloqt");
    }
    else
    {
        mybutton->setText("hellolinux");
    }
}

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



void Widget::on_pushButton_clicked()
{
    //发出自定义的信号,emit代表发送信号,emit可以省略
    emit mysignal();
    //this->close();
}

带参数的信号和槽

Qt的信号和槽也支持带有参数,同时也可以支持重载,信号函数的参数列表要和对应连接的槽函数参数列表一致。

一、槽函数重载的情况

有两个同名槽:

cpp 复制代码
void handlerclick();
void handlerclick(const QString& str);

如果用旧语法:

cpp 复制代码
connect(this, SIGNAL(mysignal(QString,int)),
        this, SLOT(handlerclick(QString)));

字符串里写死了签名,不会有二义性。

用新语法(函数指针语法)时,如果槽有重载,就必须消除重载歧义,常见写法有两种:

cpp 复制代码
// 1. 老版本指明函数指针
void (Widget::* sigptr)(const QString&,int)=&Widget::mysignal;
void (Widget::* handptr)(const QString&)=&Widget::handlerclick;

// 2. 显示类型定义
auto sigptr = static_cast<void (Widget::*)(const QString&,int)>(&Widget::mysignal);
auto handptr   = static_cast<void (Widget::*)(const QString&)>(&Widget::handlerclick);

二、信号和槽参数个数、类型关系

Qt 官方规则:槽的签名必须与信号兼容,具体来说是:

槽可以比信号少参数,但不能比信号多参数;从前往后对应的参数类型必须相同或可隐式转换。 ([Qt Documentation][1])

1. 参数完全相等

形式:

cpp 复制代码
signals:
    void mysignal(const QString& str, int signo);

public slots:
    void handlerclick(const QString& str, int signo);

连接:

cpp 复制代码
connect(this, &Widget::mysignal,
        this, &Widget::handlerclick);

emit mysignal("触发信号", 1);

特点:

  • 参数个数、类型一一对应。
  • 最直观,也最不容易出错。
  • 一般建议业务里优先采用这种方式。

2. 信号参数多于槽参数

这是你当前代码的思路:

cpp 复制代码
signals:
    void mysignal(const QString& str, int signo);

public slots:
    void handlerclick(const QString& str);

连接(注意要指定重载):

cpp 复制代码
connect(this, &Widget::mysignal,
        this, QOverload<const QString&>::of(&Widget::handlerclick));

发射信号时仍然要把所有参数补全:

cpp 复制代码
emit mysignal("触发信号", 1);   // int 参数会被忽略掉

规则说明:

  • Qt 允许槽比信号少参数,多余的信号参数会被默默丢弃。([Qt Documentation][1])

  • 但你在 emit 的时候,必须满足信号自己的完整签名,少传一个 int,直接编译不过:

    cpp 复制代码
    emit mysignal("触发信号"); // 少了一个 int,编译错误

适用场景:

  • 信号携带了比较详细的信息,但某些槽只关心前面几项。

3. 信号参数少于槽参数

例如:

cpp 复制代码
signals:
    void mysignal(const QString& str);      // 1 个参数

public slots:
    void handlerclick(const QString& str, int signo); // 2 个参数

直接用函数指针语法连接会编译失败 ,因为 Qt 无法凭空给槽补出 int signo 这个值。

几种处理方式:

  1. 槽使用默认参数 + 旧宏语法(只对宏语法有效)([Qt Documentation][3])

    cpp 复制代码
    public slots:
        void handlerclick(const QString& str, int signo = 0);
    
    connect(this, SIGNAL(mysignal(QString)),
            this, SLOT(handlerclick(QString)));  // signo 用默认值 0
  2. 用 lambda 适配(推荐写法,配合新语法)

    cpp 复制代码
    connect(this, &Widget::mysignal,
            this, [this](const QString& s){
                handlerclick(s, 0); // 手动补上第二个参数
            });
  3. 直接修改槽的签名,让槽参数不多于信号,这是最干净的写法。


4. 信号和槽参数类型不匹配

比如:

cpp 复制代码
signals:
    void mysignal(int value);

public slots:
    void handlerclick(const QString& s);

这种情况,类型完全对不上,既不能直接连接,也不能靠"多/少参数"来弥补,会报不兼容错误。

通常做法:

  • 改槽签名,使类型匹配;
  • 或者同样用 lambda 做一个适配层,在 lambda 里完成 int → QString 的转换。

总结成几句话:

  1. 槽的参数个数必须小于等于信号的参数个数;不能比信号多。
  2. 从前往后对应的参数类型必须相同或可隐式转换,否则连接失败。
  3. 信号参数多于槽参数时,多出来的参数会被忽略;但 emit 时必须把信号声明中的参数全部传齐。
  4. 信号参数少于槽参数时,函数指针语法不能直接连接;可以用旧宏语法配合默认参数,或者用 lambda 做适配。
  5. 槽/信号重载时,用新语法要通过 static_castQOverload 消除重载歧义。
  6. const QString& 不是强制要求,只是推荐用法,用于避免拷贝并保证不修改参数。

4.信号与槽的连接方式

1. 一对一:一个信号连接一个槽

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 1. 用 static_cast 明确指定信号和槽的函数指针类型
    auto sigptr  = static_cast<void (Widget::*)(const QString&, int)>(
                       &Widget::mysignal);
    auto handptr = static_cast<void (Widget::*)(const QString&)>(
                       &Widget::handlerclick);

    // 2. 创建一个按钮
    mybutton = new QPushButton("Helloworld", this);
    mybutton->move(200, 200);

    // 3. 连接信号和槽:mysignal(const QString&, int) -> handlerclick(const QString&)
    connect(this, sigptr, this, handptr);
}

这就是最标准的一对一

mysignal(const QString&, int)handlerclick(const QString&)

还可以是信号对信号:

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 1. 精确指定两个信号的函数指针类型
    auto sigptr1 = static_cast<void (Widget::*)(const QString&, int)>(
                       &Widget::mysignal);
    auto sigptr2 = static_cast<void (Widget::*)(const QString&)>(
                       &Widget::forwardSignal);

    // 2. 槽函数指针(有 QString 参数的那个重载)
    auto handptr = static_cast<void (Widget::*)(const QString&)>(
                       &Widget::handlerclick);

    // 3. 创建按钮
    mybutton = new QPushButton("Helloworld", this);
    mybutton->move(200, 200);

    
    connect(this, sigptr1, this, sigptr2);

    // 再把 forwardSignal 连到真正的槽上
    connect(this, sigptr2, this, handptr);

}

mysignal(const QString&, int) -> forwardSignal(const QString&) -> handlerclick

2. 一对多:一个信号连接多个槽

同一个 mysignal,可以同时连到多个槽,只要参数兼容即可。

假设你再加两个槽(示意代码,实际你可以自己实现):

cpp 复制代码
public slots:
    void handlerclick(const QString&);       // 已有
    void logMessage(const QString&);         // 打印日志
    void showMessageBox(const QString&);     // 弹提示框

在构造函数里可以这样连:

cpp 复制代码
// 原来的
connect(this, sigptr, this, handptr);   // mysignal -> handlerclick(const QString&)

// 直接用函数指针也行(无重载时可以省略 static_cast,如果有重载仍然需要)
connect(this, sigptr, this, &Widget::logMessage);
connect(this, sigptr, this, &Widget::showMessageBox);

当你 emit mysignal("触发信号", 0); 时:

  1. handlerclick("触发信号"); 被调用(改变按钮文字)
  2. logMessage("触发信号"); 被调用(比如 qDebug 打印)
  3. showMessageBox("触发信号"); 被调用(例如弹出 QMessageBox)
  • 一个信号可以连接任意多个槽;
  • 发射一次信号,会按连接顺序依次调用所有槽;
  • 这就是一对多

3. 多对一:多个信号连接同一个槽

我们可以让多个信号都去触发同一个槽 handlerclick()handlerclick(const QString&)

例如:

  • 自定义信号 mysignal(const QString&, int)
  • 按钮点击信号 QPushButton::clicked()

都去调用无参版 handlerclick()

cpp 复制代码
// 1)mysignal -> handlerclick()
auto sigptr_noarg = static_cast<void (Widget::*)(const QString&, int)>(&Widget::mysignal);
connect(this, sigptr_noarg, this, &Widget::handlerclick);  // 注意这里槽是无参版本

// 2)按钮的 clicked() -> handlerclick()
connect(mybutton, &QPushButton::clicked, this, &Widget::handlerclick);

当手动 emit mysignal("触发信号", 0);或者点击 mybutton都会触发同一个槽:

cpp 复制代码
void Widget::handlerclick()
{
    // 在这里做统一处理,比如:
    qDebug() << "有东西触发了 handlerclick() 槽";
}

5. 信号和槽的其他说明

5.1 信号与槽的断开

在 Qt 中,disconnect() 是一个非常重要的函数,用于断开信号与槽之间的连接。信号和槽是 Qt 的核心机制之一,用于实现对象之间的通信。当一个对象发出信号时,Qt 会触发一个或多个连接到该信号的槽。disconnect() 函数允许用户断开这种连接,从而使得信号不再触发槽。

基本语法

cpp 复制代码
bool QObject::disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method);
  • sender:发出信号的对象。
  • signal:发出的信号。
  • receiver:接收信号并处理的对象。
  • method:接收到信号后执行的方法。

示例

假设我们有一个按钮,点击按钮时触发一个信号并调用一个槽。我们可以在某个时刻断开这个连接,使得按钮的点击不再触发槽。

连接信号和槽
cpp 复制代码
QPushButton *button = new QPushButton("Click me", this);
connect(button, &QPushButton::clicked, this, &MyClass::onButtonClicked);
断开连接
cpp 复制代码
disconnect(button, &QPushButton::clicked, this, &MyClass::onButtonClicked);

这会断开 buttonclicked 信号与 MyClass 中的 onButtonClicked 槽之间的连接。

作或其他条件动态改变信号槽行为时非常有用。

5.2 使用Lambda表达式定义槽函数

  1. Qt5信号与槽的灵活性优化

    Qt5在Qt4基础上提升了信号与槽的灵活性,允许将任意函数作为槽函数;若想简化槽函数编写(比如不想定义函数名),可通过Lambda表达式实现。

  2. Lambda表达式的基础属性

    Lambda表达式是C++11新增特性,用于定义并创建匿名函数对象,核心作用是简化编程工作。

  3. Lambda表达式的语法格式

    cpp 复制代码
    [capture] (params) opt -> ret {
        Function body;  // 函数体
    };

    各占位符的含义:

    • capture:捕获列表(用于引入外部变量)
    • params:参数表(接收信号传递的参数)
    • opt:函数选项(如mutable等)
    • ret:返回值类型(可省略,编译器会自动推导)
Lambda捕获列表
符号 说明
[] Lambda无法访问外部函数体的任何局部变量
[a] 值传递 方式访问外部变量a(函数内是副本)
[&b] 引用传递 方式访问外部变量b(函数内是原变量)
[=] 外部所有局部变量都以值传递方式使用(Qt中最常用的安全形式)
[&] 外部所有局部变量都以引用传递方式使用(需注意变量生命周期)
[=, &foo] foo用引用传递,其余变量用值传递
[&, foo] foo用值传递,其余变量用引用传递
[this] 可访问类的成员函数/变量(=/&形式会默认引入this

演示如下:

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    mybutton = new QPushButton("Helloworld",this);
    mybutton->move(200,200);
    //connect(mybutton,&QPushButton::clicked,this,&Widget::handlerclick);
    //connect(this,sigptr,this,handptr);
    connect(this,&Widget::mysignal,this,[=](){
        qDebug()<<"按钮即将要关闭啦!";
        mybutton->close();
    });
}

void Widget::on_pushButton_clicked()
{
    //发出自定义的信号,emit代表发送信号,emit可以省略
    emit mysignal("触发信号",0);
}

5.4 信号与槽的优缺点

  • 优点: 松散耦合

    信号发送者不需要知道发出的信号被哪个对象的槽函数接收,槽函数也不需要知道哪些信号关联了自己,Qt的信号槽机制保证了信号与槽函数的调用。支持信号槽机制的类或者父类必须继承于QObject类。

  • 缺点: 效率较低

    与回调函数相比,信号和槽稍微慢一些,因为它们提供了更高的灵活性,尽管在实际应用程序中差别不大。

相关推荐
偶像你挑的噻1 小时前
1.Qt-编译器基本知识介绍
开发语言·qt
nono牛1 小时前
30天Shell脚本编程实战(14天逆袭)
前端·chrome·bash
透明的玻璃杯1 小时前
VS2015 +QT5.9.9 环境问题注意事项
开发语言·qt
晚霞的不甘1 小时前
实战深研:构建高可靠、低延迟的 Flutter + OpenHarmony 智慧教育互动平台(支持离线教学、多端协同与国产化适配)
前端·javascript·flutter
董世昌411 小时前
前端跨域问题:原理、8 种解决方案与实战避坑指南
开发语言·前端·javascript
十五年专注C++开发1 小时前
sigslot: 一个轻量级实现观察者模式的C++开源库
c++·观察者模式·开源
千千道1 小时前
QT上位机作为FTP客户端上传多文件
c++·qt
屿筱1 小时前
vscode 关于C/C++的环境配置
c++·ide·vscode
luoyayun3611 小时前
Qt/QML 实现类似Xmind简易思维导图绘制
qt·xmind·思维导图