Qt中的信号与槽机制

信号和槽 (Signals and Slots)

Qt 框架中的信号和槽机制是其核心特性之一,用于对象间通信,特别适用于 GUI 事件处理。它允许对象发送和接收消息,从而实现松散耦合的设计。

信号和槽的基本概念

  • 信号 (Signal):信号是对象状态发生变化时发出的通知。信号不需要实现,只需在类定义中声明即可。
  • 槽 (Slot):槽是一个函数,可以在信号发出时被调用。槽是普通的成员函数,只不过可以与信号关联。

信号和槽的连接

使用 QObject::connect 函数来连接信号和槽。信号和槽可以是一个类的成员,也可以跨类连接。

信号和槽的特点

  1. 类型安全:连接信号和槽时,Qt 会在编译时检查参数类型是否匹配。
  2. 松散耦合:对象之间可以通过信号和槽进行通信,而不需要彼此知道具体的实现,从而实现松散耦合。
  3. 多对多连接:一个信号可以连接到多个槽,一个槽也可以连接到多个信号。信号还可以连接到其他信号,从而形成信号链。

高级特性

1. Lambda 表达式

从 Qt 5 开始,信号和槽可以使用 C++11 的 lambda 表达式:

QObject::connect(&obj, &MyObject::mySignal, [](int value) {
    qDebug() << "Lambda called with value:" << value;
});
2. 断开连接

可以使用 QObject::disconnect 函数断开信号和槽的连接:

QObject::disconnect(&obj, &MyObject::mySignal, &obj, &MyObject::mySlot);

connect 函数

在 Qt 框架中,QObject::connect 函数用于将信号和槽连接起来,使得当一个对象的信号被发射时,与之连接的槽会被调用。connect 函数有多个重载版本,以适应不同的使用场景。以下是最常见的形式及其参数详解:

常见的 connect 函数形式

  1. 四参数版本
bool QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method);
  1. 五参数版本
bool QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection);
  1. 新语法(使用函数指针或 lambda 表达式)
template<typename Func1, typename Func2>
static QMetaObject::Connection QObject::connect(const QObject *sender, Func1 signal, const QObject *receiver, Func2 method);

参数详解

四参数版本
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method);
  • sender:发射信号的对象。
  • signal:信号的字符指针,格式为 "signalName(type1, type2, ...)"
  • receiver:接收信号的对象。
  • method:槽的字符指针,格式为 "slotName(type1, type2, ...)"
五参数版本
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection);
  • sender:发射信号的对象。
  • signal:信号的字符指针,格式为 "signalName(type1, type2, ...)"
  • receiver:接收信号的对象。
  • method:槽的字符指针,格式为 "slotName(type1, type2, ...)"
  • type:连接类型,默认是 Qt::AutoConnection,可以是以下类型之一:
    • Qt::AutoConnection:根据线程自动选择合适的连接方式。
    • Qt::DirectConnection:直接调用槽函数(在发射信号的线程中)。
    • Qt::QueuedConnection:将槽函数调用放入接收者的事件队列中(在接收信号的对象所属的线程中)。
    • Qt::BlockingQueuedConnection:类似 Qt::QueuedConnection,但会阻塞直到槽函数完成(仅用于多线程)。
    • Qt::UniqueConnection:确保信号和槽只连接一次,如果已经连接则不再连接。
新语法
template<typename Func1, typename Func2>
static QMetaObject::Connection connect(const QObject *sender, Func1 signal, const QObject *receiver, Func2 method);
  • sender:发射信号的对象。
  • signal:信号的成员函数指针,例如 &Sender::signalName
  • receiver:接收信号的对象。
  • method:槽的成员函数指针,例如 &Receiver::slotName,或 lambda 表达式。

槽(Slots)

在 Qt 框架中,槽函数是一个特殊的成员函数,它可以与信号关联,当信号被发出时,槽函数会被执行。槽函数本质上是普通的成员函数,但通过 Qt 的元对象系统 (Meta-Object System) 可以与信号机制结合使用。

槽函数的定义

槽函数的定义方式与普通成员函数基本相同,不同之处在于它们需要在类声明中使用 slots 关键字标识。以下是一个简单的例子:

class MyObject : public QObject {
    Q_OBJECT

public slots:  // 声明槽函数区域
    void mySlot(int value) {
        qDebug() << "Slot called with value:" << value;
    }
};

槽函数的特点

  1. 成员函数:槽函数是类的成员函数,可以访问类的成员变量和其他成员函数。
  2. 参数和返回值:槽函数可以有参数和返回值,但通常不会有返回值,因为信号和槽机制主要用于事件驱动编程。
  3. 公开或私有:槽函数可以是 publicprotectedprivate 的,这取决于你希望如何控制对槽的访问。

槽函数的调用

