【QT】信号和槽

在这里插入图片描述

欢迎来到Cefler的博客😁
🕌博客主页:折纸花满衣

在这里插入图片描述


👉🏻信号和槽

信号和槽(Signals and Slots)是Qt框架中一种非常直观且强大的对象间通信机制,它用非常通俗易懂的方式可以这样理解:

🌈 信号(Signals)

想象一下你有一个按钮(比如一个电灯开关),当你按下这个按钮时,它会“发出”一个信号,这个信号就是“按钮被按下了”。在Qt中,这个信号不会直接做任何事情,它只是告诉其他对象:“嘿,我这里的按钮被按下了,谁有兴趣知道或者想对此做出反应?”

🌈 槽(Slots)

槽就像是那些能够接收信号并做出响应的函数。比如,你有一个灯泡对象,它有一个“开灯”的函数。你可以把这个“开灯”函数设置为一个槽,然后告诉Qt:“当按钮发出‘按钮被按下了’的信号时,请调用灯泡对象的‘开灯’函数。”

🌈 连接(Connections)

连接就是建立信号和槽之间关系的过程。它就像是在说:“把按钮的‘按钮被按下了’信号连接到灯泡的‘开灯’槽上。” 这样,每当按钮被按下时,灯泡就会自动亮起。

🌈总结

信号和槽机制让对象之间的通信变得非常简单和直观。你不需要关心是谁发送了信号,也不需要知道是哪个槽接收了信号,你只需要定义好信号和槽,然后告诉Qt它们之间的关系。这样,当特定的事件发生时(比如按钮被按下),Qt就会自动找到并调用相应的槽函数,从而实现对象间的通信和交互。这种机制大大提高了代码的模块化和可维护性。

👉🏻connect函数的用法

connect 函数是 Qt 框架中用于连接信号(Signals)和槽(Slots)的核心函数。它的作用是在信号被发射时,自动调用与之连接的槽函数。connect 函数的用法相对直观,但也有一些细节需要注意。

🌍 基本用法

connect 函数的基本语法如下:

bool QObject::connect(const QObject *sender, const char *signal,
                      const QObject *receiver, const char *member,
                      Qt::ConnectionType type = Qt::AutoConnection)
  • sender:发出信号的对象指针。
  • signal:指向以 SIGNAL() 宏(在 Qt 5 中通常使用函数指针)指定的信号的字符串。但在 Qt 5 及更高版本中,推荐使用新的信号和槽语法,即直接使用成员函数指针,而不是字符串。
  • receiver:接收信号的对象指针,即槽函数所属的对象。
  • member:指向以 SLOT() 宏(同样,在 Qt 5 及更高版本中推荐使用成员函数指针)指定的槽函数的字符串,或者是成员函数指针。
  • type:连接类型,决定了信号和槽之间的同步或异步关系,默认为 Qt::AutoConnection

🌍Qt 5 及更高版本的用法

从 Qt 5 开始,推荐使用基于函数指针的新信号和槽语法,这使得代码更加类型安全且易于阅读。

connect(sender, &SenderClass::signalName, receiver, &ReceiverClass::slotName);

这里,senderreceiver 是指向对象的指针,而 &SenderClass::signalName&ReceiverClass::slotName 分别是信号和槽的成员函数指针

🌍 返回值

connect 函数返回一个布尔值,表示连接是否成功。如果连接失败(比如信号或槽不存在,或者参数不匹配),则返回 false

🌍 注意事项

  1. 信号和槽的参数必须匹配:信号和槽的参数在数量和类型上必须完全一致,或者槽的参数可以比信号的参数少,但对应的参数类型必须兼容。

  2. QObject 的派生类:使用 connect 函数的对象必须是 QObject 的派生类,因为信号和槽机制依赖于 Qt 的元对象系统。

  3. 多线程:当信号和槽位于不同的线程时,需要特别注意线程安全问题。Qt 提供了多种连接类型(如 Qt::QueuedConnection)来处理跨线程的信号和槽调用。

  4. 自动连接和手动断开:当对象被销毁时,Qt 会自动断开与该对象相关的所有连接。但是,在某些情况下,你可能需要手动断开连接以避免不必要的槽函数调用或内存泄漏。

  5. 使用 Lambda 表达式:除了将槽函数作为成员函数外,你还可以将 Lambda 表达式作为槽。这提供了更大的灵活性,但需要注意 Lambda 表达式的生命周期和捕获列表。

