【QT】十分钟全面理解 信号与槽的机制

2024-09-06 21:52

本文主要是介绍【QT】十分钟全面理解 信号与槽的机制,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

  • 从一个定时器开始
  • 全方位简介
      • 1. 基本的信号与槽连接
        • 语法
        • 例子
      • 2. 使用函数指针连接信号与槽(现代 C++ 风格)
        • 语法
        • 例子
      • 3. 使用 Lambda 表达式作为槽
        • 语法
        • 例子
      • 4. 自动连接(`QMetaObject::connectSlotsByName`)
        • 规则
        • 例子
      • 5. 信号与槽的多对多连接
        • 例子(一个信号连接多个槽)
        • 例子(多个信号连接一个槽)
      • 6. 断开信号与槽的连接
        • 语法
        • 例子
      • 7. 信号本身也可以是空的
      • 8. 信号可以连接信号
        • 例子
      • 总结
  • 进一步探讨
    • connect的第三个参数
      • 为什么有时第三个参数是 `this`?
        • 例子: `this` 作为接收对象
      • 总结
    • lambda表达式和捕获
      • 1. 传统的信号与槽连接(四个参数)
      • 2. 使用 lambda 表达式的连接(三个参数)
        • a. Lambda 自带槽的定义
        • b. Lambda 是局部可执行的函数
        • c. Qt 自动处理 lambda 的生命周期
      • 3. 例子对比
        • 传统的四个参数连接
        • Lambda 表达式的三个参数连接
      • 4. 如果需要访问对象时如何处理?
      • 总结
    • 对捕获的理解(捕获上下文)
      • 具体理解为:
      • 举例说明
        • 情况 1:不需要捕获
        • 情况 2:需要捕获 `this` 指针
        • 情况 3:需要捕获局部变量
      • 总结
  • 扩展部分 和 C# 横向对比
      • C# 中的 lambda 表达式上下文
      • 例子:C# 中的 lambda 表达式上下文
      • C# 和 C++/Qt 的比较
      • 进一步理解
        • 例子:C# lambda 捕获局部变量
      • 总结

从一个定时器开始

connect(&timer,&QTimer:timeout,[this](){
});
timer.start();

这段代码是在使用 Qt 的信号与槽机制,特别是 QTimer 类的功能。下面是逐行解释:

  1. connect(&timer, &QTimer::timeout, [this]() { });
    这行代码将 QTimer 对象的 timeout 信号与一个 lambda 表达式(匿名函数)槽连接起来。

    • &timer:指向 QTimer 对象的指针,它是发送 timeout 信号的对象。
    • &QTimer::timeoutQTimer 类的 timeout 信号,在定时器时间到达时发射。
    • [this] () { }:这是一个 lambda 表达式。[this] 表示这个 lambda 捕获当前对象(即类的实例)以便在 lambda 内部使用 this 指针。空的 { } 表示这是一个没有任何操作的槽函数。

    这个连接表示,当 timer 对象的 timeout 信号被触发时,lambda 表达式内的代码会被执行(这里的代码是空的,所以什么也不会发生)。

  2. timer.start();
    这行代码启动了定时器,也就是说,QTimer 开始计时。在经过一定的时间间隔(默认是 0 毫秒或使用 setInterval 设置)后,timeout 信号会被触发。

总的来说,这段代码的作用是创建了一个 QTimer,并设置了当定时器超时时,执行一个空的 lambda 表达式槽函数。

看到这里发现还是有点不够透彻,我们继续往下看~~~

全方位简介

Qt 中的信号与槽机制是其事件处理系统的核心,用于实现对象之间的通信。信号是对象发出的消息,而槽是响应这些消息的函数。Qt 的信号与槽机制非常灵活,允许你使用不同的方式进行连接和处理。以下是详细的讲解:

1. 基本的信号与槽连接

语法
connect(sender, SIGNAL(signalName(arguments)), receiver, SLOT(slotName(arguments)));
  • sender:信号发出的对象。
  • signalName(arguments):要连接的信号的名称,使用 SIGNAL 宏。
  • receiver:槽所在的对象。
  • slotName(arguments):槽的名称,使用 SLOT 宏。
