概述
Qt
库提供了一组通用的基于模板的容器类。这些类可用于存储指定类型的项。例如,如果需要一个可调整大小的QString
数组,可以使用QVector<QString>
。
这些容器类被设计成比STL
容器更轻、更安全、更易于使用。如果不熟悉STL
,或者更喜欢用Qt方式
进行变成,那么就可以使用这些类来代替STL类
。
容器类是隐式共享的,它们是可重入的,并且它们针对速度、低内存消耗和最小的内联代码扩展进行了优化,从而产生更小的可执行文件。此外,当所有访问它们的线程都将它们用作只读容器时,它们是线程安全的。
要遍历存储在容器中的项,可以使用两种类型的迭代器之一:java风格的迭代器
和stl风格的迭代器
。java风格的迭代器
更容易使用并提供高级功能,而STL风格的迭代器
稍微更有效,可以与Qt
和STL
的泛型算法一起使用。
Qt
还提供了foreach
关键字,它可以很容易地遍历容器中存储的所有项。
Qt容器类介绍
Qt
提供了以下顺序容器:QList,
QLinkedList
, QVector
, QStack
和QQueue
。对于大多数应用程序,QList
是最好的类型。虽然它是作为数组列表实现的,但它提供了非常快的前置和追加。如果需要一个链表,使用QLinkedList
;如果想让项目占用连续的内存位置,使用QVector
。QStack
和QQueue
是提供后进先出
和先进先出
语义的方便类。
Qt
还提供了这些关联容器:QMap
、QMultiMap
、QHash
、QMultiHash
和QSet
。“Multi”
容器方便地支持与单个键关联的多个值。“哈希”
容器通过使用哈希函数而不是对排序集进行二进制搜索来提供更快的查找。
作为特殊情况,QCache
和qconsecuousscache
类在有限的缓存存储中提供了有效的对象哈希查找。
以下是常用容器类:
类 | 介绍 |
---|---|
QList | 这是目前为止最常用的容器类。它存储可以通过索引访问的给定类型(T)的值列表。在内部,QList使用数组实现,确保基于索引的访问非常快。 |
QLinkedList | 可以使用QList::append()和QList::prepend()在列表的两端添加项,也可以使用QList::insert()在中间插入项。与任何其他容器类不同,QList经过了高度优化,可以在可执行文件中扩展到尽可能少的代码。QStringList继承自QList。这类似于QList,不同之处在于它使用迭代器而不是整数索引来访问项。在大列表中插入时,它还提供了比QList更好的性能,并且具有更好的迭代器语义。(只要QLinkedList中的元素存在,指向该元素的迭代器就保持有效,而指向QList的迭代器在插入或删除后就会失效。) |
QVector | 这将给定类型的值数组存储在内存中的相邻位置。在vector的前面或中间插入可能会非常慢,因为它可能导致大量项必须在内存中移动一个位置。 |
QStack | 这是QVector的一个方便子类,提供“后进先出”(LIFO)语义。它为QVector中已经存在的函数添加了以下函数:push()、pop()和top()。 |
QQueue | 这是QList的一个方便子类,提供“先进先出”(FIFO)语义。它向QList中已经存在的函数添加了以下函数:enqueue()、dequeue()和head()。 |
QSet | 这提供了一个具有快速查找功能的单值数学集。 |
QMap<Key, T> | 它提供了一个字典(关联数组),将Key类型的键映射到t类型的值。通常每个键都与单个值相关联。QMap按Key顺序存储数据;如果顺序不重要,QHash是一个更快的选择。 |
QMultiMap<Key, T> | 这是QMap的一个方便的子类,它为多值映射提供了一个很好的接口,即一个键可以与多个值相关联的映射。 |
QHash<Key, T> | 它具有与QMap几乎相同的API,但提供了明显更快的查找。QHash以任意顺序存储数据。 |
QMultiHash<Key, T> | 这是QHash的一个方便的子类,它为多值哈希提供了一个很好的接口。 |
容器是可以嵌套。例如,完全可以使用QMap<QString, QList<int>>
,其中键类型是QString
,值类型是QList<int>
。
容器在单独的头文件中定义,其名称与容器相同(例如,<QLinkedList>
)。为方便起见,在<QtContainerFwd>
中向前声明了容器。
存储在各种容器中的值可以是任何可赋值的数据类型。要符合条件,类型必须提供默认构造函数、复制构造函数和赋值操作符。这涵盖了可能想要存储在容器中的大多数数据类型,包括基本类型,如int
和double
,指针类型,以及Qt
数据类型,如QString
, QDate
和QTime
,但它不包括QObject
或任何QObject
子类(QWidget
, QDialog
, QTimer
等)。如果尝试实例化QList<QWidget>
,编译器将报错QWidget
的复制构造函数和赋值操作符被禁用。如果希望将这些类型的对象存储在容器中,请将它们存储为指针,例如QList<QWidget *>
。
下面是一个满足可赋值数据类型要求的自定义数据类型示例:
class Employee{public:Employee() {}Employee(const Employee &other);Employee &operator=(const Employee &other);private:QString myName;QDate myDateOfBirth;};
如果没有提供复制构造函数或赋值操作符,c++提供了执行逐个成员复制的默认实现。在上面的例子中,这就足够了。此外,如果不提供任何构造函数,c++还提供一个默认构造函数,该构造函数使用默认构造函数初始化其成员。尽管它没有提供任何显式构造函数或赋值操作符,但下列数据类型可以存储在容器中:
struct Movie{int id;QString title;QDate releaseDate;};
有些容器对它们可以存储的数据类型有额外的要求。例如,QMap<Key, T>
的Key
类型必须提供操作符<()
。这些特殊需求在类的详细描述中都有文档。在某些情况下,特定功能有特殊要求;这些是按功能描述的。如果不满足要求,编译器总是会发出一个错误。
Qt的容器提供了operator<<()
和operator>>()
,因此它们可以很容易地使用QDataStream
进行读写。这意味着存储在容器中的数据类型还必须支持operator<<()
和operator>>()
。提供这样的支持是直截了当的;下面是我们如何为上面的Movie结构体做到这一点:
QDataStream &operator<<(QDataStream &out, const Movie &movie){out << (quint32)movie.id << movie.title<< movie.releaseDate;return out;}QDataStream &operator>>(QDataStream &in, Movie &movie){quint32 id;QDate date;in >> id >> movie.title >> date;movie.id = (int)id;movie.releaseDate = date;return in;}
某些容器类函数的文档引用默认构造的值;例如,QVector
用默认构造的值自动初始化它的项,如果指定的键不在映射中,QMap::value()
返回一个默认构造的值。对于大多数值类型,这仅仅意味着使用默认构造函数创建值(例如,QString
的空字符串)。但是对于int
和double
这样的基本类型,以及指针类型,c++
语言没有指定任何初始化;在这种情况下,Qt
的容器会自动将该值初始化为0
。
示例
以下是使用QList
和QMap
进行介绍,其他几个容器可以参考这两个进行操作,因为他们的API
很相似。
QList
QList
是Qt中提供的一个容器类,它是一个动态数组,可以自动调整大小以容纳新元素
- append:在列表的末尾添加一个新元素。
QList<int> list;
list.append(1);
list.append(2);
list.append(3);
// list: {1, 2, 3}
- prepend:在列表的开头添加一个新元素。
QList<int> list;
list.prepend(1);
list.prepend(2);
list.prepend(3);
// list: {3, 2, 1}
- insert:在列表的指定位置插入一个新元素。
QList<int> list;
list << 1 << 2 << 4;
list.insert(2, 3);
// list: {1, 2, 3, 4}
- replace:用新元素替换列表中的一个元素。
QList<int> list;
list << 1 << 2 << 3;
list.replace(1, 4);
// list: {1, 4, 3}
- removeAt:从列表中删除指定索引处的元素。
QList<int> list;
list << 1 << 2 << 3;
list.removeAt(1);
// list: {1, 3}
- swap:交换列表中两个元素的位置。
QList<int> list;
list << 1 << 2 << 3;
list.swap(0, 2);
// list: {3, 2, 1}
- contains:检查列表中是否包含某个元素。
QList<int> list;
list << 1 << 2 << 3;
if (list.contains(2)) {qDebug() << "2 is in the list.";
}
- count:返回列表中某个元素的数量。
QList<int> list;
list << 1 << 2 << 2 << 3 << 2;
int count = list.count(2); // count is 3
- indexOf:返回列表中某个元素的第一个索引。
QList<int> list;
list << 1 << 2 << 2 << 3 << 2;
int index = list.indexOf(2); // index is 1
- lastIndexOf:返回列表中某个元素的最后一个索引。
QList<int> list;
list << 1 << 2 << 2 << 3 << 2;
int index = list.lastIndexOf(2); // index is 4
- empty:检查列表是否为空。
QList<int> list;
if (list.empty()) {qDebug() << "The list is empty.";
}
- size:返回列表的大小。
QList<int> list;
list << 1 << 2 << 3;
int size = list.size(); // size is 3
- clear:删除列表中的所有元素。
QList<int> list;
list << 1 << 2 << 3;
list.clear();
// list is now empty
对于只读的访问,一般使用at()
函数,因为比"[ ]"
操作符快很多。
QMap
QMap
是Qt中提供的一个容器类,它是一个关联数组,可以将键值对存储在一起.
- insert:在映射中插入一个键值对。
QMap<QString, int> map;
map.insert("Bob", 25);
map.insert("Alice", 30);
map.insert("Charlie", 35);
// map: { "Alice": 30, "Bob": 25, "Charlie": 35 }
- replace:用新值替换映射中的一个值。
QMap<QString, int> map;
map.insert("Bob", 25);
map.insert("Alice", 30);
map.insert("Charlie", 35);
map.replace("Bob", 20);
// map: { "Alice": 30, "Bob": 20, "Charlie": 35 }
- remove:从映射中删除指定键的值。
QMap<QString, int> map;
map.insert("Bob", 25);
map.insert("Alice", 30);
map.insert("Charlie", 35);
map.remove("Bob");
// map: { "Alice": 30, "Charlie": 35 }
- swap:交换两个映射的内容。
QMap<QString, int> map1, map2;
map1.insert("Bob", 25);
map1.insert("Alice", 30);
map2.insert("Charlie", 35);
map2.insert("Dave", 40);
map1.swap(map2);
// map1: { "Charlie": 35, "Dave": 40 }
// map2: { "Alice": 30, "Bob": 25 }
- contains:检查映射中是否包含指定键。
QMap<QString, int> map;
map.insert("Bob", 25);
map.insert("Alice", 30);
if (map.contains("Bob")) {qDebug() << "Bob is in the map.";
}
- count:返回映射中指定键的数量。
QMap<QString, int> map;
map.insert("Bob", 25);
map.insert("Alice", 30);
map.insert("Charlie", 35);
int count = map.count("Bob"); // count is 1
- value:返回映射中指定键的值,如果键不存在则返回默认值。
QMap<QString, int> map;
map.insert("Bob", 25);
map.insert("Alice", 30);
int value = map.value("Charlie", 0); // value is 0
- keys:返回映射中所有的键。
QMap<QString, int> map;
map.insert("Bob", 25);
map.insert("Alice", 30);
QList<QString> keys = map.keys(); // keys: { "Bob", "Alice" }
- values:返回映射中所有的值。
QMap<QString, int> map;
map.insert("Bob", 25);
map.insert("Alice", 30);
QList<int> values = map.values(); // values: { 25, 30 }
- empty:检查映射是否为空。
QMap<QString, int> map;
if (map.empty()) {qDebug() << "The map is empty.";
}
- size:返回映射中键值对的数量。
QMap<QString, int> map;
map.insert("Bob", 25);
map.insert("Alice", 30);
int size = map.size(); // size is 2
- clear:删除映射中的所有键值对。
QMap<QString, int> map;
map.insert("Bob", 25);
map.insert("Alice", 30);
map.clear();
// map is now empty
遍历容器
要遍历存储在容器中的项,可以使用两种类型的迭代器之一:java风格的迭代器
和stl风格的迭代器
。java风格的迭代器
更容易使用并提供高级功能,而STL风格的迭代器
稍微更有效,可以与Qt
和STL
的泛型算法一起使用。
Qt
还提供了foreach
关键字,它可以很容易地遍历容器中存储的所有项。
在本文档中,将集中讨论QList和QMap。QLinkedList、QVector和QSet的迭代器类型与QList的迭代器具有完全相同的接口;类似地,QHash的迭代器类型与QMap的迭代器具有相同的接口。
与stl风格的迭代器(将在下面介绍)不同,java风格的迭代器指向项目之间,而不是直接指向项目。由于这个原因,它们要么指向容器的最开始(在第一个项目之前),要么指向容器的最末尾(在最后一个项目之后),要么指向两个项目之间。下图用红色箭头显示了包含四个元素的列表的有效迭代器位置:
下面,按顺序遍历QList中的所有元素,并将它们打印到控制台:
QList<QString> list;list << "A" << "B" << "C" << "D";QListIterator<QString> i(list);while (i.hasNext())qDebug() << i.next();
它的工作原理如下:将要迭代的QList传递给QListIterator
构造函数。此时,迭代器位于列表中第一项的前面(在项“A”之前)。然后调用hasNext()
来检查迭代器后是否有项。如果有,则调用next()跳过该项。next()函数返回它跳过的项。对于QList,该项的类型为QString。
下面是如何在QList中向后迭代:
QListIterator<QString> i(list);i.toBack();while (i.hasPrevious())qDebug() << i.previous();
代码与向前迭代是对称的,除了我们首先调用toBack()将迭代器移动到列表中的最后一项之后。
下图说明了在迭代器上调用next()和previous()的效果:
QListIterator
常用API:
方法 | 行为 |
---|---|
toFront() | 将迭代器移动到列表的前面(在第一项之前) |
toBack() | 将迭代器移动到列表的后面(在最后一项之后) |
hasNext() | 如果迭代器不在列表的末尾,则返回true |
next() | 返回下一项并将迭代器向前移动一个位置 |
peekNext() | 不移动迭代器返回下一项 |
hasPrevious() | 如果迭代器不在列表的前面,则返回true |
previous() | 返回前一项并将迭代器向后移动一个位置 |
peekPrevious() | 不移动迭代器返回前一项 |
QListIterator
不提供在迭代时从列表中插入或删除项的函数。要做到这一点,必须使用QMutableListIterator
。
下面是一个使用QMutableListIterator
从QList<int>
中删除所有奇数的例子:
QMutableListIterator<int> i(list);while (i.hasNext()) {if (i.next() % 2 != 0)i.remove();}
循环中的next()调用每次都进行。它跳过列表中的下一项。remove()函数删除了我们从列表中跳过的最后一项。调用remove()不会使迭代器失效,所以继续使用它是安全的。这在向后迭代时同样有效:
QMutableListIterator<int> i(list);i.toBack();while (i.hasPrevious()) {if (i.previous() % 2 != 0)i.remove();}
就像remove()一样,setValue()对我们跳过的最后一项进行操作。如果向前迭代,这是迭代器前面的项;如果向后迭代,这是迭代器后面的项。
next()函数返回对列表中项目的非const引用。对于简单的操作,我们甚至不需要setValue():
QMutableListIterator<int> 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"结尾的(capital, country)对:
QMap<QString, QString> map;map.insert("Paris", "France");map.insert("Guatemala City", "Guatemala");map.insert("Mexico City", "Mexico");map.insert("Moscow", "Russia");...QMutableMapIterator<QString, QString> i(map);while (i.hasNext()) {if (i.next().key().endsWith("City"))i.remove();}
QMapIterator还提供了一个key()和一个value()函数,它们直接对迭代器进行操作,并返回迭代器跳到上面的最后一项的键和值。例如,下面的代码将QMap的内容复制到QHash中:
QMap<int, QWidget *> map;QHash<int, QWidget *> hash;QMapIterator<int, QWidget *> i(map);while (i.hasNext()) {i.next();hash.insert(i.key(), i.value());}
如果要遍历具有相同值的所有项,可以使用findNext()或findPrevious()。下面是一个例子,删除所有具有特定值的项:
QMutableMapIterator<int, QWidget *> i(map);while (i.findNext(widget))i.remove();
结论
想改变物质生活,后来发现,改变心态更容易
。