🌍 示例

// 假设有两个类,Button 和 Counter
Button *button = new Button;
Counter *counter = new Counter;

// 使用新语法连接信号和槽
connect(button, &Button::clicked, counter, &Counter::increment);

// 当 button 的 clicked 信号被发出时,counter 的 increment 槽会被调用

点击button关闭widget:

#include "widget.h"
#include "ui_widget.h"
#include<QPushButton>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QPushButton* btn = new QPushButton("关闭",this);
    connect(btn,&QPushButton::clicked,this,&Widget::close);
}

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

在这里插入图片描述

通过 Qt Creator ⽣成信号槽代码

在这里插入图片描述
2.
在这里插入图片描述
3.而后widget.h文件中自动会生成关于on_pushButton_clicked()的槽函数
在这里插入图片描述
4.接下来正常connect信号和槽即可

#include "widget.h"
#include "ui_widget.h"
#include<QPushButton>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QPushButton* btn = new QPushButton("关闭",this);
    connect(btn,&QPushButton::clicked,this,&Widget::close);

    //控件pushButton连接
    //connect(ui->pushButton,&QPushButton::clicked,this,&Widget::on_pushButton_clicked);//无需添加
}

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


void Widget::on_pushButton_clicked()
{
    this->setWindowTitle("按钮已按下");
}


在这里插入图片描述
在这里插入图片描述

说明:
⾃动⽣成槽函数的名称有⼀定的规则。槽函数的命名规则为:on_XXX_SSS,其中:
1、以 " on " 开头,中间使⽤下划线连接起来;
2、" XXX " 表⽰的是对象名(控件的 objectName 属性)。
3、" SSS " 表⽰的是对应的信号。
如:" on_pushButton_clicked() " ,pushButton 代表的是对象名,clicked 是对应的信号。

按照这种命名⻛格定义的槽函数, 就会被 Qt ⾃动的和对应的信号进⾏连接.

👉🏻自定义槽和函数

在Qt框架中,自定义槽(Slots)和信号(Signals)是对象间通信的一种强大方式。它们允许开发者定义自己的成员函数作为信号或槽,以在对象之间传递消息和响应事件。下面分别介绍自定义槽和信号的概念、用法及注意事项。

自定义槽(Slots)

自定义槽是普通的成员函数,但它们被特别标记为槽,以便能够被信号连接。槽函数可以有参数,也可以被重载,但它们必须满足以下要求:

  1. 声明与实现:槽函数需要在类的声明中声明,并在类的实现文件中实现。与普通的成员函数一样,槽函数可以有访问修饰符(如public、protected、private),但在Qt 5及更高版本中,槽函数可以声明为public,因为信号和槽的连接不再受访问权限的限制。

  2. 返回值:槽函数的返回值必须是void。

  3. 参数槽函数可以有参数,这些参数的类型和数量必须与连接的信号相匹配,或者槽函数的参数可以比信号的参数少,但对应的参数类型必须兼容。

  4. 连接:槽函数可以被连接到任何信号上,当信号被发射时,连接的槽函数会被自动调用。

自定义信号(Signals)

自定义信号是特殊的成员函数,它们用于在对象之间传递消息。但与槽函数不同,信号有以下特点:

  1. 声明不实现:信号只需要在类的声明中声明,不需要在类的实现文件中实现。信号的声明使用signals关键字,并且信号函数没有函数体。

  2. 返回值:信号的返回值类型必须是void。

  3. 参数:信号可以有参数,这些参数的类型和数量是灵活的,可以根据需要定义。但需要注意的是,由于信号没有实现,因此信号的参数不能被修改或访问。

  4. 发射:信号通过emit关键字来发射。当某个事件发生时,对象可以发射一个信号,通知其他对象该事件已经发生。

  5. 连接:信号可以被连接到任何槽函数上,包括自定义槽函数、系统提供的槽函数、全局函数或Lambda表达式。当信号被发射时,所有连接到该信号的槽函数都会被调用。

