信号与槽
Qt
信号与槽机制可以看作是一种观察者模式(Observer Pattern)
的设计模型。观察者模式中,有一个被观察的对象(Subject),它可以发出事件或状态改变,并通知所有依赖它的观察者(Observer)。Qt
中的信号可以看作是 Subject 发出的事件
,而槽可以看作是 Observer 对事件作出的响应
。通过信号和槽机制,Qt 实现了一种松耦合、灵活性高的对象间通信方法,能够在许多场景下简化代码逻辑,提高程序的可维护性和可扩展性。
信号与槽的实现需要借助元对象编译器MOC(Meta Object Compiler),这个工具被集成在了 Qt 的编译工具链 qmake
中,在开始编译 Qt
工程时,会先去执行 MOC。要使用信号 – 槽功能,先决条件是继承 QObject
类,并在类声明中增加 Q_OBJECT
宏。之后在signals
字段之后声明一些函数,这些函数就是信号。在public slots
之后声明的函数,就是槽函数。
static QMetaObject::Connection connect(
const QObject *sender, //信号发送对象指针
const char *signal, //信号函数字符串,使用SIGNAL()
const QObject *receiver, //槽函数对象指针
const char *member, //槽函数字符串,使用SLOT()
Qt::ConnectionType = Qt::AutoConnection//连接类型,一般默认即可
);
//例如
connect(pushButton, SIGNAL(clicked()), dialog, SLOT(close()));
static QMetaObject::Connection connect(
const QObject *sender, //信号发送对象指针
const QMetaMethod &signal,//信号函数地址
const QObject *receiver, //槽函数对象指针
const QMetaMethod &method,//槽函数地址
Qt::ConnectionType type = Qt::AutoConnection//连接类型,一般默认即可
);
//例如
connect(pushButton, QPushButton::clicked, dialog, QDialog::close);
信号和槽的关联方式,它决定了信号是立即传送到一个槽还是在稍后时间排队等 待传送。关联方式使用枚举 Qt::ConnectionType
进行描述,下表为其取值及意义:
枚举 | 值 | 说明 |
---|---|---|
Qt::AutoConnection | 0 | 自动关联,默认值。若信号和槽在同一线程中,则使用Qt::DirectConnection ,否则,使用 Qt::QueuedConnection 。当信号发射时确定使用哪种关联类型。 |
Qt::DirectConnection | 1 | 直接关联。当信号发射后,立即调用槽。在槽执行完之后,才会执行发射信号之后的代码 (即 emit 关键字之后的代码)。该槽在信号线程中执行。 |
Qt::QueuedConnection | 2 | 队列关联。当控制权返回到接收者线程的事件循环后,槽才会被调用,也就是说 emit 关键字后面的代码将立即执行,槽将在稍后执行,该槽在接收者的线程中执行。(异步非阻塞) |
Qt::BlockingQueuedConnection | 3 | 阻塞队列关联。和 Qt::QueuedConnection 一样,只是信号线程会一直阻塞,直到槽返回。如果接收者驻留在信号线程中,则不能使用此连接,否则应用程序将会死锁。(同步阻塞) |
Qt::UniqueConnection | 0x80 | 唯一关联。这是一个标志,可使用按位或与上述任何连接类型组合。当设置 Qt::UniqueConnection 时,则只有在不重复的情况下才会进行连接,如果已经存在重复连接 (即,相同的信号指向同一对象上的完全相同的槽),则连接将失败,此时将返回无效的 QMetaObject::Connection |
Qt 信号与槽机制是 Qt 框架的核心部分之一,它能够实现对象之间的解耦,使得一个对象的变化可以自动地通知到其他对象,并且能够方便地在不同的线程之间进行通信。它的底层实现原理主要涉及到三个方面:元对象系统、信号和槽的连接、信号和槽的触发。
- 元对象系统
Qt
的元对象系统是由 QObject
类提供的,允许在运行时查询对象的属性和方法,并提供了信号和槽机制。在运行时,QObject
类会为每个类创建一个元对象(meta-object),它是Q_OBJECT
宏展开后创建的,它包含了该类的所有信息,例如类名、父类和成员函数等。元对象还包含了信号和槽的信息,即它们的名称和参数类型等。
- 信号和槽的连接
当一个信号和一个槽连接时,会在元对象中创建一个连接表(connection table),用于存储这个连接的信息。连接表是一个二维数组,其中每行表示一个信号和一个槽的连接,第一列和第二列分别表示信号和槽的指针,第三列是一个整数,表示连接类型(例如,连接方式是直接连接还是队列连接)。连接表还包含了一个函数指针数组,用于执行连接的操作。当信号被触发时,连接表中与这个信号相关的函数指针就会被调用,从而触发槽的执行。
- 信号和槽的触发
当一个信号被触发时,Qt
会根据连接表中的信息,调用相应的函数指针来触发槽的执行。如果连接类型是直接连接,那么槽函数就会直接在当前线程中执行;如果连接方式是队列连接,那么槽函数就会被放入事件队列中,等待事件循环处理。这种方式可以保证槽函数在正确的线程中执行,从而避免了线程安全问题。
总之,Qt
信号与槽机制是一种高效、灵活和易用的编程模式,它能够帮助我们实现对象之间的松耦合和消息传递,减少代码的耦合度和复杂度。其底层实现主要依赖于元对象系统、信号和槽的连接以及信号和槽的触发等。
Http
在 Qt 中,可以使用 QNetworkAccessManager
类来实现 Http 下载文件。具体步骤如下:
// 1. 创建QNetworkAccessManager对象:
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
// 2. 创建QNetworkRequest对象,并设置下载地址:
QUrl url("http://www.example.com/file.zip");
QNetworkRequest request(url);
// 3. 发送 Http 请求并获取响应:
QNetworkReply *reply = manager->get(request);
// 4. 接收响应并保存文件:
QFile file("file.zip");
if (file.open(QIODevice::WriteOnly)) {
file.write(reply->readAll());
file.close();
}
TCP
服务器端QTcpServer
:
auto tcpServer = new QTcpServer(this);
connect(tcpServer, &QTcpServer::newConnection, this, &Dialog::sendData);
connect(tcpServer, &QTcpServer::acceptError, this, &Dialog::displayError);
tcpServer->listen(QHostAddress::Any, 8888);
TcpServer
是 Qt 中用来创建 TCP 服务的类,其主要功能是监听客户端连接请求并创建对应的QTcpSocket
对象。QTcpServer
类的重要函数如下:重要函数:
listen()
:开始监听指定的端口。close()
:停止监听端口。nextPendingConnection()
:获取下一个等待处理的连接,如果没有等待的连接,则返回nullptr
。error()
:获取最后一次出现的错误。hasPendingConnections()
:判断是否有等待处理的连接。maxPendingConnections()
:获取最大等待连接数。setMaxPendingConnections()
:设置最大等待连接数。重要信号:
newConnection()
:当有新的连接请求时,会发出该信号。acceptError()
:当接受连接时出现错误时,会发出该信号。closed()
:当server
关闭时,会发出该信号。需要注意的是,
QTcpServer
是异步的,当有新的连接请求时,会发出newConnection()
信号,需要在该信号的槽函数中调用nextPendingConnection()
函数获取新的QTcpSocket
对象,并使用信号和槽机制来处理连接事件。
客户端QTcpSocket
:
// 1. 创建 QTcpSocket 对象:
QTcpSocket *socket = new QTcpSocket(this);
// 2. 连接服务器:
socket->connectToHost("localhost", 12345);
if (!socket->waitForConnected()) {
qDebug() << "Failed to connect to server";
return;
}
// 3. 发送数据:
socket->write("Hello, world!");
// 4. 接收数据:
if (socket->waitForReadyRead()) {
QByteArray data = socket->readAll();
qDebug() << "Received data: " << data;
}
需要注意的是,Qt
的网络模块是异步的,因此需要使用信号和槽机制来处理网络事件。同时需要注意,在发送完数据后,需要等待服务器返回数据才能读取数据。可以使用waitForReadyRead()
函数等待数据到达,也可以使用readyRead()
信号来处理数据到达事件。
QTcpSocket
是Qt
中用来实现TCP
客户端的类,其重要函数和信号如下:重要函数:
connectToHost()
:连接到指定的主机和端口。disconnectFromHost()
:断开与主机的连接。write()
:向socket
写入数据。readAll()
:读取所有可用的数据。state()
:获取当前socket
的状态。setReadBufferSize()
:设置读取缓冲区的大小。重要信号:
connected()
:当socket
成功连接到远程主机时发出。disconnected()
:当socket
从远程主机断开连接时发出。readyRead()
:当有数据可读时发出。stateChanged()
:当socket
状态发生改变时发出。error()
:当socket
发生错误时发出。
UDP
// 1. 创建 QUdpSocket 对象:
auto socket = new QUdpSocket(this);
// 2. 绑定端口:
if (!socket->bind(QHostAddress::Any, 12345)) {
qDebug() << "Failed to bind port";
return;
}
// 3. 实现接收数据的处理:
connect(socket, &QUdpSocket::readyRead, this, &MyServer::readDataHandler);
void MyServer::readDataHandler() {
while (socket->hasPendingDatagrams()) {
QByteArray data;
data.resize(socket->pendingDatagramSize());
QHostAddress sender;
quint16 senderPort;
socket->readDatagram(data.data(), data.size(), &sender, &senderPort);
qDebug() << "Received data: " << data;
}
}
QUdpSocket
是Qt
中用来实现UDP
通信的类,其重要函数和信号如下:重要函数:
bind()
:绑定指定的端口和地址。writeDatagram()
:向指定的地址和端口发送数据。close()
:关闭 socket。hasPendingDatagrams()
:判断是否有等待处理的数据报。readDatagram()
:读取一个数据报。joinMulticastGroup()
:加入指定的多播组。leaveMulticastGroup()
:离开指定的多播组。setSocketOption()
:设置 socket 选项。重要信号:
readyRead()
:当有数据可读时发出。stateChanged()
:当 socket 状态发生改变时发出。error()
:当 socket 发生错误时发出。connected()
:当 socket 成功连接到远程主机时发出。disconnected()
:当 socket 从远程主机断开连接时发出。需要注意的是,由于 Qt 的网络模块是异步的,因此需要使用信号和槽机制来处理网络事件。在处理完一个数据包后,需要循环读取所有的数据包以处理后续数据。
Qt单例
在 Qt 中,可以通过QSharedMemory
判断当前进程是否已经存在来实现 exe 进程单例。具体步骤如下:
QSharedMemory sharedMemory("MyApplication");
if (!sharedMemory.create(1)) {
qDebug() << "Application already running";
return 0;
}
QWidget
与QML
QWidget
和 QML
都是 Qt
中用来实现 GUI 的
工具,但其本质和使用方式有所不同。
QWidget
是基于C++
的GUI
工具,而QML
是基于JavaScript
的 GUI工具
。QWidget
需要通过编写C++
代码来创建和管理GUI
,而QML
则使用一种类似于HTML
的声明式语言来创建GUI
。QWidget
使用的是Qt Widgets
模块,而QML
使用的是Qt Quick
模块。Qt Widgets
提供了一系列的控件和布局管理器,可以方便地创建传统的桌面应用程序。而Qt Quick
则提供了一套用于创建流畅、高效的界面的API
,适用于创建现代化的移动应用程序。QWidget
支持的平台包括Windows、Mac
和Linux
等桌面操作系统,而QML
则可以运行在多种平台上,包括桌面、移动、嵌入式和Web
等。- 在使用上,
QWidget
需要通过信号和槽机制来实现控件之间的交互,而 QML 则可以直接在代码中绑定属性和函数来实现交互。 QWidget
使用的绘制引擎是基于底层操作系统的原生控件实现的,例如在Windows
下使用的是GDI+
,在macOS
下使用的是Quartz
,这些底层控件都是由操作系统提供的,因此QWidget
的绘制效果和性能与操作系统有直接关系。QML
使用的是OpenGL ES 2.0
来实现绘制,这是一种跨平台的图形API
,可以在多种平台上运行,并且具有高效的绘制性能。QML
使用OpenGL ES 2.0
来绘制所有的GUI
元素,包括文本、图像和动画等。- 由于
QWidget
使用的是底层操作系统的原生控件,因此其绘制效果和性能稳定可靠,但缺乏灵活性,不易于实现自定义控件。而QML
则可以实现高度自定义的控件,并且具有流畅、高效的动画效果,但其绘制效果和性能可能会因为底层OpenGL
实现的不同而有所不同。
Qt多线程
在 Qt 中,可以使用多种方式实现多线程执行任务,例如:
- 使用
QThread
类:继承QThread
类并重载其run()
函数来执行任务,然后创建新线程并启动。
class MyThread : public QThread
{
public:
void run() override
{
// 执行任务
}
};
MyThread *thread = new MyThread;
thread->start();
- 使用
QtConcurrent
类:使用QtConcurrent::run()
函数来启动一个新线程并执行任务。
QtConcurrent::run([](){
// 执行任务
});
- 使用
QThreadPool
类:将任务封装为一个QRunnable
对象,并将其添加到线程池中,线程池会自动分配线程来执行任务.
class MyTask : public QRunnable
{
public:
void run() override
{
// 执行任务
}
};
MyTask *task = new MyTask;
QThreadPool::globalInstance()->start(task);
- 使用信号和槽机制:创建一个
QObject
对象,并将其移动到新线程中,然后使用信号和槽机制来执行任务。
class MyObject : public QObject
{
Q_OBJECT
public slots:
void doTask()
{
// 执行任务
}
};
MyObject *object = new MyObject;
QThread *thread = new QThread;
object->moveToThread(thread);
connect(thread, &QThread::started, object, &MyObject::doTask);
thread->start();