例子
QPushButton *button = new QPushButton("Click me");
connect(button, SIGNAL(clicked()), this, SLOT(onButtonClicked()));
  • button 按钮被点击时,它会发出 clicked() 信号,onButtonClicked() 槽函数会被调用。

2. 使用函数指针连接信号与槽(现代 C++ 风格)

Qt 5 引入了更简洁的信号与槽连接方法,支持使用函数指针来连接。相比传统的 SIGNALSLOT 宏,使用函数指针的方式更安全,且可以检查参数类型。

语法
connect(sender, &SenderClass::signalName, receiver, &ReceiverClass::slotName);
  • SenderClass::signalName:发送信号的函数指针。
  • ReceiverClass::slotName:接收信号的函数指针。
例子
QPushButton *button = new QPushButton("Click me");
connect(button, &QPushButton::clicked, this, &MainWindow::onButtonClicked);
  • button 按钮被点击时,MainWindow 中的 onButtonClicked 槽会被调用。

3. 使用 Lambda 表达式作为槽

从 Qt 5.0 开始,可以使用 lambda 表达式作为槽,这使得编写简单的响应代码变得更加方便。

语法
connect(sender, &SenderClass::signalName, [=](){// Lambda 函数体
});
  • [=]:捕获上下文中的变量(值捕获)。
  • SenderClass::signalName:信号的函数指针。
  • Lambda 函数体内可以编写要执行的代码。
例子
QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, [=]() {qDebug() << "Timeout!";
});
timer->start(1000);
  • 这个例子每隔 1 秒会输出一次 "Timeout!"