💧注意事项

  1. QObject派生类:使用信号和槽的类必须是QObject的派生类,因为信号和槽机制依赖于Qt的元对象系统。

  2. 元对象编译器(moc):当类中包含自定义信号和槽时,需要使用Qt的元对象编译器(moc)对类进行处理,以生成必要的元对象信息。这通常是在编译过程中自动完成的。

  3. 连接类型:Qt提供了多种连接类型(如Qt::AutoConnectionQt::DirectConnectionQt::QueuedConnection等),用于控制信号和槽之间的同步或异步关系。

  4. 多线程:当信号和槽位于不同的线程时,需要特别注意线程安全问题。Qt提供了跨线程的信号和槽连接机制,但开发者需要确保连接的槽函数是线程安全的。

  5. 断开连接:在某些情况下,可能需要手动断开信号和槽之间的连接,以避免不必要的槽函数调用或内存泄漏。

  6. Lambda表达式:在Qt 5及更高版本中,可以使用Lambda表达式作为槽函数,这提供了更大的灵活性和便利性。但需要注意Lambda表达式的生命周期和捕获列表。

无参数示例

1.声明槽和信号
在这里插入图片描述
2.connect关联槽和信号再发送信号
在这里插入图片描述

qt5之后,emit可省略不写

带参数的信号和槽示例

在这里插入图片描述
2.
在这里插入图片描述
3.效果
在这里插入图片描述

👉🏻disconnect

Qt中的disconnect函数是用于断开信号(Signals)与槽(Slots)之间连接的机制。在Qt的事件驱动架构中,信号和槽提供了一种强大的对象间通信方式,但有时候我们需要取消这种连接,以避免不必要的槽函数调用、内存泄漏或响应冲突。以下是关于Qt中disconnect函数的详细介绍:

🔥基本用法

Qt的disconnect函数通常有以下几种用法,这些用法基于QObject类的静态成员函数:

  1. 完全指定断开
    如果你知道连接的具体细节,可以指定发送者(sender)、信号(signal)、接收者(receiver)和槽函数(slot)来断开连接。

    bool QObject::disconnect(const QObject *sender, const char *signal,
                             const QObject *receiver, const char *method);
    // 或者在Qt 5及更高版本中,使用函数指针
    bool QObject::disconnect(const QObject *sender, PointerToMemberFunction signal,
                             const QObject *receiver, PointerToMemberFunction method);
    
  2. 部分指定断开
    如果你只想断开特定发送者的所有信号与某个接收者的连接,或者断开某个接收者的所有槽函数对特定信号的监听,可以使用nullptr0作为占位符。

    • 断开sender的所有信号与receiver的所有槽的连接:
      disconnect(sender, nullptr, receiver, nullptr);
      
    • 断开所有发送者发出的signal信号与receiver的连接:
      disconnect(nullptr, signal, receiver, nullptr);
      
    • 断开sender的signal信号与receiver的所有槽的连接:
      disconnect(sender, signal, nullptr, nullptr);
      
  3. 断开所有连接
    如果你想断开一个对象的所有信号与槽的连接,可以使用以下方式:

    • 在Qt 5及更高版本中,提供了更简洁的语法:
      myObject->disconnect();
      
    • 或者使用静态函数,传入对象指针和必要的占位符:
      disconnect(myObject, nullptr, nullptr, nullptr);
      

🔥注意事项

  • 当使用disconnect函数时,如果成功断开连接,它会返回true;如果没有找到匹配的连接,则返回false,但disconnect不会抛出异常。
  • 在多线程环境下使用disconnect时,需要确保操作是线程安全的,尤其是在连接和断开连接操作跨越线程边界时。
  • 从Qt 5开始,disconnect函数的语法变得更加简洁,不再需要指定信号和槽的具体签名,直接使用对象实例即可断开所有关联。
  • 如果涉及的任何对象被销毁,Qt将自动删除相关的信号槽连接,但显式调用disconnect可以避免在对象销毁前的不必要调用或内存泄漏。

🔥 结论

Qt的disconnect函数是管理信号与槽连接的重要工具,它提供了灵活的方式来断开不再需要的连接。通过合理使用disconnect,可以优化应用程序的性能和稳定性。

👉🏻lamda表达式做槽函数

在Qt中,lambda表达式经常被用作槽函数(slot functions)的便捷替代,尤其是当槽函数的内容相对简单,或者你不希望为了一个小的操作而定义一个单独的槽函数时。Lambda表达式提供了一种简洁的方式来直接在信号连接的地方定义槽的行为。

在Qt 5及更高版本中,信号和槽的连接支持使用std::functionQFunctionPointer或lambda表达式作为槽函数的参数。然而,最常用和推荐的方式是使用函数指针(对于Qt的内置信号和槽)或QObject::connect的重载版本,该版本接受指向成员函数或lambda表达式的指针。

