信号和槽 (Signals and Slots)
Qt 框架中的信号和槽机制是其核心特性之一,用于对象间通信,特别适用于 GUI 事件处理。它允许对象发送和接收消息,从而实现松散耦合的设计。
信号和槽的基本概念
- 信号 (Signal):信号是对象状态发生变化时发出的通知。信号不需要实现,只需在类定义中声明即可。
- 槽 (Slot):槽是一个函数,可以在信号发出时被调用。槽是普通的成员函数,只不过可以与信号关联。
信号和槽的连接
使用 QObject::connect
函数来连接信号和槽。信号和槽可以是一个类的成员,也可以跨类连接。
信号和槽的特点
- 类型安全:连接信号和槽时,Qt 会在编译时检查参数类型是否匹配。
- 松散耦合:对象之间可以通过信号和槽进行通信,而不需要彼此知道具体的实现,从而实现松散耦合。
- 多对多连接:一个信号可以连接到多个槽,一个槽也可以连接到多个信号。信号还可以连接到其他信号,从而形成信号链。
高级特性
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
函数形式
- 四参数版本:
bool QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method);
- 五参数版本:
bool QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection);
- 新语法(使用函数指针或 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;
}
};
槽函数的特点
- 成员函数:槽函数是类的成员函数,可以访问类的成员变量和其他成员函数。
- 参数和返回值:槽函数可以有参数和返回值,但通常不会有返回值,因为信号和槽机制主要用于事件驱动编程。
- 公开或私有:槽函数可以是
public
、protected
或private
的,这取决于你希望如何控制对槽的访问。
槽函数的调用
槽函数可以被任何对象直接调用,就像调用普通成员函数一样,但更常见的是通过信号触发调用。这种机制允许对象之间进行松散耦合的通信。
直接调用
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
关键字标识。槽函数可以是 public
、protected
或 private
的。
自定义槽的特点
- 成员函数:槽函数是类的成员函数,可以访问类的成员变量和其他成员函数。
- 参数和返回值:槽函数可以有参数和返回值,但通常不会有返回值,因为信号和槽机制主要用于事件驱动编程。
- 公开或私有:槽函数可以是 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 表达式非常灵活,适用于处理简单的信号响应。
信号和槽的特点
- 类型安全:连接信号和槽时,Qt 会在编译时检查参数类型是否匹配。
- 松散耦合:对象之间可以通过信号和槽进行通信,而不需要彼此知道具体的实现,从而实现松散耦合。
- 多对多连接:一个信号可以连接到多个槽,一个槽也可以连接到多个信号。信号还可以连接到其他信号,从而形成信号链。
- 线程安全: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开发 大厂面试题、学习资料、教学视频和学习路线图,↓↓↓↓↓↓见下面文章底部点击免费领取↓↓↓↓↓↓