【Qt】——理解信号与槽,学会使用connect

🎭 ​​前言:信号与槽------QT的"魔法对话"机制​

你是否遇到过这样的开发困境?

  • ​按钮点击后如何触发业务逻辑?​(用回调函数?写事件监听?)
  • ​两个窗口组件如何通信?​(全局变量?消息队列?)
  • ​如何避免多线程下的资源竞争?​(锁地狱?)

在传统C++开发中,这些问题往往需要复杂的​​回调函数​ ​或​​观察者模式​ ​来解决,代码容易变成"意大利面条"🍝。而QT提供了一种更优雅的解决方案------​​信号与槽(Signals & Slots)​​,它让对象之间的通信变得像"魔法对话"一样简单直观!

🌟 ​**​Qt信号与槽(Signals & Slots)**​

​🔍 什么是信号与槽?​

​信号与槽(Signals & Slots)​ ​ 是Qt框架独创的一种​​对象间通信机制​ ​,用于替代传统的回调函数和事件监听模式。它允许对象在特定事件发生时​​自动触发​​其他对象的操作,而无需直接耦合。

​📌 核心概念​

术语 说明 示例
​信号(Signal)​ 由对象发出的​​事件通知​​(如按钮点击、数据更新) clicked(), textChanged()
​槽(Slot)​ 响应信号的​​处理函数​​(可以是普通成员函数或Lambda) onButtonClick(), updateUI()
​连接(Connect)​ 建立信号与槽的绑定关系 connect(sender, signal, receiver, slot)

​⚡ 为什么信号与槽比传统方式更好?​

✅ ​​类型安全​ ​:编译时检查参数类型,避免运行时错误

✅ ​​松耦合​ ​:发送者无需知道接收者是谁(降低代码依赖)

✅ ​​支持多对多通信​ ​:一个信号可触发多个槽,一个槽可接收多个信号

✅ ​​线程安全​​:自动处理跨线程通信(无需手动加锁)

而如何使用信号与槽机制,则需要connect来叩开这道大门!

🔌 ​​Qt中的connect函数​

connect()是Qt信号与槽机制的核心函数,是Qobject的一个静态成员函数,用于建立对象间的通信桥梁。

​1️⃣ 函数原型​

cpp 复制代码
bool QObject::connect(
    const QObject *sender,            // 信号发送者
    const QMetaMethod &signal,        // 信号(元方法)
    const QObject *receiver,          // 槽函数接收者
    const QMetaMethod &method,        // 槽函数(元方法)
    Qt::ConnectionType type = Qt::AutoConnection//使用的时候一般忽略这个参数
);

2️⃣ 参数详解​

​🔹 发送者与接收者​

  • 必须是QObject或其子类的实例
  • 接收者可以为nullptr(表示连接到任意接收者)

🔹 信号与槽标识​

  • ​新式语法​ (类型安全):

    复制代码
    connect(button, &QPushButton::clicked, label, &QLabel::hide);
  • ​旧式语法​ (Qt4风格,不推荐):

    复制代码
    connect(button, SIGNAL(clicked()), label, SLOT(hide()));

3️⃣ 返回值与错误处理​

  • 返回bool表示连接是否成功

  • 常见失败原因:

    复制代码
    if (!connect(button, &QPushButton::clicked, this, &MyClass::invalidSlot)) {
        qWarning() << "Connection failed!";
        // 可能原因:
        // 1. 信号/槽不存在
        // 2. 参数不匹配
        // 3. 对象已被销毁
    }

4️⃣ 连接管理​

  • ​断开连接​

    // 断开特定连接
    disconnect(button, &QPushButton::clicked, label, &QLabel::hide);

    // 断开对象所有连接
    button->disconnect(); // 断开所有发出/接收的信号

  • ​连接检查​

    // 检查是否存在特定连接
    bool isConnected = QObject::receivers(button->metaObject()->method(
    button->metaObject()->indexOfSignal("clicked()")
    )) > 0;

​5️⃣ 高级用法​

Lambda表达式连接

复制代码
connect(button, &QPushButton::clicked, [=]() {
    qDebug() << "Button clicked by" << sender()->objectName();
});

简单使用示例:点击一个按钮,即可关闭当前窗口

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QPushButton* button = new QPushButton(this);
    button->setText("关闭");
    button->move(200,200);
    connect(button,&QPushButton::clicked,this,&Widget::close);

}

connect就是让两个本来不相关的对象建立通信机制,并且规定发送者只能发送什么样的信号,接收者接收到这个信号后,只能怎么样!

connect就像给两个人(如A,B)分配一个任务,并且这个任务的流程是:A做了xx,B才能做xx

如何查看文档

我们在介绍connect函数的时候,小试牛刀写了一个点击按钮关闭窗口的事件

信号是点击,槽是关闭窗口

++而想要写出更加丰富的事件,就必须知道有哪些信号,又哪些槽++