槽函数可以被任何对象直接调用,就像调用普通成员函数一样,但更常见的是通过信号触发调用。这种机制允许对象之间进行松散耦合的通信。

直接调用
MyObject obj;
obj.mySlot(42);  // 直接调用槽函数
通过信号调用
class Sender : public QObject {
    Q_OBJECT
signals:
    void mySignal(int value);  // 声明信号
};

Sender sender;
MyObject receiver;
QObject::connect(&sender, &Sender::mySignal, &receiver, &MyObject::mySlot);
emit sender.mySignal(42);  // 发射信号,触发槽函数

槽函数的灵活性

槽函数比信号更加灵活,因为它们可以有返回值和参数,可以直接调用,还可以与多个信号关联。以下是一些示例:

槽函数带返回值

尽管槽函数通常没有返回值,但在某些情况下它们可以有返回值:

class MyObject : public QObject {
    Q_OBJECT

public slots:
    int calculateSum(int a, int b) {
        return a + b;
    }
};

MyObject obj;
int result = obj.calculateSum(3, 4);  // 直接调用槽函数,获取返回值
一个槽函数连接多个信号

同一个槽函数可以连接到多个不同的信号,从而处理不同的事件:

class Sender1 : public QObject {
    Q_OBJECT
signals:
    void signal1(int value);
};

class Sender2 : public QObject {
    Q_OBJECT
signals:
    void signal2(int value);
};

Sender1 sender1;
Sender2 sender2;
MyObject receiver;
QObject::connect(&sender1, &Sender1::signal1, &receiver, &MyObject::mySlot);
QObject::connect(&sender2, &Sender2::signal2, &receiver, &MyObject::mySlot);

emit sender1.signal1(42);  // 触发 mySlot
emit sender2.signal2(84);  // 触发 mySlot

槽函数与信号的连接方式

连接信号和槽的 connect 函数有多种形式,使用新语法可以利用函数指针和 lambda 表达式:

使用函数指针连接
QObject::connect(&sender, &Sender::mySignal, &receiver, &MyObject::mySlot);
使用 lambda 表达式
QObject::connect(&sender, &Sender::mySignal, [](int value) {
    qDebug() << "Lambda slot called with value:" << value;
});

定义自定义槽

自定义槽函数与普通的成员函数非常相似,但它们需要在类声明中使用 slots 关键字标识。槽函数可以是 publicprotectedprivate 的。

自定义槽的特点
  • 成员函数:槽函数是类的成员函数,可以访问类的成员变量和其他成员函数。
  • 参数和返回值:槽函数可以有参数和返回值,但通常不会有返回值,因为信号和槽机制主要用于事件驱动编程。
  • 公开或私有:槽函数可以是 public、protected 或 private 的,这取决于你希望如何控制对槽的访问。
简单自定义槽

头文件 (myobject.h)

#ifndef MYOBJECT_H
#define MYOBJECT_H

#include <QObject>
#include <QDebug>

class MyObject : public QObject {
    Q_OBJECT

public:
    explicit MyObject(QObject *parent = nullptr);

public slots:
    void myCustomSlot(int value);
};

#endif // MYOBJECT_H

实现文件 (myobject.cpp)

#include "myobject.h"

MyObject::MyObject(QObject *parent) : QObject(parent) {}

void MyObject::myCustomSlot(int value) {
    qDebug() << "Custom slot called with value:" << value;
}
连接信号到自定义槽

在你的应用程序中,创建对象并将信号连接到自定义槽。

头文件 (sender.h)

#ifndef SENDER_H
#define SENDER_H

#include <QObject>

class Sender : public QObject {
    Q_OBJECT

signals:
    void mySignal(int value);
};

#endif // SENDER_H

主程序 (main.cpp)

#include <QCoreApplication>
#include "myobject.h"
#include "sender.h"

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    MyObject receiver;
    Sender sender;

    QObject::connect(&sender, &Sender::mySignal, &receiver, &MyObject::myCustomSlot);

    emit sender.mySignal(42);  // 发射信号,触发自定义槽

    return a.exec();
}

信号(Signals)

信号是 Qt 对象在特定事件发生时发出的。它们可以由 Qt 框架中的类定义,也可以由用户自定义的类定义。信号是事件驱动的,即它们只在特定事件发生时发出,并且不包含返回类型(即它们是单向的)。信号的发出类似于广播,没有特定的接收者,只要有对象对这个信号感兴趣并进行了连接,就可以接收到这个信号。

自定义信号

自定义信号是用户根据需要在自己的类中定义的信号。它们在类声明中通过 signals 关键字声明,不需要在类实现文件中定义,因为信号本身没有实现,只是一个声明。信号的发射是通过 emit 关键字来完成的。

自定义信号的示例

头文件 (sender.h)

#ifndef SENDER_H
#define SENDER_H