当使用lambda表达式作为槽函数时,你需要注意lambda表达式捕获列表(capture list)的使用,以确保在lambda表达式执行时,所需的数据是可访问的。此外,由于Qt的信号和槽机制通常跨线程工作,你需要确保lambda表达式中的操作是线程安全的。

下面是一个使用lambda表达式作为槽函数的例子:

#include <QPushButton>
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QPushButton button("Click me");

    // 使用lambda表达式作为槽函数
    QObject::connect(&button, &QPushButton::clicked, [&](){
        // 这里是槽函数的实现
        qDebug() << "Button clicked!";
    });

    button.show();

    return app.exec();
}

在这个例子中,当按钮被点击时,会打印一条消息到控制台。Lambda表达式使用了捕获列表[&],这意味着它可以捕获其所在作用域中所有变量的引用。在这个例子中,由于lambda表达式没有使用任何外部变量,捕获列表实际上是多余的,但为了说明目的而包含在内。

需要注意的是,虽然lambda表达式提供了很大的灵活性,但在某些情况下(如需要跨线程的信号槽连接,或者在lambda表达式中使用了复杂的资源管理逻辑时),定义一个单独的槽函数可能会是更清晰、更安全的选择。此外,如果lambda表达式变得过于复杂,它可能会降低代码的可读性和可维护性。

关于lamda表达式的知识点可参考:C++11【下】

👉🏻信号和槽的优缺点

信号和槽(Signals and Slots)是Qt框架中用于对象间通信的一种机制,它允许一个对象在其内部状态发生改变时通知其他对象。这种机制提供了一种松耦合的方式来处理对象间的交互,是Qt事件系统的一部分。以下是信号和槽机制的主要优缺点总结:

优点

  1. 类型安全:信号和槽机制在连接时是类型安全的,这意味着编译器会检查信号和槽的参数是否匹配,减少了运行时错误的可能性。

  2. 解耦:信号和槽的使用使得对象之间的通信更加灵活,减少了对象之间的直接依赖,提高了代码的模块化和可重用性。

  3. 灵活性:一个信号可以连接到多个槽,同样一个槽也可以被多个信号连接。此外,连接可以是跨线程的,使得在多线程程序中进行通信变得简单。

  4. 易于使用:Qt的信号和槽机制提供了一种直观且易于理解的接口,使得对象间的通信变得简单明了。

  5. 自动连接:Qt的信号和槽机制支持自动连接,当对象在运行时被创建或销毁时,相关的信号和槽连接会自动管理,减少了内存泄漏的风险。

  6. 支持lambda表达式:从Qt 5开始,信号和槽的连接支持使用C++11的lambda表达式,这为编写简洁、灵活的槽函数提供了便利。

缺点

  1. 性能开销:与直接的函数调用相比,信号和槽机制涉及到更多的间接调用和可能的线程间通信,这可能会引入一定的性能开销。然而,在大多数应用场景中,这种开销是可以接受的。

  2. 调试复杂性:由于信号和槽的解耦特性,当程序中出现问题时,可能需要跟踪多个对象之间的交互才能找到问题的根源,这增加了调试的复杂性。

  3. 学习曲线:对于初学者来说,信号和槽机制可能需要一些时间来理解和适应。尽管它提供了许多优点,但学习如何使用它可能是一个挑战。

  4. 限制:虽然信号和槽机制非常灵活,但它也有一些限制。例如,槽函数必须是成员函数、静态函数或全局函数(通过特定的函数指针或std::function),这限制了槽函数的灵活性。此外,由于Qt的信号和槽机制是基于Qt的元对象系统实现的,因此它只能用于继承自QObject的类。

  5. 宏定义:Qt的信号和槽机制使用了大量的宏定义来实现,这可能会使代码看起来有些混乱,尤其是对于不熟悉Qt的人来说。然而,这是Qt设计的一部分,旨在提供类型安全和灵活性。

总的来说,信号和槽机制是Qt框架中一个非常强大且灵活的特性,它允许开发者以解耦和类型安全的方式处理对象间的通信。尽管它有一些缺点,但在大多数情况下,这些缺点是可以接受的,并且被其提供的优点所弥补。


如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注🌹🌹🌹❤️ 🧡 💛,学海无涯苦作舟,愿与君一起共勉成长

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值