我们可以++查阅文档++,来获得所需要的信息

每一个类几乎都有自己信号函数和槽函数,我们只需要查看对应的类,再去找信号和槽函数即可

例如:查看QPushButton类

往下翻,我们可以发现并没有所谓的信号函数,只有一个槽函数

那么,QBushButton就没有信号函数?

这显然是不可能的,我们已经使用过它的信号函数了

但当前类中没有,我们可以去它的父类中寻找,回到开头,查看QPushButton的父类

进入父类QAbstractButton,往下翻,就可以看到信号函数,里面就有我们熟知的clicked

自定义信号与槽

自定义槽

++所谓的slot函数,也不过是一个普通的成员函数++

虽然qt已经有了很多内置的槽函数,但这些槽函数并不能满足我们所有的业务需求

所以为了更能迎合用户的需求,我们就需要自定义槽函数,自己写出符合需求的槽函数

而定义一个槽函数,操作过程和自定义一个普通的成员函数,没啥区别!

第一种:代码自定义槽函数流程

简单定义一个槽函数:将窗口标题设置为"新的开始"

cpp 复制代码
//首先在头文件中声明
public slots:
    void myslot();
};
//.cpp文件中定义
void Widget::myslot(){
    this->setWindowTitle("新的一天");
}

连接自定义槽函数

cpp 复制代码
  QPushButton* button = new QPushButton(this);
    button->setText("按钮");
    button->move(50,50);
    connect(button,&QPushButton::clicked,this,&Widget::myslot);
}

实际效果演示

点击后

注意:

我们在.h文件中声明自定义槽函数,在其上添加了 public slots:

pubiic slots是qt自己扩展的关键字(不是C++标准中的语法)

qt中广泛使用了元编程技术,所谓元编程就是基于代码,生成代码

qmake构建qt项目的时候,就会调用专门的扫描器,扫描代码中特定的关键字

如public slots,基于关键字自动生成一大堆的相关代码

在qt5后,已经可以不用写public slots了,但我们建议还是添加上,好区分

第二种:ui界面自定义槽函数

在ui设计界面添加对应控件,然后进行右击,出现选择,点击转到槽

选择对应的信号,会自动生成对应的槽函数

点击后,会生成以下的槽函数,我们需要在其中补充逻辑

cpp 复制代码
void Widget::on_pushButton_clicked()
{
    this->setWindowTitle("新的一天")
}

实际效果演示

点击后

注意:

我们可以发现我们使用UI界面自定义槽函数时,并没有使用connect函数进行建立桥梁

"在QT中,除了通过connect来连接信号与槽外,还可以通过函数名字的方式来自动连接!"

槽函数的命名符合以下规则,就可以实现自动连接

  • on_发送信号者的objectName_信号的名字

例如:我们在ui界面创建的按钮object名字:pushButton

自动生成的槽函数名字就是: on_pushButton_clicked

关于两种方式的选择

如果我们通过图形化界面创建控件,还是推荐快速的方式来连接信号槽

如果我们通过代码的方式来创建控件,还是得手动connect

自定义信号

自定义信号比较少见,实际开发中几乎遇不到

信号就对应到用户的某个操作

而在GUI中,用户能够进行哪些操作,是可以穷举的

QT内置的信号,基本上已经覆盖到了上述所有可能的用户操作

因此,使用QT内置的信号,就足以应付大部分的开发场景

自定义信号,本身是很简单的,一行代码就可以搞定

例如:自定义一个信号

cpp 复制代码
//在头文件中声明即可
signals:
void signal();

所谓的QT的"信号,本质是也是一个"函数"

QT5以及更高版本中,槽函数和普通函数之间,几乎没有差别

但是,信号则是一类非常特殊的函数

  1. 程序员只需要写出函数声明,并且告诉QT,这是一个"信号"即可,这个函数的定义,是QT在编译过程中,自动生成的,程序员无法干预
  2. 作为信号函数,这个函数的返回值,必须void
  3. 有没有参数都可以,甚至可以支持重载

"信号在QT中是特殊的机制,QT生成的信号函数的实现,要配合QT框架做很多既定的操作"

signals 也是QT自己扩展出来的关键字,也是需要qmake时候,替换成一些代码的

如何触发自定义信号?

使用connect建立连接,并不代表信号发了出来

QT内置的信号,都不需要咱们手动通过代码触发

用户在GUI进行某些操作,就会自动触发对应信号(发射信号的代码已经内置到了QT框架中了)

而对于自定义信号

我们可以使用 ++emit++来进行触发

演示一下

自定义一个信号

cpp 复制代码
//在头文件中声明
signals:
    void mysignal();

建立对应的连接

cpp 复制代码
//在.cpp中建立连接
connect(this,&Widget::mysignal,this,&Widget::myslot);

使用emit进行触发

cpp 复制代码
//在.cpp中的构造函数中进行触发
emit mysignal();

