一、介绍
Qt库提供了一套通用的基于模板的容器类,可以用这些类存储指定类型的项。比如,你需要一个大小可变的QString的数组,则使用QVector<QString>。
这些容器类比STL(C++标准模板库)容器设计得更轻量、更安全并且更易于使用。如果对STL不熟悉,或者倾向于用“Qt的方式”,那么你可以使用这些类,而不去使用STL的类。(STL容器使用详见首页其他作品)
二、容器类
Qt提供了一些顺序容器:QList、QLinkedList、QVector、QStack和QQueue。因为这些容器中的数据都是一个接一个线性存储的,所以称为顺序容器。大多数时候,QList是最好的选择,虽然是用数组实现的,但在它的首尾添加元素都非常快。如果你需要一个链表(linked-list)就用QLinkedList;想要你的项在内存中连续存储,就使用QVector。QStack和QQueue(栈和队列)分别提供了后进先出(LIFO)和先进先出(FIFO)的机制。
Qt还提供了一些关联容器:QMap、QMultiMap、QHash、QMultiHash、QSet。因为这些容器存储的是<键,值>对,比如QMap<Key,T>,所以称为关联容器。其中“Multi”容器支持一个键对应多个值。“Hash”容器在有序集合上使用hash函数进行快速的查找,而没有用二叉搜索。
容器也可以嵌套使用,例如QMap<QString,QList<int> >,这里键的类型是QString,而值的类型是QList<int>,需要注意,在后面的“> >”符号之间要有一个 空格,不然编译器会将它当作“>>”操作符对待。
该代码运行环境为Windows,采用Mingw64编译器输出结果。
1. QList容器
QList是一个模板类,提供了一个列表。QList<T>实际上是一个T类型项目的指针数组,所以支持基于索引的访问,而且当项目的数目小于1000时,可以实现在列表中间进行快速的插入操作。
QList提供了很多方便的接口函数来操作列表中的项目,例如插入操作insert()、替换操作replace()、移除操作removeAt()、移动操作move()、交换操作swap()、在表尾添加项目append()、在表头添加项目prepend()、从列表中移除一项并获取这个项目takeAt()及相应的takeFirst()和takeLast()、获取一个项目的索引indexOf()、判断是否含有相应的项目contains()以及获取一个项目出现的次数count()等。
插入
#include "widget.h"
#include <QApplication>
#include <QList>
#include <QMap>
#include <QSplitter>
#include <QVector2D>
#include <QVector>
#include <QtAlgorithms>
#include <QtCore>
#include <QtWidgets>
void printList(QList<int>&li)
{
for(auto it = li.begin();it != li.end();it++)
{
qDebug()<< *it <<"=\t"<< &(*it);
}
}
int main(int argc, char* argv[])
{
QCoreApplication a(argc, argv); //无GUI的窗口
QList<int> li;
//插入
li.insert(li.begin(),1); //在迭代器开始位置插入1
li.insert(0,2); //在0序号位置插入 2
li.insert(4,100); //在4序号位置插入 100
li.append(10); //追加元素10
li.push_back(3); // 尾插
li.push_front(3); // 头插
li.push_back(1);
li.push_back(1);
li.push_back(1);
li.push_back(1);
li << 20 << 30<<40;
//替换
li.replace(0,200);
printList(li);
return a.exec();
}
运行结果如下,可以看出QList容器的元素地址分配十分规律,一次偏移8字节 ,存储的应该是指针(不确定)
其他操作
QList<int> li;
//插入
li << 20 << 30<<40<< 50<<60 <<30 ;
//删除
li.erase(li.begin());
li.takeAt(0);
// li.takeLast(); // 删除最后一个元素
// li.takeFirst(); // 删除第一个元素
//替换
li.replace(3,200); // 3下标 为第四个
//统计
qDebug()<<"count(3) = "<<li.count(30); //统计元素出现的次数
qDebug()<<"contains 50? = "<< li.contains(50);
QVector容器
插入
插入和QList容器相比,insert方法插入 li.insert(4,100); 报错:不能够超出索引插入元素。
#include "widget.h"
#include <QApplication>
#include <QList>
#include <QMap>
#include <QSplitter>
#include <QVector2D>
#include <QVector>
#include <QtAlgorithms>
#include <QtCore>
#include <QtWidgets>
void printList(QVector<int>&li)
{
for(auto it = li.begin();it != li.end();it++)
{
qDebug()<< *it <<"=\t"<< &(*it);
}
}
int main(int argc, char* argv[])
{
QCoreApplication a(argc, argv); //无GUI的窗口
QVector<int> li;
//插入
li.insert(li.begin(),1); //在迭代器开始位置插入1
li.insert(0,2); //在0序号位置插入 2
// li.insert(4,100); //在4序号位置插入 100
li.append(10); //追加元素10
li.push_back(3); // 尾插
li.push_front(3); // 头插
li.push_back(1);
li.push_back(1);
li.push_back(1);
li.push_back(1);
li << 20 << 30<<40;
//替换
li.replace(0,200);
printList(li);
return a.exec();
}
运行结果如下,可以看到内存直接分配的间隔为4字节,比QList小 ,存储的为int的大小
其他方法
比QList容器多个capacity 方法,类似与std::vector容器
QLinkedList
QLinkedList 的废弃:从 Qt 6.0 开始,QLinkedList 容器已被废弃并从 Qt 容器库中移除。Qt 团队推荐使用 std::list
(C++ 标准库中的双向链表)作为替代
QLinkedList(Qt链表)是一个 C++ 标准库中的双向链表容器类,它是 Qt 框架中的一部分。Qt 框架是一个跨平台的应用程序开发框架,广泛应用于 GUI 和非 GUI 程序的开发。QLinkedList 提供了一个高效的数据结构,用于存储和管理元素的集合。
重要性:
动态大小:QLinkedList 的大小是动态的,可以根据需要轻松添加或删除元素。这使得它在处理具有不确定大小的数据集时非常有用。
双向遍历:与 QVector 和 QList 不同,QLinkedList 允许从头到尾以及从尾到头的双向遍历。这种遍历方式在某些场景下会非常有用。
高效的插入和删除操作:在链表的中间插入或删除元素的时间复杂度为 O(1)。这使得 QLinkedList 在需要频繁插入和删除元素的场景中,具有优势。
不需要连续内存:与 QVector 和 QList 相比,QLinkedList 的元素不需要存储在连续的内存空间中。这意味着在内存碎片化的情况下,QLinkedList 可能更容易分配内存。
QLinkedList 的性能优化
QLinkedList 是 Qt 提供的双向链表容器,适用于需要频繁插入和删除元素的场景。在某些情况下,使用 QLinkedList 可以获得更好的性能。以下是关于 QLinkedList 性能优化的一些建议:
适用场景选择:在需要频繁插入和删除元素的场景下,使用 QLinkedList 是合适的,因为它在这些操作上具有优秀的性能。然而,如果需要频繁地随机访问元素,QList 或 QVector 可能是更好的选择,因为它们提供更快的随机访问速度。
使用迭代器:QLinkedList 提供了迭代器接口,以高效地遍历链表。在需要遍历链表时,使用迭代器可以获得更好的性能。要注意的是,在遍历过程中插入或删除元素时,应使用迭代器的 insert() 和 remove() 函数,以保持链表的完整性。
减少内存分配次数:尽管 QLinkedList 在插入和删除元素时性能优越,但它的内存分配次数相对较多。为了提高性能,可以预先估计链表的大小,并通过 reserve() 函数预留相应的内存空间,以减少内存分配次数。
使用空间局部性:QLinkedList 的元素存储在非连续的内存地址中,这可能导致 CPU 缓存利用率降低。在可能的情况下,尝试让相邻的元素在逻辑上也相邻,以提高空间局部性。
避免频繁拷贝:链表的拷贝操作相对较慢。如果需要共享数据,可以考虑使用 QSharedPointer 进行引用计数,以避免频繁的拷贝操作。
总之,为了充分发挥 QLinkedList 的性能优势,开发者应在合适的场景下使用它,并采用迭代器、预留内存等方法进行优化。同时,也要注意避免在不适合的场景下使用 QLinkedList,以确保整体性能的最优化。
插入
容器的insert插入方法不能指定下标,需要迭代器,且没有replace方法。
template <typename T>
void printList(QLinkedList<T>&li)
{
for(auto it = li.begin();it != li.end();it++)
{
qDebug()<< *it <<"=\t"<< &(*it);
}
}
int main(int argc, char* argv[])
{
QCoreApplication a(argc, argv); //无GUI的窗口
QLinkedList<int> li;
//插入
li.insert(li.begin(),1); //在迭代器开始位置插入1
// li.insert(0,2); //在0序号位置插入 2
// li.insert(4,100); //在4序号位置插入 100
li.append(10); //追加元素10
li.push_back(3); // 尾插
li.push_front(3); // 头插
li.push_back(1);
li.push_back(1);
li.push_back(1);
li.push_back(1);
li << 20 << 30<<40;
//替换
// li.replace(0,200);
printList(li);
a.exec();
}
结果如下,可以看到元素直接不是顺序存储的,是通过指针链接在一起的,两个相连元素之间大概相差32字节,(因为是双向链表,内存随机分配,每个节点应该包含了 int类型4字节和两个指针16字节,再加上内存偏移)
其他操作
QLinkedList<int> li;
//插入
li << 20 << 30<<40<< 50<<60 <<30 ;
//删除
li.erase(li.begin());
// li.takeAt(0);
// li.takeLast(); // 删除最后一个元素
// li.takeFirst(); // 删除第一个元素
//替换
//统计
qDebug()<<"count(3) = "<<li.count(30); //统计元素出现的次数
qDebug()<<"contains 50? = "<< li.contains(50);
//访问元素
li.back();
li.front();
QLinkedList<int>::iterator it = std::find(li.begin(),li.end(),30);
if(it!= li.end())
{
qDebug() <<*it;
}
QStack
QStack(Qt Stack)是一个来自于Qt框架的容器类,类似于C++标准库中的std::stack。QStack容器是一种后进先出(LIFO)的数据结构,即最后一个进入堆栈的元素将最先被移除。QStack继承了QVector,所以它拥有QVector的所有功能,同时提供了堆栈的特定操作。
由于QStack继承自QVector,因此它还具有QVector的所有成员函数,如append()、at()、capacity()、contains()、count()、data()、fill()、indexOf()、insert()、lastIndexOf()、mid()、prepend()、remove()、removeAll()、removeAt()、removeFirst()、removeLast()、replace()、reserve()、resize()、size()、squeeze()、startsWith()、swap()、takeAt()、takeFirst()、takeLast()、value()等。然而,在使用QStack时,最好坚持使用堆栈特定的接口,以保持代码的清晰性和可读性。
#include "widget.h"
#include <QApplication>
#include <QList>
#include <QMap>
#include <QSplitter>
#include <QVector2D>
#include <QVector>
#include <QtAlgorithms>
#include <QtCore>
#include <QtWidgets>
template <typename T>
void printList(QStack<T>&li)
{
for(auto it = li.begin();it != li.end();it++)
{
qDebug()<< *it <<"=\t"<< &(*it);
}
}
int main(int argc, char* argv[])
{
QCoreApplication a(argc, argv); //无GUI的窗口
QStack<int>s;
s.reserve(100); //提前分配内存
s.size(); //获取数据大小
s.capacity(); //查看容量大小
s.push(2);
s.push(3);
s.push(4);
s.push(5);
s.insert(0,1);
s.insert(1,4);
//查看栈顶元素
s.top();
//弹出栈顶元素
s.pop();
printList(s);
a.exec();
}
运行结果如下,栈是一种顺序存储的容器,每个元素直接相隔4字节大小。
其他用法
s.toList(); //转为QList容器
s.toStdVector(); //转为std::vector
printList(s);
qDebug()<<"stack size = "<<sizeof(s);
QList<int>l=s.toList();
std::vector<int> v=s.toStdVector();
printList(l);
qDebug()<<"QStack size = "<<sizeof(l);
qDebug()<<"std::vector size = "<<sizeof(v);
v.push_back(10);
qDebug()<<"std::vector size = "<<sizeof(v);
这里补充下关于QStack容器大小的问题,stack容器里有5个int类型的容器,我们可能以为sizeof的大小的5*4 =20;但是这里却是8字节,转为STL中的容器,可以看到结果如下所示,这些容器的内部会有各自的实现。
QQueue容器
QQueue 是 Qt 库中的一个容器类,它表示一个双端队列(double-ended queue,简称 deque)。QQueue 提供了在队列两端进行高效插入和删除操作的功能。QQueue 继承自 QList,可以在首尾方便地添加和删除元素。下面是一些 QQueue 的使用场景:
任务队列:可以用 QQueue 来管理一个任务队列,将待处理的任务添加到队列中,然后依次处理并移除已完成的任务。这种场景下,可以将 QQueue 当作一个先进先出(FIFO)数据结构来使用。
消息队列:在多线程应用中,可以使用 QQueue 存储线程间通信的消息。主线程将消息添加到队列中,子线程从队列中取出消息并处理。这种场景下,需要确保对队列的访问是线程安全的,可以通过加锁等机制实现。
数据缓冲:在处理大量数据时,可以使用 QQueue 作为缓冲区,存储已经处理过的数据,以便在需要时能够快速访问。例如,在处理实时音频或视频数据时,可以将已处理的数据帧存入 QQueue 中,以备后续的播放或分析使用。
编程算法:QQueue 可以用于实现一些编程算法,如宽度优先搜索(BFS)等。通过将待处理的节点添加到队列中,可以保证算法的执行顺序。
事件处理:在事件驱动的程序中,可以使用 QQueue 存储待处理的事件。当事件发生时,将事件对象添加到队列中,然后逐个处理并删除队列中的事件。
比QVector容器多了enqueue插入元素的方法。
综上所述,QQueue作为一种实用的数据结构,广泛应用于不同领域的软件开发。了解QQueue的重要性和基本概念,将有助于开发者更好地利用这一工具解决实际问题。
QQueue的性能优化
QQueue 是 Qt 容器类之一,它实现了一个先进先出(FIFO)的队列。QQueue 是 QList 的子类,因此它继承了 QList 的许多特性。为了优化 QQueue 的性能,可以采取以下几个方面的措施:利用 QList 的特性:QQueue 的底层实现基于 QList,所以可以利用 QList 的性能优势。QList 的内存分配策略使得在队列的头部和尾部插入和删除元素具有较高效率。因此,当使用 QQueue 时,尽量避免在队列中间进行插入和删除操作。
预分配内存:如果可以预估队列的最大大小,那么可以使用 QQueue::reserve() 函数预先分配内存。这将有助于减少内存分配和释放的开销,提高性能。
避免频繁的内存分配:在频繁地向队列添加和删除元素时,避免频繁的内存分配和释放。可以考虑使用对象池或其他内存管理技术来重用对象,从而提高性能。
使用 const 成员函数:在遍历队列或访问队列元素时,使用 const 成员函数(如 constFirst() 和 constLast())可以避免不必要的拷贝操作,提高性能。
考虑使用其他容器:在某些特定场景下,QQueue 可能不是性能最优的选择。例如,如果需要处理大量连续内存访问操作,可以考虑使用 QVector。同样,如果需要在列表中间频繁地插入和删除元素,QLinkedList 可能是更好的选择。根据实际需求选择合适的容器类,以实现最佳性能。
总之,在使用 QQueue 时,可以通过充分利用 QList 的特性、预分配内存、避免频繁的内存分配、使用 const 成员函数以及根据需求选择合适的容器类等方法来优化性能。
插入
#include "widget.h"
#include <QApplication>
#include <QList>
#include <stack>
#include <queue>
#include <map>
#include <QMap>
#include <QSplitter>
#include <QVector2D>
#include <QVector>
#include <QtAlgorithms>
#include <QtCore>
#include <QtWidgets>
#include <vector>
template <typename T>
void printList(QList<T>&li)
{
for(auto it = li.begin();it != li.end();it++)
{
qDebug()<< *it <<"=\t"<< &(*it);
}
}
template <typename T>
void printList(QQueue<T>&li)
{
for(auto it = li.begin();it != li.end();it++)
{
qDebug()<< *it <<"=\t"<< &(*it);
}
}
int main(int argc, char* argv[])
{
QCoreApplication a(argc, argv); //无GUI的窗口
QQueue<int>li;
li.enqueue(2);
li.enqueue(3);
li.insert(0,1);
li << 20 << 30<<40;
//替换
li.replace(0,200);
qDebug()<<"head = "<<li.head();
qDebug()<<"take = "<<li.takeAt(3); //删除元素返回元素值
li.dequeue(); //出队列
printList(li);
a.exec();
}
可以看到 QQueue是一个顺序存储的容器,每个元素之间的间隔为8个字节。同QList(继承关系)