Qt 库提供一组基于模板的一般目的容器类。这些类可以用于存储指定类型项。例如,若需要可重置尺寸数组的 QString ,使用 QVector < QString >.
这些容器类被设计得比 STL 容器更轻、更安全且更易于使用。若不熟悉 STL,或偏好以 Qt 方式做事情,可以使用这些类而不是 STL 类。
容器类 隐式共享 ,它们 可重入 ,且它们有优化针对速度、低内存消耗及最小内联代码扩展,结果是更小可执行文件。此外,它们是 thread-safe 在所有用于访问它们的线程将其用作只读容器的状况下。
为遍历容器存储项,可以使用 2 种类型迭代器之一: Java 风格迭代器 and STL 样式迭代器 。Java 样式迭代器更易于使用且提供高级功能,而 STL 样式迭代器稍微更高效,可以一起使用同 Qt 和 STL 的 一般算法 .
Qt 还提供 foreach 关键字,使之非常易于遍历容器中的所有存储项。
Qt 提供以下顺序容器: QList , QLinkedList , QVector , QStack ,和 QQueue 。对于大多数应用程序, QList is the best type to use. Although it is implemented as an array-list, it provides very fast prepends and appends. If you really need a linked-list, use QLinkedList ; if you want your items to occupy consecutive memory locations, use QVector . QStack and QQueue 是提供 LIFO (后进先出) 和 FIFO (先进先出) 语义的方便类。
Qt 还提供这些关联容器: QMap , QMultiMap , QHash , QMultiHash ,和 QSet . The "Multi" containers conveniently support multiple values associated with a single key. The "Hash" containers provide faster lookup by using a hash function instead of a binary search on a sorted set.
作为特殊情况, QCache and QContiguousCache 类以有限缓存存储,为对象提供高效哈希查找。
类 | 摘要 |
---|---|
QList <T> |
This is by far the most commonly used container class. It stores a list of values of a given type (T) that can be accessed by index. Internally, the
QList
is implemented using an array, ensuring that index-based access is very fast.
Items can be added at either end of the list using QList::append () 和 QList::prepend (), or they can be inserted in the middle using QList::insert (). More than any other container class, QList is highly optimized to expand to as little code as possible in the executable. QStringList 继承自 QList < QString >. |
QLinkedList <T> | 这类似于 QList , except that it uses iterators rather than integer indexes to access items. It also provides better performance than QList when inserting in the middle of a huge list, and it has nicer iterator semantics. (Iterators pointing to an item in a QLinkedList remain valid as long as the item exists, whereas iterators to a QList can become invalid after any insertion or removal.) |
QVector <T> | This stores an array of values of a given type at adjacent positions in memory. Inserting at the front or in the middle of a vector can be quite slow, because it can lead to large numbers of items having to be moved by one position in memory. |
QStack <T> | 这是方便子类化的 QVector that provides "last in, first out" (LIFO) semantics. It adds the following functions to those already present in QVector : push() , pop() ,和 top() . |
QQueue <T> | 这是方便子类化的 QList that provides "first in, first out" (FIFO) semantics. It adds the following functions to those already present in QList : enqueue() , dequeue() ,和 head() . |
QSet <T> | 这提供带有快速查找的单值数学集。 |
QMap <Key, T> | This provides a dictionary (associative array) that maps keys of type Key to values of type T. Normally each key is associated with a single value. QMap stores its data in Key order; if order doesn't matter QHash is a faster alternative. |
QMultiMap <Key, T> | 这是方便子类化的 QMap that provides a nice interface for multi-valued maps, i.e. maps where one key can be associated with multiple values. |
QHash <Key, T> | This has almost the same API as QMap , but provides significantly faster lookups. QHash stores its data in an arbitrary order. |
QMultiHash <Key, T> | 这是方便子类化的 QHash that provides a nice interface for multi-valued hashes. |
容器可以嵌套。例如,完全可能使用 QMap < QString , QList <int>>,其中键类型为 QString 和值类型 QList <int>.
The containers are defined in individual header files with the same name as the container (e.g.,
<QLinkedList>
)。 为方便起见,容器的向前声明在
<QtContainerFwd>
.
The values stored in the various containers can be of any
可赋值数据类型
. To qualify, a type must provide a default constructor, a copy constructor, and an assignment operator. This covers most data types you are likely to want to store in a container, including basic types such as
int
and
double
, pointer types, and Qt data types such as
QString
,
QDate
,和
QTime
, but it doesn't cover
QObject
或任何
QObject
子类 (
QWidget
,
QDialog
,
QTimer
, etc.). If you attempt to instantiate a
QList
<
QWidget
>, the compiler will complain that
QWidget
's copy constructor and assignment operators are disabled. If you want to store these kinds of objects in a container, store them as pointers, for example as
QList
<
QWidget
*>.
Here's an example custom data type that meets the requirement of an assignable data type:
class Employee { public: Employee() {} Employee(const Employee &other); Employee &operator=(const Employee &other); private: QString myName; QDate myDateOfBirth; };
If we don't provide a copy constructor or an assignment operator, C++ provides a default implementation that performs a member-by-member copy. In the example above, that would have been sufficient. Also, if you don't provide any constructors, C++ provides a default constructor that initializes its member using default constructors. Although it doesn't provide any explicit constructors or assignment operator, the following data type can be stored in a container:
struct Movie { int id; QString title; QDate releaseDate; };
Some containers have additional requirements for the data types they can store. For example, the Key type of a
QMap
<Key, T> 必须提供
operator<()
. Such special requirements are documented in a class's detailed description. In some cases, specific functions have special requirements; these are described on a per-function basis. The compiler will always emit an error if a requirement isn't met.
Qt's containers provide operator<<() and operator>>() so that they can easily be read and written using a QDataStream . This means that the data types stored in the container must also support operator<<() and operator>>(). Providing such support is straightforward; here's how we could do it for the Movie struct above:
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; }
The documentation of certain container class functions refer to
default-constructed values
; for example,
QVector
automatically initializes its items with default-constructed values, and
QMap::value
() returns a default-constructed value if the specified key isn't in the map. For most value types, this simply means that a value is created using the default constructor (e.g. an empty string for
QString
). But for primitive types like
int
and
double
, as well as for pointer types, the C++ language doesn't specify any initialization; in those cases, Qt's containers automatically initialize the value to 0.
Iterators provide a uniform means to access items in a container. Qt's container classes provide two types of iterators: Java-style iterators and STL-style iterators. Iterators of both types are invalidated when the data in the container is modified or detached from 隐式共享副本 due to a call to a non-const member function.
The Java-style iterators are new in Qt 4 and are the standard ones used in Qt applications. They are more convenient to use than the STL-style iterators, at the price of being slightly less efficient. Their API is modelled on Java's iterator classes.
For each container class, there are two Java-style iterator data types: one that provides read-only access and one that provides read-write access.
容器 | 只读迭代器 | 读写迭代器 |
---|---|---|
QList <T>, QQueue <T> | QListIterator <T> | QMutableListIterator <T> |
QLinkedList <T> | QLinkedListIterator <T> | QMutableLinkedListIterator <T> |
QVector <T>, QStack <T> | QVectorIterator <T> | QMutableVectorIterator <T> |
QSet <T> | QSetIterator <T> | QMutableSetIterator <T> |
QMap <Key, T>, QMultiMap <Key, T> | QMapIterator <Key, T> | QMutableMapIterator <Key, T> |
QHash <Key, T>, QMultiHash <Key, T> | QHashIterator <Key, T> | QMutableHashIterator <Key, T> |
In this discussion, we will concentrate on QList and QMap . The iterator types for QLinkedList , QVector ,和 QSet have exactly the same interface as QList 's iterators; similarly, the iterator types for QHash have the same interface as QMap 's iterators.
Unlike STL-style iterators (covered below ), Java-style iterators point between items rather than directly at items. For this reason, they are either pointing to the very beginning of the container (before the first item), at the very end of the container (after the last item), or between two items. The diagram below shows the valid iterator positions as red arrows for a list containing four items:
Here's a typical loop for iterating through all the elements of a QList < QString > in order and printing them to the console:
QList<QString> list; list << "A" << "B" << "C" << "D"; QListIterator<QString> i(list); while (i.hasNext()) qDebug() << i.next();
It works as follows: The QList to iterate over is passed to the QListIterator constructor. At that point, the iterator is located just in front of the first item in the list (before item "A"). Then we call hasNext() to check whether there is an item after the iterator. If there is, we call next() to jump over that item. The next() function returns the item that it jumps over. For a QList < QString >, that item is of type QString .
Here's how to iterate backward in a QList :
QListIterator<QString> i(list); i.toBack(); while (i.hasPrevious()) qDebug() << i.previous();
The code is symmetric with iterating forward, except that we start by calling toBack() to move the iterator after the last item in the list.
The diagram below illustrates the effect of calling next() and previous() on an iterator:
The following table summarizes the QListIterator API:
函数 | 行为 |
---|---|
toFront() | Moves the iterator to the front of the list (before the first item) |
toBack() | Moves the iterator to the back of the list (after the last item) |
hasNext() |
返回
true
if the iterator isn't at the back of the list
|
next() | Returns the next item and advances the iterator by one position |
peekNext() | Returns the next item without moving the iterator |
hasPrevious() |
返回
true
if the iterator isn't at the front of the list
|
previous() | Returns the previous item and moves the iterator back by one position |
peekPrevious() | Returns the previous item without moving the iterator |
QListIterator provides no functions to insert or remove items from the list as we iterate. To accomplish this, you must use QMutableListIterator . Here's an example where we remove all odd numbers from a QList <int> using QMutableListIterator :
QMutableListIterator<int> i(list); while (i.hasNext()) { if (i.next() % 2 != 0) i.remove(); }
The next() call in the loop is made every time. It jumps over the next item in the list. The remove() function removes the last item that we jumped over from the list. The call to remove() does not invalidate the iterator, so it is safe to continue using it. This works just as well when iterating backward:
QMutableListIterator<int> i(list); i.toBack(); while (i.hasPrevious()) { if (i.previous() % 2 != 0) i.remove(); }
If we just want to modify the value of an existing item, we can use setValue() . In the code below, we replace any value larger than 128 with 128:
QMutableListIterator<int> i(list); while (i.hasNext()) { if (i.next() > 128) i.setValue(128); }
就像 remove() , setValue() operates on the last item that we jumped over. If we iterate forward, this is the item just before the iterator; if we iterate backward, this is the item just after the iterator.
The next() function returns a non-const reference to the item in the list. For simple operations, we don't even need setValue() :
QMutableListIterator<int> i(list); while (i.hasNext()) i.next() *= 2;
As mentioned above, QLinkedList 's, QVector 's, and QSet 's iterator classes have exactly the same API as QList 's. We will now turn to QMapIterator , which is somewhat different because it iterates on (key, value) pairs.
像 QListIterator , QMapIterator 提供 toFront() , toBack() , hasNext() , next() , peekNext() , hasPrevious() , previous() ,和 peekPrevious() . The key and value components are extracted by calling key() and value() on the object returned by next(), peekNext(), previous(), or peekPrevious().
The following example removes all (capital, country) pairs where the capital's name ends with "City":
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 also provides a key() 和 value() function that operate directly on the iterator and that return the key and value of the last item that the iterator jumped above. For example, the following code copies the contents of a 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()); }
If we want to iterate through all the items with the same value, we can use findNext() or findPrevious() . Here's an example where we remove all the items with a particular value:
QMutableMapIterator<int, QWidget *> i(map); while (i.findNext(widget)) i.remove();
STL-style iterators have been available since the release of Qt 2.0. They are compatible with Qt's and STL's 一般算法 and are optimized for speed.
For each container class, there are two STL-style iterator types: one that provides read-only access and one that provides read-write access. Read-only iterators should be used wherever possible because they are faster than read-write iterators.
容器 | 只读迭代器 | 读写迭代器 |
---|---|---|
QList <T>, QQueue <T> | QList <T>::const_iterator | QList <T>::iterator |
QLinkedList <T> | QLinkedList <T>::const_iterator | QLinkedList <T>::iterator |
QVector <T>, QStack <T> | QVector <T>::const_iterator | QVector <T>::iterator |
QSet <T> | QSet <T>::const_iterator | QSet <T>::iterator |
QMap <Key, T>, QMultiMap <Key, T> | QMap <Key, T>::const_iterator | QMap <Key, T>::iterator |
QHash <Key, T>, QMultiHash <Key, T> | QHash <Key, T>::const_iterator | QHash <Key, T>::iterator |
The API of the STL iterators is modelled on pointers in an array. For example, the
++
operator advances the iterator to the next item, and the
*
operator returns the item that the iterator points to. In fact, for
QVector
and
QStack
, which store their items at adjacent memory positions, the
iterator
type is just a typedef for
T *
,和
const_iterator
type is just a typedef for
const T *
.
In this discussion, we will concentrate on QList and QMap . The iterator types for QLinkedList , QVector ,和 QSet have exactly the same interface as QList 's iterators; similarly, the iterator types for QHash have the same interface as QMap 's iterators.
Here's a typical loop for iterating through all the elements of a QList < QString > in order and converting them to lowercase:
QList<QString> list; list << "A" << "B" << "C" << "D"; QList<QString>::iterator i; for (i = list.begin(); i != list.end(); ++i) *i = (*i).toLower();
不像 Java 风格迭代器 , STL-style iterators point directly at items. The begin() function of a container returns an iterator that points to the first item in the container. The end() function of a container returns an iterator to the imaginary item one position past the last item in the container. end() marks an invalid position; it must never be dereferenced. It is typically used in a loop's break condition. If the list is empty, begin() 等于 end() , so we never execute the loop.
The diagram below shows the valid iterator positions as red arrows for a vector containing four items:
Iterating backward with an STL-style iterator is done with reverse iterators:
QList<QString> list; list << "A" << "B" << "C" << "D"; QList<QString>::reverse_iterator i; for (i = list.rbegin(); i != list.rend(); ++i) *i = i->toLower(); }
In the code snippets so far, we used the unary
*
operator to retrieve the item (of type
QString
) stored at a certain iterator position, and we then called
QString::toLower
() on it. Most C++ compilers also allow us to write
i->toLower()
, but some don't.
For read-only access, you can use const_iterator, constBegin() ,和 constEnd() 。例如:
QList<QString>::const_iterator i; for (i = list.constBegin(); i != list.constEnd(); ++i) qDebug() << *i;
The following table summarizes the STL-style iterators' API:
表达式 | 行为 |
---|---|
*i
|
返回当前项 |
++i
|
将迭代器推进到下一项 |
i += n
|
Advances the iterator by
n
项
|
--i
|
Moves the iterator back by one item |
i -= n
|
Moves the iterator back by
n
项
|
i - j
|
Returns the number of items between iterators
i
and
j
|
The
++
and
--
operators are available both as prefix (
++i
,
--i
) and postfix (
i++
,
i--
) operators. The prefix versions modify the iterators and return a reference to the modified iterator; the postfix versions take a copy of the iterator before they modify it, and return that copy. In expressions where the return value is ignored, we recommend that you use the prefix operators (
++i
,
--i
), as these are slightly faster.
For non-const iterator types, the return value of the unary
*
operator can be used on the left side of the assignment operator.
For
QMap
and
QHash
,
*
operator returns the value component of an item. If you want to retrieve the key, call key() on the iterator. For symmetry, the iterator types also provide a value() function to retrieve the value. For example, here's how we would print all items in a
QMap
to the console:
QMap<int, int> map; ... QMap<int, int>::const_iterator i; for (i = map.constBegin(); i != map.constEnd(); ++i) qDebug() << i.key() << ':' << i.value();
Thanks to 隐式共享 , it is very inexpensive for a function to return a container per value. The Qt API contains dozens of functions that return a QList or QStringList per value (e.g., QSplitter::sizes ()). If you want to iterate over these using an STL iterator, you should always take a copy of the container and iterate over the copy. For example:
// RIGHT const QList<int> sizes = splitter->sizes(); QList<int>::const_iterator i; for (i = sizes.begin(); i != sizes.end(); ++i) ... // WRONG QList<int>::const_iterator i; for (i = splitter->sizes().begin(); i != splitter->sizes().end(); ++i) ...
This problem doesn't occur with functions that return a const or non-const reference to a container.
隐式共享 has another consequence on STL-style iterators: you should avoid copying a container while iterators are active on that container. The iterators point to an internal structure, and if you copy a container you should be very careful with your iterators. E.g:
QVector<int> a, b; a.resize(100000); // make a big vector filled with 0. QVector<int>::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. */
The above example only shows a problem with QVector , but the problem exists for all the implicitly shared Qt containers.
If you just want to iterate over all the items in a container in order, you can use Qt's
foreach
keyword. The keyword is a Qt-specific addition to the C++ language, and is implemented using the preprocessor.
Its syntax is:
foreach
(
variable
,
container
)
语句
. For example, here's how to use
foreach
to iterate over a
QLinkedList
<
QString
>:
QLinkedList<QString> list; ... QString str; foreach (str, list) qDebug() << str;
The
foreach
code is significantly shorter than the equivalent code that uses iterators:
QLinkedList<QString> list; ... QLinkedListIterator<QString> i(list); while (i.hasNext()) qDebug() << i.next();
Unless the data type contains a comma (e.g.,
QPair<int, int>
), the variable used for iteration can be defined within the
foreach
语句:
QLinkedList<QString> list; ... foreach (const QString &str, list) qDebug() << str;
And like any other C++ loop construct, you can use braces around the body of a
foreach
loop, and you can use
break
to leave the loop:
QLinkedList<QString> list; ... foreach (const QString &str, list) { if (str.isEmpty()) break; qDebug() << str; }
采用
QMap
and
QHash
,
foreach
accesses the value component of the (key, value) pairs automatically, so you should not call values() on the container (it would generate an unnecessary copy, see below). If you want to iterate over both the keys and the values, you can use iterators (which are faster), or you can obtain the keys, and use them to get the values too:
QMap<QString, int> map; ... foreach (const QString &str, map.keys()) qDebug() << str << ':' << map.value(str);
For a multi-valued map:
QMultiMap<QString, int> map; ... foreach (const QString &str, map.uniqueKeys()) { foreach (int i, map.values(str)) qDebug() << str << ':' << i; }
Qt automatically takes a copy of the container when it enters a
foreach
loop. If you modify the container as you are iterating, that won't affect the loop. (If you do not modify the container, the copy still takes place, but thanks to
隐式共享
copying a container is very fast.)
Since foreach creates a copy of the container, using a non-const reference for the variable does not allow you to modify the original container. It only affects the copy, which is probably not what you want.
An alternative to Qt's
foreach
loop is the range-based
for
that is part of C++ 11 and newer. However, keep in mind that the range-based
for
might force a Qt container to
detach
,而
foreach
would not. But using
foreach
always copies the container, which is usually not cheap for STL containers. If in doubt, prefer
foreach
for Qt containers, and range based
for
for STL ones.
除了
foreach
,Qt 还提供
forever
pseudo-keyword for infinite loops:
forever { ... }
If you're worried about namespace pollution, you can disable these macros by adding the following line to your
.pro
文件:
CONFIG += no_keywords
Qt includes three template classes that resemble containers in some respects. These classes don't provide iterators and cannot be used with the
foreach
关键词。
Additional non-template types that compete with Qt's template containers are QBitArray , QByteArray , QString ,和 QStringList .
Algorithmic complexity is concerned about how fast (or slow) each function is as the number of items in the container grow. For example, inserting an item in the middle of a QLinkedList is an extremely fast operation, irrespective of the number of items stored in the QLinkedList . On the other hand, inserting an item in the middle of a QVector is potentially very expensive if the QVector contains many items, since half of the items must be moved one position in memory.
To describe algorithmic complexity, we use the following terminology, based on the "big Oh" notation:
The following table summarizes the algorithmic complexity of Qt's sequential container classes:
索引查找 | 插入 | 前置 | 追加 | |
---|---|---|---|---|
QLinkedList <T> | O( n ) | O(1) | O(1) | O(1) |
QList <T> | O(1) | O(n) | Amort. O(1) | Amort. O(1) |
QVector <T> | O(1) | O(n) | O(n) | Amort. O(1) |
In the table, "Amort." stands for "amortized behavior". For example, "Amort. O(1)" means that if you call the function only once, you might get O( n ) behavior, but if you call it multiple times (e.g., n times), the average behavior will be O(1).
The following table summarizes the algorithmic complexity of Qt's associative containers and sets:
键查找 | 插入 | |||
---|---|---|---|---|
平均 | 最坏情况 | 平均 | 最坏情况 | |
QMap <Key, T> | O(log n ) | O(log n ) | O(log n ) | O(log n ) |
QMultiMap <Key, T> | O(log n ) | O(log n ) | O(log n ) | O(log n ) |
QHash <Key, T> | Amort. O(1) | O( n ) | Amort. O(1) | O( n ) |
QSet <Key> | Amort. O(1) | O( n ) | Amort. O(1) | O( n ) |
采用 QVector , QHash ,和 QSet , the performance of appending items is amortized O(log n ). It can be brought down to O(1) by calling QVector::reserve (), QHash::reserve (),或 QSet::reserve () with the expected number of items before you insert the items. The next section discusses this topic in more depth.
QVector <T>, QString ,和 QByteArray store their items contiguously in memory; QList <T> maintains an array of pointers to the items it stores to provide fast index-based access (unless T is a pointer type or a basic type of the size of a pointer, in which case the value itself is stored in the array); QHash <Key, T> keeps a hash table whose size is proportional to the number of items in the hash. To avoid reallocating the data every single time an item is added at the end of the container, these classes typically allocate more memory than necessary.
Consider the following code, which builds a QString from another QString :
QString onlyLetters(const QString &in) { QString out; for (int j = 0; j < in.size(); ++j) { if (in[j].isLetter()) out += in[j]; } return out; }
构建字符串
out
dynamically by appending one character to it at a time. Let's assume that we append 15000 characters to the
QString
string. Then the following 18 reallocations (out of a possible 15000) occur when
QString
runs out of space: 4, 8, 12, 16, 20, 52, 116, 244, 500, 1012, 2036, 4084, 6132, 8180, 10228, 12276, 14324, 16372. At the end, the
QString
has 16372 Unicode characters allocated, 15000 of which are occupied.
The values above may seem a bit strange, but here are the guiding principles:
QByteArray and QList <T> use more or less the same algorithm as QString .
QVector
<T> also uses that algorithm for data types that can be moved around in memory using
memcpy()
(including the basic C++ types, the pointer types, and Qt's
shared classes
) but uses a different algorithm for data types that can only be moved by calling the copy constructor and a destructor. Since the cost of reallocating is higher in that case,
QVector
<T> reduces the number of reallocations by always doubling the memory when running out of space.
QHash <Key, T> 是完全不同的情况。 QHash 's internal hash table grows by powers of two, and each time it grows, the items are relocated in a new bucket, computed as qHash ( key ) % QHash::capacity () (the number of buckets). This remark applies to QSet <T> 和 QCache <Key, T> as well.
For most applications, the default growing algorithm provided by Qt does the trick. If you need more control, QVector <T>, QHash <Key, T>, QSet <T>, QString ,和 QByteArray provide a trio of functions that allow you to check and specify how much memory to use to store the items:
若大概知道将在容器中存储多少项,开始可以通过调用 reserve() , and when you are done populating the container, you can call squeeze() to release the extra preallocated memory.