1.QT核心机制
-
元对象
Qt的元对象系统(meta-object)提供了用于内部对象通讯的信号与槽(signals & slots)机制,运行时类型信息,以及动态属性系统;
(1)QObject类为所有对象提供了一个基类,只要继承此类,那创建出的对象便可以使用元对象系统;
(2)在声明类时,将Q_OBJECT宏放置于类的私有区域就可以在类中使能元对象特性,诸如动态属性,信号,以及槽。一般实际使用中,我们总是把Q_OBJECT宏放置在类声明时的开头位置,除此之外我们的类还需要继承QObject类;
(3)元对象编译器(Meta-Object Compiler,缩写moc),为每个QObject的子类提供必要的代码去实现元对象特性; -
属性系统
-
信号与槽
Qt的信号槽机制是一种用于实现对象间通信的机制。它允许一个对象(信号发出者)发出一个特定的信号,而另一个对象(槽函数接收者)可以接收这个信号并作出相应的反应。
在Qt中,信号槽的实现依赖于元对象系统(Meta-Object System)的支持。这个系统通过在编译时为每个具有信号槽需求的类生成额外的元对象信息(MOC文件),实现了信号槽的动态连接。
具体工作原理如下:
- 定义信号:在类的声明中使用
signals
关键字声明一个信号,信号是一种特殊的成员函数,没有具体的实现。 - 定义槽函数:在类的声明中声明一个槽函数,槽函数的定义和普通的成员函数一样。
- 连接信号和槽:使用
QObject::connect
函数将信号和槽函数连接起来,这样当信号被发出时,槽函数会被调用。 - 发出信号:通过使用
emit
关键字在信号发出者对象中发出一个信号。 - 槽函数响应:当信号被发出时,如果与之连接的槽函数已经被执行,那么槽函数会被调用并执行相应的操作。
需要注意的是,信号和槽函数的参数类型和个数必须匹配(信号的参数个数可以比槽多),否则在连接时会产生编译错误。此外,信号和槽函数可以是任何访问权限(public、protected、private)的成员函数,但是在连接时只有具有公共访问权限(public)的信号和槽函数才能被连接成功。
信号槽第五个参数
在Qt的信号槽机制中,第五个参数是Qt::ConnectionType,用于指定信号槽的连接类型。Qt提供了几种不同的连接类型,常用的有以下几种:
- AutoConnection(默认类型):当发送信号和接收信号的对象处于同一线程时,使用直接连接(DirectConnection)。当发送信号和接收信号的对象处于不同线程时,使用队列连接(QueuedConnection)。
- DirectConnection:发送信号时,立即调用绑定的槽函数,无论信号和槽函数所属的对象是否在同一线程。这种连接类型适用于信号发送者和接收者在同一线程,且希望及时处理信号。
- QueuedConnection:发送信号时,将信号放入接收对象所在线程的事件队列中,等待事件循环处理。这种连接类型适用于信号发送者和接收者在不同线程,且希望异步处理信号。
- BlockingQueuedConnection:发送信号时,阻塞发送者线程,等待接收者线程处理完信号槽函数后继续执行。这种连接类型适用于信号发送者和接收者在不同线程,但需要同步处理信号。
- UniqueConnection:在连接信号槽之前,检查是否已经存在相同的连接,如果存在则不再连接。这种连接类型适用于希望确保只有一个连接被建立的场景。
信号与槽的多种用法
- 一个信号可以和多个槽相连
这时槽的执行顺序和在不在同一个线程上有关,同一线程,槽的执行顺序和声明顺序有关,跨线程时,执行顺序是不确定的。 - 多个信号可以连接到一个槽
只要任意一个信号发出,这个槽就会被调用。 - 一个信号可以连接到另外的一个信号
当第一个信号发出时,第二个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式没有什么区别。 - 槽可以被取消链接
这种情况并不经常出现,因为当一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽。想主动取消连接就用disconnect()函数中添加任何实现。
C++锁
C++中有以下几种类型的锁:
- 互斥锁(Mutex Lock):用于保护共享资源,只允许一个线程访问共享资源。使用场景包括对共享资源的读写操作、线程同步等。
- 递归锁(Recursive Lock):与互斥锁类似,但允许同一个线程对锁进行多次加锁和解锁。使用场景包括递归调用、嵌套函数等。
- 读写锁(Read-Write Lock):允许多个读操作同时进行,但只允许一个写操作进行。适用于读操作频繁、写操作较少的场景。
- 条件变量锁(Condition Variable Lock):用于线程之间的等待和唤醒机制,常与互斥锁配合使用。使用场景包括等待某个条件满足后再进行操作等。
- 自旋锁(Spin Lock):通过循环等待的方式获取锁,适用于锁的持有时间短、线程挂起开销较大的场景。
- 屏障锁(Barrier Lock):用于同步多个线程,确保它们在达到某个屏障前都暂停执行,然后一起继续执行。
QT内存管理机制
Qt中有两种常用的内存管理机制:显式内存管理和隐式内存管理。
- 显式内存管理:在Qt中,可以使用new关键字来手动创建对象,并使用delete关键字来手动释放对象。例如:
QObject* obj = new QObject();
// 使用obj对象...
delete obj;
- 隐式内存管理:Qt提供了自动内存管理机制,使用了父子关系来管理对象的内存释放。当一个对象被设置为另一个对象的子对象时,父对象会在析构时自动删除其所有子对象。例如:
QObject* parent = new QObject();
QObject* child = new QObject(parent);
// 当parent对象析构时,会自动删除其子对象child
在隐式内存管理中,可以使用QObject的setParent()
方法来设置父对象,也可以在对象创建时通过构造函数的参数来指定父对象。
此外,Qt还提供了智能指针类QSharedPointer和QWeakPointer来进行内存管理。这些指针类使用引用计数的方式来管理对象的生命周期,在没有引用时自动释放对象的内存。
QT多线程
- QThread类:Qt提供的用于创建多线程的基类。通过继承QThread类并重写run()函数来实现多线程操作。优点是简单易用,缺点是灵活性相对较差,无法同时处理多个任务。
- QtConcurrent库:该库提供了一些函数(例如:QtConcurrent::run(),QtConcurrent::map()等),用于简化多线程编程。它可以在函数调用过程中使用多个线程,并使用Qt的信号槽机制实现线程之间的通信。优点是易于使用和理解,但对于较为复杂的线程操作可能不够灵活。
C++ QtConcurrent的map和run都是用于并发执行任务的函数。
功能区别:
map函数接受一个容器和一个函数作为参数,然后并发地应用该函数于容器的每个元素,并且返回一个新的容器,其中包含每个函数调用的结果。
run函数接受一个函数作为参数,并在一个单独的线程中执行该函数。它不会返回任何结果。
返回值的区别:
map函数会返回一个新的容器,其中包含每个函数调用的结果。例如,如果容器中有10个元素,那么map函数会返回一个包含10个结果的新容器。
run函数没有返回值,它是一个void函数。它主要用于执行某些任务,而不需要从任务中获取任何结果。
使用方式上的区别:
map函数适用于需要使用多个线程并发地处理较大数据集的情况。它可以方便地将任务分配给多个线程,并将结果收集在一个容器中。
run函数适用于需要在后台执行某些任务的情况,而不需要处理结果。它可以方便地启动一个线程并执行指定的函数。
QtConcurrent中的map和run函数是用于并行执行任务的函数。
map函数接受一个序列,并对每个元素进行并行处理。它的原型如下:
template <typename Sequence, typename MapFunction>
QFuture<typename QtPrivate::MapResultType<MapFunction, typename Sequence::value_type>::ResultType>
map(const Sequence &sequence, MapFunction function)
下面是一个使用map函数的示例代码:
#include <QtConcurrent>
int increment(int value)
{
return value + 1;
}
int main()
{
// 创建一个包含1到10的序列
QList<int> numbers;
for (int i = 1; i <= 10; i++) {
numbers.append(i);
}
// 并行处理序列中的每个元素
QFuture<int> future = QtConcurrent::map(numbers, increment);
// 等待并行处理的结果
future.waitForFinished();
// 输出结果
for (int i = 0; i < numbers.size(); i++) {
qDebug() << numbers.at(i) << " => " << future.resultAt(i);
}
return 0;
}
输出结果为:
1 => 2
2 => 3
3 => 4
4 => 5
5 => 6
6 => 7
7 => 8
8 => 9
9 => 10
10 => 11
run函数用于在一个新的线程中执行函数。它的原型如下:
template <typename Function>
QFuture<typename QtPrivate::FunctionResultHolder<Function>::ResultType>
run(Function function)
下面是一个使用run函数的示例代码:
#include <QtConcurrent>
void printHello()
{
qDebug() << "Hello from thread:" << QThread::currentThread();
}
int main()
{
// 在一个新的线程中执行函数
QFuture<void> future = QtConcurrent::run(printHello);
// 等待函数执行完成
future.waitForFinished();
return 0;
}
输出结果为:
Hello from thread: QThread(0x1db71741a80)
- QThreadPool类:该类是Qt提供的线程池实现,可以通过创建QRunnable对象并使用QThreadPool::start()函数来实现多线程。它可以动态管理线程数量,并自动复用线程对象。优点是可以有效地管理线程,避免频繁创建和销毁线程的开销,但相对来说稍微复杂一些。
以下是一个使用QThreadPool类的简单C++代码示例:
#include <QCoreApplication>
#include <QRunnable>
#include <QThreadPool>
#include <QDebug>
// 定义一个可运行对象类,继承自QRunnable
class MyRunnable : public QRunnable
{
public:
void run() override
{
qDebug() << "Thread ID: " << QThread::currentThreadId();
qDebug() << "Hello from MyRunnable!";
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// 创建一个线程池对象
QThreadPool threadPool;
// 设置线程池的最大线程数
threadPool.setMaxThreadCount(2);
// 创建并提交任务到线程池
for(int i = 0; i < 5; ++i)
{
MyRunnable *runnable = new MyRunnable();
threadPool.start(runnable);
}
// 退出应用程序时等待线程池中所有任务执行完毕
threadPool.waitForDone();
return a.exec();
}
上面的代码示例中,我们创建了一个名为MyRunnable
的可运行对象类,它继承自QRunnable
。在run()
方法中,我们使用qDebug()
输出当前线程的标识符和一条简单的消息。
在main()
函数中,我们首先创建了一个QThreadPool
对象threadPool
,然后通过调用setMaxThreadCount()
方法设置线程池的最大线程数。接下来,我们使用一个循环创建了5个MyRunnable
对象,并调用start()
方法将它们提交到线程池中执行。
最后,我们调用waitForDone()
方法等待线程池中所有任务执行完毕。这样可以确保在程序退出之前所有线程都已经完成。
请注意,上述代码中的QCoreApplication
类用于创建一个事件循环,它是Qt应用程序必需的。
需要注意的是,在使用多线程时,还需要注意线程之间的数据共享和同步问题,Qt提供了一些机制(例如:QMutex、QWaitCondition、QSemaphore等)来实现线程间的数据共享与同步。