4. 自动连接(QMetaObject::connectSlotsByName

Qt 还支持通过命名约定自动连接信号与槽,通常用于 UI 文件和 QObject 派生类。

规则
  • 信号的格式是:objectName_signalName
  • 槽函数的格式是:on_objectName_signalName
例子
void on_button_clicked();

如果在 UI 文件中有一个 QPushButton,其 objectNamebutton,那么 Qt 会自动将 buttonclicked() 信号连接到 on_button_clicked() 槽。

5. 信号与槽的多对多连接

Qt 的信号与槽机制支持:

  • 一个信号连接多个槽:一个信号可以触发多个槽函数。
  • 多个信号连接同一个槽:不同的信号可以触发同一个槽。
例子(一个信号连接多个槽)
connect(button, &QPushButton::clicked, this, &MainWindow::onButtonClicked);
connect(button, &QPushButton::clicked, this, &MainWindow::logButtonClicked);

点击 button 会同时调用 onButtonClicked()logButtonClicked()

例子(多个信号连接一个槽)
connect(button1, &QPushButton::clicked, this, &MainWindow::handleButtonClick);
connect(button2, &QPushButton::clicked, this, &MainWindow::handleButtonClick);

无论点击 button1 还是 button2,都会调用 handleButtonClick()

6. 断开信号与槽的连接

你可以随时断开信号与槽的连接。

语法
disconnect(sender, SIGNAL(signalName()), receiver, SLOT(slotName()));
例子
disconnect(button, &QPushButton::clicked, this, &MainWindow::onButtonClicked);
  • 这将断开 buttonclicked() 信号与 onButtonClicked() 槽的连接。

7. 信号本身也可以是空的

在 Qt 中,信号不需要有槽函数与之连接,它们可以是“空的”。当一个信号发出时,如果没有槽函数接收,也不会有任何错误。这提供了灵活性,使得信号与槽的使用更加松耦合。

8. 信号可以连接信号

在某些情况下,你可能希望一个信号发出时,自动触发另一个信号。在 Qt 中这是允许的。

例子
connect(button, &QPushButton::clicked, anotherButton, &QPushButton::click);

button 被点击时,它将发出 clicked() 信号,anotherButton 将接收到 click() 信号。

总结

Qt 的信号与槽机制非常灵活,支持多种连接方式:

  • 经典的 SIGNALSLOT 宏语法。
  • 使用函数指针的现代 C++ 风格。
  • 使用 lambda 表达式的简洁写法。
  • 自动连接的方便方法。

Qt 的信号与槽机制通过松耦合的方式实现了对象之间的通信,使得 Qt 应用程序具有高度的模块化和可维护性。

进一步探讨

我们发现一般情况下,connect 是四个参数,而使用lambda表达式时是需要三个参数,这是为什么呢?为什么lambda需要捕获呢?

connect的第三个参数

在 Qt 的 connect 函数中,第三个参数指的是“槽函数的接收对象” 或者说是 槽函数的主子,即信号触发时执行槽函数的对象。因此,第三个参数通常是槽所属的对象。例如,在以下代码中:

connect(button, &QPushButton::clicked, this, &MainWindow::onButtonClicked);
  • button 是发送信号的对象(QPushButton),即信号发出的源。
  • &QPushButton::clicked 是信号,表示按钮点击时会发出 clicked() 信号。
  • this 是接收信号的对象,也就是槽函数的所属对象。在这种情况下,this 表示当前对象(通常是 MainWindow),也就是槽函数 onButtonClicked 所在的对象。
  • &MainWindow::onButtonClicked 是槽函数的指针,表示当 clicked() 信号被触发时,onButtonClicked 函数会被调用。

为什么有时第三个参数是 this

当槽函数是类的成员函数时,你通常会使用 this 作为接收对象。因为槽函数 onButtonClicked 属于 MainWindow 类,你需要告诉 connect 函数在哪个对象上调用这个槽函数,因此使用 this,指代当前的 MainWindow 实例。

例子: this 作为接收对象
connect(button, &QPushButton::clicked, this, &MainWindow::onButtonClicked);

这里的 thisMainWindow 类的对象,表示当 button 被点击时,MainWindowonButtonClicked 函数会被调用。

总结

  • connect 的第三个参数用于指明接收信号并执行槽函数的对象。当槽函数属于当前类实例时,通常使用 this

lambda表达式和捕获

当使用 lambda 表达式作为槽时,connect 只需要三个参数的原因在于 lambda 本质上就是一个内联的可调用对象,它已经包含了槽函数的定义。因此,不再需要明确地指定槽函数的接收对象。下面详细解释原因。

1. 传统的信号与槽连接(四个参数)

在传统的 Qt 信号与槽机制中,connect 函数的四个参数分别是:

connect(sender, SIGNAL(signalName()), receiver, SLOT(slotName()));
  • sender:信号的发送者。
  • signalName:信号的名称,定义了发送者会触发哪个信号。
  • receiver:槽的接收者,指明哪个对象的槽函数会响应信号。
  • slotName:槽的名称,指明接收者的哪个函数会处理信号。

这种方式需要指定接收对象 receiver,因为 Qt 需要知道在哪个对象上调用槽函数。

2. 使用 lambda 表达式的连接(三个参数)

当使用 lambda 表达式时,connect 只需要三个参数:

connect(sender, &SenderClass::signalName, []() {// Lambda 作为槽
});

原因在于,lambda 表达式本质上是一个可调用对象,而且这个可调用对象已经包含了执行的代码逻辑,因此不需要再指定一个接收对象。具体原因如下:

a. Lambda 自带槽的定义

在传统方式中,槽函数是一个对象的成员函数,因此需要指定在哪个对象上调用槽函数(通过 receiver 参数)。但 lambda 表达式是匿名的,它定义了槽函数的逻辑,因此:

  • 不需要一个额外的接收对象。lambda 自身就是一个可调用的对象,它会在信号触发时直接执行 lambda 中定义的代码。
b. Lambda 是局部可执行的函数

Lambda 表达式是一种轻量的方式来处理简单的事件响应,它既可以捕获局部变量,也可以不捕获任何变量。

c. Qt 自动处理 lambda 的生命周期

当使用 lambda 表达式作为槽时,Qt 内部会管理 lambda 的生命周期。只要信号与槽保持连接,lambda 表达式就会保持有效,直到连接被断开或对象被销毁。这与成员函数不同,成员函数必须绑定到一个对象上(即 receiver),而 lambda 是匿名的,因此不需要 receiver 参数。

3. 例子对比

传统的四个参数连接
connect(button, &QPushButton::clicked, this, &MainWindow::onButtonClicked);
  • 这里需要指定 this,因为 onButtonClickedMainWindow 类的成员函数,必须指明在哪个对象上调用该函数。
Lambda 表达式的三个参数连接
connect(button, &QPushButton::clicked, []() {// Lambda 逻辑qDebug() << "Button clicked!";
});
  • 这里不需要指定 receiver,因为 lambda 是一个匿名的、内联的函数,它不属于任何特定的对象。

4. 如果需要访问对象时如何处理?

如果 lambda 需要访问某个对象的成员函数或成员变量,可以通过捕获该对象(通常是 this)来实现。例如:

connect(button, &QPushButton::clicked, [this]() {this->doSomething();
});
  • this 被捕获到 lambda 表达式中,因此可以在 lambda 内部调用当前对象的成员函数 doSomething()
  • 在这种情况下,仍然只需要三个参数,因为 this 已经被捕获进 lambda,而不是通过 connect 的第三个参数显式传递。

总结

使用 lambda 表达式时,connect 只需要三个参数是因为 lambda 本身就是一个可调用对象,已经包含了槽函数的逻辑,因此:

  • 不再需要指定接收对象(第三个参数)。
  • Lambda 的执行上下文可以通过捕获来定义,不需要通过 connectreceiver 参数显式传递。

这使得代码更加简洁灵活,特别适用于处理简单的事件响应逻辑。

对捕获的理解(捕获上下文)

当你使用 lambda 表达式 作为槽函数时,确实没有指定槽函数的接收对象。因此,lambda 表达式本身没有上下文,所以需要显式捕获你希望使用的上下文(如 this 指针或局部变量),以便在 lambda 表达式内访问相关的数据或函数。

具体理解为:

  1. 没有接收对象时:
    在传统的 connect 中,第三个参数(接收对象 receiver)是明确指定的上下文,它告诉 Qt 在哪个对象上调用槽函数。因此,槽函数可以直接访问该对象的成员变量和成员函数。例如:

    connect(button, &QPushButton::clicked, this, &MainWindow::onButtonClicked);
    

    在这里,onButtonClicked() 是属于 MainWindow 类的成员函数,this 表示槽函数的接收对象,函数执行时有明确的上下文,即 MainWindow 对象的成员可以被访问。

  2. 使用 lambda 表达式时:
    Lambda 表达式没有天然的上下文,因为它是一个匿名的内联函数,不属于某个对象,因此:

    • 如果需要在 lambda 内部访问外部对象或变量,必须通过捕获机制手动传递这些上下文
    • 捕获的变量(如 this 指针或局部变量)会成为 lambda 的执行上下文,使得 lambda 能够访问这些变量。

举例说明

情况 1:不需要捕获
connect(button, &QPushButton::clicked, []() {qDebug() << "Button clicked!";
});
  • 在这个例子中,lambda 不需要上下文,因为它没有访问任何外部对象或变量,只是简单地打印了一条消息,因此没有必要捕获任何上下文。
情况 2:需要捕获 this 指针
connect(button, &QPushButton::clicked, [this]() {this->doSomething();
});
  • 在这里,lambda 表达式内部需要调用当前对象(this)的成员函数 doSomething()。由于 lambda 没有天然的上下文,因此需要通过 [this] 捕获当前对象的指针,以便能够在 lambda 内访问 this->doSomething()
情况 3:需要捕获局部变量
int counter = 0;
connect(button, &QPushButton::clicked, [=]() mutable {counter++;qDebug() << "Counter: " << counter;
});
  • 在这个例子中,lambda 需要访问局部变量 counter。由于 lambda 默认没有访问外部局部变量的能力,所以通过 [=] 捕获所有外部局部变量(按值捕获),这样 lambda 内部就可以访问 counter 变量,并对其进行修改(需要 mutable 关键字)。

总结

  • 没有上下文:当使用 lambda 表达式作为槽时,没有接收对象,所以默认没有上下文。
  • 通过捕获添加上下文:如果 lambda 需要访问外部对象(如 this)或局部变量,则必须通过捕获机制显式提供上下文。

你需要捕获什么,取决于 lambda 内部需要访问的内容。如果 lambda 不访问任何外部变量或对象,就不需要捕获任何上下文。

扩展部分 和 C# 横向对比

在 C# 中,使用 lambda 表达式时,默认上下文是当前类的实例,即 this 指针。也就是说,在 C# 中,lambda 表达式可以直接访问类的成员变量和成员方法,而不需要显式捕获 this

C# 中的 lambda 表达式上下文

在 C# 中,当你在类中定义一个 lambda 表达式时,lambda 表达式会自动捕获当前的上下文,包括类的实例(即 this),因此你可以直接访问该类的成员变量或成员方法。

例子:C# 中的 lambda 表达式上下文

class MyClass
{private int counter = 0;public void RegisterEvent(Button button){// 在 C# 中,lambda 表达式可以直接访问类的成员变量或方法button.Click += (sender, e) =>{counter++; // 直接访问类的成员变量DoSomething(); // 直接调用类的成员方法};}private void DoSomething(){Console.WriteLine("Counter: " + counter);}
}

在上面的代码中,lambda 表达式直接访问了 counter 成员变量和 DoSomething 方法。无需像在 C++ 或 Qt 中那样显式捕获 this,因为 C# 自动捕获了当前类的上下文

C# 和 C++/Qt 的比较

  • C#:在 lambda 表达式中,类的上下文(即 this自动捕获,不需要显式指定。因此,你可以直接访问当前类的成员变量和方法,代码更加简洁。
  • C++/Qt:lambda 表达式不会自动捕获上下文,如果需要访问 this 或外部变量,必须显式捕获,如 [this][&]

进一步理解

C# 的 lambda 表达式不仅自动捕获 this,还可以自动捕获局部变量。在 C# 中,lambda 表达式会捕获其定义所在方法中的局部变量,并在事件触发时保持这些变量的状态(闭包)。

例子:C# lambda 捕获局部变量
public void RegisterEvent(Button button)
{int localCounter = 0;button.Click += (sender, e) =>{localCounter++; // 捕获局部变量Console.WriteLine("Local Counter: " + localCounter);};
}

在这个例子中,localCounter 是一个局部变量,lambda 表达式在事件中捕获了它,并在每次点击按钮时对其进行修改。

总结

在 C# 中,lambda 表达式的上下文默认就是 this,你不需要像在 C++ 或 Qt 中那样显式捕获当前对象。这使得在 C# 中使用 lambda 表达式更加简洁直观。如果你需要访问局部变量或类的成员,C# 会自动处理捕获工作。

这篇关于【QT】十分钟全面理解 信号与槽的机制的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1143222

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

【编程底层思考】垃圾收集机制,GC算法,垃圾收集器类型概述

Java的垃圾收集(Garbage Collection,GC)机制是Java语言的一大特色,它负责自动管理内存的回收,释放不再使用的对象所占用的内存。以下是对Java垃圾收集机制的详细介绍: 一、垃圾收集机制概述: 对象存活判断:垃圾收集器定期检查堆内存中的对象,判断哪些对象是“垃圾”,即不再被任何引用链直接或间接引用的对象。内存回收:将判断为垃圾的对象占用的内存进行回收,以便重新使用。

【Tools】大模型中的自注意力机制

摇来摇去摇碎点点的金黄 伸手牵来一片梦的霞光 南方的小巷推开多情的门窗 年轻和我们歌唱 摇来摇去摇着温柔的阳光 轻轻托起一件梦的衣裳 古老的都市每天都改变模样                      🎵 方芳《摇太阳》 自注意力机制(Self-Attention)是一种在Transformer等大模型中经常使用的注意力机制。该机制通过对输入序列中的每个元素计算与其他元素之间的相似性,

深入理解RxJava:响应式编程的现代方式

在当今的软件开发世界中,异步编程和事件驱动的架构变得越来越重要。RxJava,作为响应式编程(Reactive Programming)的一个流行库,为Java和Android开发者提供了一种强大的方式来处理异步任务和事件流。本文将深入探讨RxJava的核心概念、优势以及如何在实际项目中应用它。 文章目录 💯 什么是RxJava?💯 响应式编程的优势💯 RxJava的核心概念

如何通俗理解注意力机制?

1、注意力机制(Attention Mechanism)是机器学习和深度学习中一种模拟人类注意力的方法,用于提高模型在处理大量信息时的效率和效果。通俗地理解,它就像是在一堆信息中找到最重要的部分,把注意力集中在这些关键点上,从而更好地完成任务。以下是几个简单的比喻来帮助理解注意力机制: 2、寻找重点:想象一下,你在阅读一篇文章的时候,有些段落特别重要,你会特别注意这些段落,反复阅读,而对其他部分