#include <QObject>

class Sender : public QObject {
    Q_OBJECT

public:
    explicit Sender(QObject *parent = nullptr);

signals:
    void mySignal(int value);  // 自定义信号

public:
    void emitSignal();  // 方法用于发射信号
};

#endif // SENDER_H

实现文件 (sender.cpp)

#include "sender.h"

Sender::Sender(QObject *parent) : QObject(parent) {}

void Sender::emitSignal() {
    emit mySignal(42);  // 发射信号
}

使用自定义信号

要使用自定义信号,你需要将信号连接到一个槽函数,当信号被发射时,槽函数将被调用。

头文件 (receiver.h)

#ifndef RECEIVER_H
#define RECEIVER_H

#include <QObject>
#include <QDebug>

class Receiver : public QObject {
    Q_OBJECT

public:
    explicit Receiver(QObject *parent = nullptr);

public slots:
    void myCustomSlot(int value);  // 自定义槽函数
};

#endif // RECEIVER_H

实现文件 (receiver.cpp)

#include "receiver.h"

Receiver::Receiver(QObject *parent) : QObject(parent) {}

void Receiver::myCustomSlot(int value) {
    qDebug() << "Custom slot called with value:" << value;
}

主程序 (main.cpp)

#include <QCoreApplication>
#include "sender.h"
#include "receiver.h"

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    Sender sender;
    Receiver receiver;

    // 连接信号和槽
    QObject::connect(&sender, &Sender::mySignal, &receiver, &Receiver::myCustomSlot);

    sender.emitSignal();  // 发射信号,触发自定义槽

    return a.exec();
}

信号和槽的连接方式

Qt 提供了多种连接信号和槽的方法,主要有以下几种:

使用旧的字符串语法
QObject::connect(&sender, SIGNAL(mySignal(int)), &receiver, SLOT(myCustomSlot(int)));

这种方式适用于 Qt 4 及之前的版本,但在 Qt 5 及之后推荐使用新语法。

使用函数指针的新语法(推荐)
QObject::connect(&sender, &Sender::mySignal, &receiver, &Receiver::myCustomSlot);

这种方式在编译时进行类型检查,确保信号和槽的参数类型匹配,更加安全可靠。

使用 lambda 表达式
QObject::connect(&sender, &Sender::mySignal, [](int value) {
    qDebug() << "Lambda slot called with value:" << value;
});

lambda 表达式非常灵活,适用于处理简单的信号响应。

信号和槽的特点

  1. 类型安全:连接信号和槽时,Qt 会在编译时检查参数类型是否匹配。
  2. 松散耦合:对象之间可以通过信号和槽进行通信,而不需要彼此知道具体的实现,从而实现松散耦合。
  3. 多对多连接:一个信号可以连接到多个槽,一个槽也可以连接到多个信号。信号还可以连接到其他信号,从而形成信号链。
  4. 线程安全:Qt 的信号和槽机制支持多线程编程,可以在不同线程间传递信号。

高级示例:带返回值的自定义信号(使用新特性)

尽管信号通常没有返回值,但可以使用 Qt 的新特性(如 Qt 6 中引入的特性)实现带返回值的信号和槽。

头文件 (advancedreceiver.h)

#ifndef ADVANCEDRECEIVER_H
#define ADVANCEDRECEIVER_H

#include <QObject>
#include <QDebug>

class AdvancedReceiver : public QObject {
    Q_OBJECT

public:
    explicit AdvancedReceiver(QObject *parent = nullptr);

public slots:
    int calculateSum(int a, int b);  // 自定义槽函数,带返回值
};

#endif // ADVANCEDRECEIVER_H

实现文件 (advancedreceiver.cpp)

#include "advancedreceiver.h"

AdvancedReceiver::AdvancedReceiver(QObject *parent) : QObject(parent) {}

int AdvancedReceiver::calculateSum(int a, int b) {
    int result = a + b;
    qDebug() << "Sum:" << result;
    return result;
}

主程序 (main.cpp)

#include <QCoreApplication>
#include "sender.h"
#include "advancedreceiver.h"

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    AdvancedReceiver receiver;
    Sender sender;

    // 使用 lambda 表达式连接信号和槽,并处理返回值
    QObject::connect(&sender, &Sender::mySignal, [&receiver](int value) {
        int sum = receiver.calculateSum(value, 10);
        qDebug() << "Received sum:" << sum;
    });

    sender.emitSignal();  // 发射信号,触发自定义槽

    return a.exec();
}

【【V5.11.31版QT教程】项目创建 - 初识 Qt(QT5+VS2022开发环境)从零基础入门开始】

C/C++QT开发 大厂面试题、学习资料、教学视频和学习路线图,↓↓↓↓↓↓见下面文章底部点击免费领取↓↓↓↓↓↓

  • 29
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值