结果

注意:

其实QT5,emit现在什么也没有做,真正的操作都包含在mysignal内部生成的函数定义了,即使不写emit,信号也能发送出去

即使如此,实际开发中,还是建议大家,把emit添加上

"加上emit,可以使代码的可读性增加"

信号与槽与参数

信号和槽,也可以带参数

当信号带有参数的时候,槽的参数必须和信号的参数一致

此时发射信号的时候,就可以给信号函数传递实参,与之对应的这个参数就会被传递到对应的槽函数当中

cpp 复制代码
//自定义信号
signals:
    void mySignal(const QString& str);
//自定义槽
public slots:
    void handleMySignal(const QString& str);

这里的参数必须要求一致

  • 一致主要是要求类型,类型必须一致
  • 个数可以不一致,但不一致的时候,要求信号的参数的个数必须要比槽的参数个数更多

QT中很多内置信号,也是带有参数的

例如:clicked信号

clicked(bool) 这个bool参数表示当前按钮是否处于"选中"状态

这个选中状态对于 QPushButtton 没啥意义,对于 QCheckBox复选框,就很有用了

信号函数的参数个数,超过槽函数的参数个数,是没有问题的

cpp 复制代码
signals:
    void mysignal(QString& str1,QString& str2);
public slots:
    void myslot(QString& str1);

但槽函数的参数个数,多于信号的参数个数,此时代码无法编译通过

cpp 复制代码
signals:
    void mysignal(QString& str1);
public slots:
    void myslot(QString& str1,QString& str2);

报错:

为什么,只允许信号的参数个数比槽函数多?

++一个槽函数,有可能绑定多个信号++

如果我们严格要求参数个数一致,就意味着信号绑定到槽的要求就变高了

换而言之,当下这样的规则,就运行信号和槽直接的绑定更灵活,更多的信号可以绑定到这个槽函数上

++个数不一致,槽函数就会按照参数顺序,拿到信号的前N个参数++

因此,至少需要确保,槽函数的每个参数都是有值的,要求信号给槽的参数,可以有富裕,但不能少

注意:

QT中如果要让某个类能够使用信号与槽(可以在类中定义信号和槽函数),则必须要在类最开始的地方,写下 ++Q_OBJECT++宏

就像前面提到的宏,这个宏能展开成很多额外的代码

"QT中的每个短短的宏,最终都会变成大量的代码!"

信号与槽机制

QT的信号与槽机制,设计的开始是有两个目的

  • 解耦合:把 触发用户操作的控件 和 处理对应用户的操作 解耦合
  • 实现"多对多"效果:一个信号,可以 connect 到多个槽函数;一个槽函数,也可以被多个信号 connect

而最本质的目的,就是为了能够让信号和槽直接按照"多对多"的方式进行关联

其他的GUI框架往往也不具备这样的特性......


🎯 ​​结语:掌握信号与槽,开启Qt开发新境界​

信号与槽机制作为Qt框架最精妙的设计之一,就像​​电子电路中的导线​​⚡,将应用程序的各个组件优雅地连接在一起。通过本文的学习,相信您已经掌握了:

  • 🔌 ​信号与槽的核心原理​:理解Qt的"魔法通信"机制
  • 🛠️ ​两种自定义槽方式​:代码声明与UI自动生成
  • 🧩 ​实际应用技巧​:从简单点击到复杂业务逻辑处理

🚀 ​​下一步学习建议​

  1. ​深入元对象系统​:了解信号槽背后的魔法(moc机制)
  2. ​探索高级用法​:如信号连接信号、Lambda表达式槽
  3. ​实战项目练习​:尝试用信号槽重构旧代码

💡 ​​开发者箴言​ ​:

"在Qt的世界里,好的架构就像精密的瑞士手表⌚ - 每个部件通过信号槽精准协作,而不是像意大利面条🍝般纠缠不清。"

相关推荐
小小小小宇14 分钟前
TypeScript 中 infer 关键字
前端
火热的茶独独26 分钟前
C语言==》字符串断行
c语言·c++
__不想说话__28 分钟前
面试官问我React状态管理,我召唤了武林群侠传…
前端·react.js·面试
Cutey91629 分钟前
前端SEO优化方案
前端·javascript
webxin66630 分钟前
带鱼屏页面该怎么适配?看我的
前端
axinawang31 分钟前
SpringBoot整合Java Web三大件
java·前端·spring boot
炯哈哈32 分钟前
【上位机——MFC】MFC入门
开发语言·c++·mfc·上位机
小old弟34 分钟前
🎨如何动态主题切换 —— css变量🖌️
前端
辰哥单片机设计35 分钟前
非接触式水位传感器详解(STM32)
数据库·mongodb
JiangJiang37 分钟前
🎯 Vue 人看 useReducer:比 useState 更强的状态管理利器!
前端·react.js·面试