目录
介绍
容器类
迭代器类
STL型迭代器
foreach关键字
其他类似容器的类
算法复杂性
Qt库提供了一组基于模板的通用容器类,这些类可用于存储指定类型的项目。如果需要可调整大小的QString数组,可以使用QVector < QString >。这些容器类设计为比STL容器更轻,更安全,更易于使用。容器类是隐式共享的,它们是可重入的,并且它们针对速度,低内存消耗和最小内联代码扩展进行了优化,从而导致较小的可执行文件。此外,它们在用于访问它们的所有线程用作只读容器的情况下是线程安全的。为了遍历存储在容器中的项,可以使用两种类型的迭代器:Java样式迭代器和STL样式迭代器。Java样式的迭代器更易于使用并提供高级功能,而STL样式的迭代器稍微更高效,可以与Qt和STL的通用算法一起使用。Qt还提供了一个foreach关键字,可以很容易地迭代存储在容器中的所有项目。
Qt容器类分为顺序容器和关联容器。
顺序容器:QList,QLinkedList,QVector,QStack和QQueue。
关联容器:QMap,QMultiMap,QHash,QMultiHash和QSet。
类 | 摘要 |
---|---|
QList |
这是迄今为止最常用的容器类。它存储可以通过索引访问的给定类型(T)的值列表。在内部,QList使用数组实现,确保基于索引的访问非常快。 可以使用QList :: append()和QList :: prepend()在列表的任一端添加项,也可以使用QList :: insert()将它们插入到中间。QList比任何其他容器类都高度优化,可以在可执行文件中尽可能少地扩展代码。QStringList继承自QList < QString >。 |
QLinkedList |
这类似于QList,除了它使用迭代器而不是整数索引来访问项目。当在大型列表中插入时,它还提供比QList更好的性能,并且它具有更好的迭代器语义。(只要项目存在,指向QLinkedList中项目的迭代器仍然有效,而QList的迭代器在插入或删除后可能变为无效。) |
QVector |
这将给定类型的值数组存储在存储器中的相邻位置。在向量的前面或中间插入可能非常慢,因为它可能导致大量项目必须在内存中移动一个位置。 |
QStack |
这是QVector的一个便利子类,它提供“后进先出”(LIFO)语义。它将以下函数添加到QVector中已存在的函数:push(),pop()和top()。 |
QQueue |
这是QList的一个便利子类,它提供“先进先出”(FIFO)语义。它将以下函数添加到QList中已存在的函数:enqueue(),dequeue()和head()。 |
QSet |
这提供了具有快速查找的单值数学集。 |
QMap |
这提供了一个字典(关联数组),它将Key类型的键映射到类型T的值。通常,每个键都与一个值相关联。QMap按Key顺序存储数据; 如果订单无关紧要QHash是一个更快的选择。 |
QMultiMap |
这是QMap的一个便利子类,它为多值映射提供了一个很好的接口,即一个键可以与多个值相关联的映射。 |
QHash |
它具有与QMap几乎相同的API ,但提供了明显更快的查找。QHash以任意顺序存储其数据。 |
QMultiHash |
这是QHash的一个便利子类,它为多值哈希提供了一个很好的接口。 |
容器嵌套
可以使用QMap < QString,QList
>,其中键类型为QString,值类型为QList
容器头文件
要使用容器类需要添加相应的头文件,格式为#include <容器类名>
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
Qt容器中可以存储值的类型包括C++基本类型和Qt特有的数据类型。C++基本类型包括int、double、指针类型等,而Qt特有的数据类型包括QString、QDate、QTime等。不包括QObject或任何QObject子类(QWidget,QDialog,QTimer等),如果要将这些类型的对象存储在容器中,可以将他们存储为指针,例如,QList < QWidget*>。
如果要访问Qt容器中存储的项目,可以使用迭代器。Qt容器类提供两种类型的迭代器:Java样式迭代器和STL样式迭代器。
Java风格的迭代器
Java风格的迭代器是Qt 4中的新增功能,是Qt应用程序中使用的标准迭代器。Java风格的迭代器比STL样式的迭代器更方便使用,但效率稍低,因为它们的API是基于Java的迭代器类创建的。
对于每个容器类,都有两种Java样式的迭代器:一种提供只读访问,另一种提供读写访问。
容器类 | 只读迭代器 | 读写迭代器 |
---|---|---|
QList |
QListIterator |
QMutableListIterator |
QLinkedList |
QLinkedListIterator |
QMutableLinkedListIterator |
QVector |
QVectorIterator |
QMutableVectorIterator |
QSet |
QSetIterator |
QMutableSetIterator |
QMap |
QMapIterator |
QMutableMapIterator |
QHash |
QHashIterator |
QMutableHashIterator |
与STL风格的迭代器相比,Java风格迭代器的指向不是元素,而是元素之间。因此,它们要么指向容器的最开头(在第一个项目之前),要么指向容器的最末端(在最后一个项目之后),要么指向两个项目之间。下图显示了包含四个项目的列表的有效迭代器位置为红色箭头:
下面用一个示例来展示这种风格的迭代器的使用,先按顺序迭代QList < QString >中的所有元素并将它们打印到控制台:
QList list;
list << "A" << "B" << "C" << "D";
QListIterator i(list);
while (i.hasNext())
{
qDebug() << i.next();
}
打印结果:
以下是如何将QList中的元素由后往前迭代:
QList list;
list << "A" << "B" << "C" << "D";
QListIterator i(list);
i.toBack();
while (i.hasPrevious())
{
qDebug() << i.previous();
}
打印结果:
下表总结了QListIterator API:
函数 | 功能 |
---|---|
toFront() | 将迭代器移动到列表的最前面(在第一个项目之前) |
toBack() | 将迭代器移动到列表的最后面(在最后一项之后) |
hasNext() | 返回true 如果迭代器是不是在名单的后面 |
next() | 返回下一个元素并将迭代器向前移动一个位置 |
peekNext() | 返回下一个元素而不移动迭代器 |
hasPrevious() | true 如果迭代器不在列表的前面,则返回 |
previous() | 返回前一项并将迭代器向后移动一个位置 |
peekPrevious() | 返回前一项而不移动迭代器 |
在迭代时,QListIterator不提供在列表中插入或删除项目的功能。要完成此任务,您必须使用QMutableListIterator。这是一个使用QMutableListIterator从QList
中删除所有奇数的示例:
QList list;
list << 1 << 2 << 3 << 4;
QMutableListIterator i(list);
while (i.hasNext())
{
if (i.next() % 2 != 0)
{
i.remove();
}
qDebug()<
打印结果:
在向后迭代时,这也可以正常工作:
QList list;
list << 1 << 2 << 3 << 4;
QMutableListIterator i(list);
i.toBack();
while (i.hasPrevious())
{
if (i.previous() % 2 != 0)
{
i.remove();
}
qDebug()<
打印结果:
如果我们只想修改现有项的值,我们可以使用setValue(),在下面的代码中,我们用128替换任何大于128的值:
QList list;
list << 1 << 2 << 3 << 4;
QMutableListIterator i(list);
while (i.hasNext())
{
if (i.next() > 128)
{
i.setValue(128);
}
qDebug()<
打印结果:
就像remove()一样,setValue()对我们跳过的最后一项进行操作。如果我们向前迭代,这就是迭代器之前的项目; 如果我们向后迭代,这就是迭代器之后的项目。
的下一个()函数返回在列表中的非const引用的项目。对于简单的操作,我们甚至不需要setValue():
QMutableListIterator i(list);
while (i.hasNext())
i.next() *= 2;
如上所述,QLinkedList,QVector和QSet的迭代器类具有与QList完全相同的API 。我们现在转向QMapIterator,它有点不同,因为它迭代(键,值)对。
与QListIterator一样,QMapIterator提供toFront(),toBack(),hasNext(),next(),peekNext(),hasPrevious(),previous()和peekPrevious()。通过调用next(),peekNext(),previous()或peekPrevious()返回的对象上的key()和value()来提取键和值组件。
以下示例删除首都名称以“City”结尾的所有(大写,国家/地区)对:
QMap map;
map.insert("Paris", "France");
map.insert("Guatemala City", "Guatemala");
map.insert("Mexico City", "Mexico");
map.insert("Moscow", "Russia");
...
QMutableMapIterator i(map);
while (i.hasNext()) {
if (i.next().key().endsWith("City"))
i.remove();
}
QMapIterator还提供了一个key()和一个value()函数,它直接在迭代器上运行,并返回迭代器跳到上面的最后一项的键和值。例如,以下代码将QMap的内容复制到QHash中:
QMap map;
QHash hash;
QMapIterator i(map);
while (i.hasNext()) {
i.next();
hash.insert(i.key(), i.value());
}
如果我们想要迭代具有相同值的所有项,我们可以使用findNext()或findPrevious()。这是一个示例,我们删除具有特定值的所有项目:
QMutableMapIterator i(map);
while (i.findNext(widget))
i.remove();
自Qt 2.0发布以来,STL样式的迭代器已经可用。它们与Qt和STL的通用算法兼容,并针对速度进行了优化。
对于每个容器类,有两种STL样式的迭代器类型:一种提供只读访问,另一种提供读写访问。应尽可能使用只读迭代器,因为它们比读写迭代器更快。
集装箱 | 只读迭代器 | 读写迭代器 |
---|---|---|
QList |
QList |
QList |
QLinkedList |
QLinkedList |
QLinkedList |
QVector |
QVector |
QVector |
QSet |
QSet |
QSet |
QMap |
QMap |
QMap |
QHash |
QHash |
QHash |
STL迭代器的API以数组中的指针为模型。例如,++
运算符将迭代器前进到下一个项,*
操作符返回迭代器指向的项。实际上,对于将它们的项存储在相邻内存位置的QVector和QStack,迭代器类型只是一个typedef T *
,而const_iterator类型只是一个typedef const T *
。
在本次讨论中,我们将专注于QList和QMap。QLinkedList,QVector和QSet的迭代器类型与QList的迭代器具有完全相同的接口; 类似地,QHash的迭代器类型与QMap的迭代器具有相同的接口。
这是一个典型的循环,用于按顺序迭代QList < QString >的所有元素并将它们转换为小写:
QList list;
list << "A" << "B" << "C" << "D";
QList::iterator i;
for (i = list.begin(); i != list.end(); ++i)
*i = (*i).toLower();
与Java风格的迭代器不同,STL样式的迭代器直接指向项目。容器的begin()函数返回指向容器中第一个项的迭代器。容器的end()函数将迭代器返回到假想项目的一个位置,超过容器中的最后一个项目。end()标记无效的位置; 它绝不能被取消引用。它通常用于循环中断条件。如果列表为空,则begin()等于end(),因此我们永远不会执行循环。
下图显示了包含四个项的向量的有效迭代器位置为红色箭头:
使用反向迭代器完成使用STL样式迭代器向后迭代:
QList list;
list << "A" << "B" << "C" << "D";
QList::reverse_iterator i;
for (i = list.rbegin(); i != list.rend(); ++i)
*i = i->toLower();
}
在目前为止的代码片段中,我们使用一元运算*
符来检索存储在某个迭代器位置的项(类型为QString),然后我们在其上调用QString :: toLower()。大多数C ++编译器也允许我们编写i->toLower()
,但有些则不允许。
对于只读访问,可以使用const_iterator,constBegin()和constEnd()。例如:
QList::const_iterator i;
for (i = list.constBegin(); i != list.constEnd(); ++i)
qDebug() << *i;
下表总结了STL样式迭代器的API:
表达 | 行为 |
---|---|
*i |
返回当前项 |
++i |
将迭代器推进到下一个项目 |
i += n |
按n 项目推进迭代器 |
--i |
将迭代器向后移动一个项目 |
i -= n |
按n 项目向后移动迭代器 |
i - j |
返回项目的迭代器之间的数量i 和j |
在++
和--
运营商都可以既作为前缀(++i
,--i
)和后缀(i++
,i--
)运算符。前缀版本修改迭代器并返回对修改后的迭代器的引用; postfix版本在修改它之前会获取迭代器的副本,并返回该副本。在忽略返回值的表达式中,我们建议您使用前缀运算符(++i
,--i
),因为它们稍微快一些。
对于非const迭代器类型,*
可以在赋值运算符的左侧使用一元运算符的返回值。
对于QMap和QHash,*
运算符返回项的值组件。如果要检索密钥,请在迭代器上调用key()。对于对称性,迭代器类型还提供value()函数来检索值。例如,以下是我们如何将QMap中的所有项目打印到控制台:
QMap map;
...
QMap::const_iterator i;
for (i = map.constBegin(); i != map.constEnd(); ++i)
qDebug() << i.key() << ':' << i.value();
由于隐式共享,函数返回每个值的容器非常便宜。Qt API包含许多函数,每个值返回一个QList或QStringList(例如,QSplitter :: sizes())。如果要使用STL迭代器迭代这些,则应始终获取容器的副本并遍历副本。例如:
// RIGHT
const QList sizes = splitter->sizes();
QList::const_iterator i;
for (i = sizes.begin(); i != sizes.end(); ++i)
...
// WRONG
QList::const_iterator i;
for (i = splitter->sizes().begin();
i != splitter->sizes().end(); ++i)
...
对于向容器返回const或非const引用的函数不会发生此问题。
隐式共享迭代器问题
隐式共享对STL样式的迭代器有另一个影响:当迭代器在该容器上处于活动状态时,应避免复制容器。迭代器指向内部结构,如果复制容器,则应该非常小心迭代器。例如:
QVector a, b;
a.resize(100000); // make a big vector filled with 0.
QVector::iterator i = a.begin();
// WRONG way of using the iterator i:
b = a;
/*
Now we should be careful with iterator i since it will point to shared data
If we do *i = 4 then we would change the shared instance (both vectors)
The behavior differs from STL containers. Avoid doing such things in Qt.
*/
a[0] = 5;
/*
Container a is now detached from the shared data,
and even though i was an iterator from the container a, it now works as an iterator in b.
Here the situation is that (*i) == 0.
*/
b.clear(); // Now the iterator i is completely invalid.
int j = *i; // Undefined behavior!
/*
The data from b (which i pointed to) is gone.
This would be well-defined with STL containers (and (*i) == 5),
but with QVector this is likely to crash.
*/
上面的示例仅显示了QVector的问题,但所有隐式共享的Qt容器都存在问题。
如果您只想按顺序迭代容器中的所有项目,可以使用Qt的foreach
关键字。该关键字是对C ++语言的Qt特定添加,并使用预处理器实现。
它的语法是:foreach
(变量,容器)声明。例如,以下是如何使用foreach
迭代QLinkedList < QString >:
QLinkedList list;
...
QString str;
foreach (str, list)
qDebug() << str;
该foreach
代码是比使用迭代器的等效代码显著短:
QLinkedList list;
...
QLinkedListIterator i(list);
while (i.hasNext())
qDebug() << i.next();
除非数据类型包含逗号(例如QPair
),否则用于迭代的变量可以在foreach
语句中定义:
QLinkedList list;
...
foreach (const QString &str, list)
qDebug() << str;
和任何其他C ++循环结构一样,你可以在foreach
循环体周围使用大括号,你可以break
用来离开循环:
QLinkedList list;
...
foreach (const QString &str, list) {
if (str.isEmpty())
break;
qDebug() << str;
}
使用QMap和QHash,自动foreach
访问(键,值)对的值组件,因此不应在容器上调用values()(它会生成不必要的副本,请参见下文)。如果要迭代键和值,可以使用迭代器(更快),或者可以获取键,并使用它们来获取值:
QMap map;
...
foreach (const QString &str, map.keys())
qDebug() << str << ':' << map.value(str);
对于多值地图:
QMultiMap map;
...
foreach (const QString &str, map.uniqueKeys()) {
foreach (int i, map.values(str))
qDebug() << str << ':' << i;
}
Qt在进入foreach
循环时自动获取容器的副本。如果在迭代时修改容器,则不会影响循环。(如果不修改容器,副本仍然会发生,但由于隐式共享复制,容器非常快。)
由于foreach创建了容器的副本,因此对变量使用非const引用不允许您修改原始容器。它只影响副本,这可能不是你想要的。
Qt foreach
循环的替代方案是基于范围的for
,它是C ++ 11及更新版本的一部分。但是,请记住,基于范围for
可能会强制Qt容器分离,而foreach
不会。但是使用foreach
始终复制容器,这对于STL容器通常并不便宜。如果有疑问,更喜欢foreach
Qt容器,而范围基于for
STL 容器。
除此之外foreach
,Qt还forever
为无限循环提供了一个伪关键
forever {
...
}
如果您担心命名空间污染,可以通过在.pro
文件中添加以下行来禁用这些宏:
CONFIG + = no_keywords
Qt包括三个模板类,在某些方面类似于容器。这些类不提供迭代器,不能与foreach
关键字一起使用。
与Qt的模板容器竞争的其他非模板类型是QBitArray,QByteArray,QString和QStringList。
算法复杂性关注的是每个函数的快速(或慢)随着容器中项目的数量的增长而增加。例如,在一中间插入一个项QLinkedList是一个极其快速的操作,而不管存储在所述项的数目的QLinkedList。在另一方面,在中间插入一个项目QVector可能非常昂贵,如果QVector包含很多项目,由于项目的一半必须移动内存中的一个位置。
为了描述算法的复杂性,我们使用以下术语,基于“大哦”符号:
下表总结了Qt顺序容器类的算法复杂性:
索引查找 | 插入 | 预谋 | 追加 | |
---|---|---|---|---|
QLinkedList |
O(n) | O(1) | O(1) | O(1) |
QList |
O(1) | 上) | 垂头丧气地[526。O(1) | 垂头丧气地[526。O(1) |
QVector |
O(1) | 上) | 上) | 垂头丧气地[526。O(1) |
在表格中,“Amort。” 代表“摊销行为”。例如,“Amort.O(1)”表示如果仅调用该函数一次,则可能会获得O(n)行为,但如果您多次调用它(例如,n次),则平均行为将为O. (1)。
下表总结了Qt关联容器和集的算法复杂性:
密钥查找 | 插入 | |||
---|---|---|---|---|
平均 | 最糟糕的情况 | 平均 | 最糟糕的情况 | |
QMap |
O(log n) | O(log n) | O(log n) | O(log n) |
QMultiMap |
O(log n) | O(log n) | O(log n) | O(log n) |
QHash |
垂头丧气地[526。O(1) | O(n) | 垂头丧气地[526。O(1) | O(n) |
QSet |
垂头丧气地[526。O(1) | O(n) | 垂头丧气地[526。O(1) | O(n) |
使用QVector,QHash和QSet,附加项目的性能按摊销O(log n)。在插入项目之前,通过调用QVector :: reserve(),QHash :: reserve()或QSet :: reserve()以及预期的项目数,可以将其降低到O(1)。下一节将更深入地讨论该主题。