关联式容器
关联式容器持有同一类型的条目,每个条目被一个键所索引。 Qt提供两种主要的关联式容器: QMap<K, T> 和 QHash<K, T>。
QMap<K, T>是一种按照键值从小到大的顺序存储键值对的结构。 这个装置提供了一个好的性能,对于检索,插入和中序遍历(in-order iteration)。 在内部,QMap<K, T>用跳跃表(skip-list)来实现。
图 11.6. A map of QString to int
向map中插入条目的一个简单方法是调用insert():
QMap<QString, int> map; map.insert("eins", 1); map.insert("sieben", 7); map.insert("dreiundzwanzig", 23);
或者,我们可以简单地像下面那样为一个给定的键赋值:
map["eins"] = 1; map["sieben"] = 7; map["dreiundzwanzig"] = 23;
[]操作符可以用来进行插入和解引用。 在一个non-const map中,如果[]为一个不存在的键解引用,那么系统将会用给定的键和一个空值来新建一个条目。 为了避免意外创建空值,我们可以用value()函数代替[]来引用条目。
int val = map.value("dreiundzwanzig");
如果键不存在,将会用值类型的缺省构造函数返回一个缺省值,并且没有新的条目被创建。 对于基本类型和指针类型,返回0。 我们可以另指定缺省值作为value()的第二个参数,例如:
int seconds = map.value("delay", 30);
这相当于:
int seconds = 30; if (map.contains("delay")) seconds = map.value("delay");
QMap<K, T>中的K和T可以是基本类型像int和double,指针类型,或拥有缺省构造函数,拷贝构造函数,和赋值操作符的类。 另外,K类型必须提供operator<(),因为QMap<K, T>使用这个操作符按键值升序来存储条目。
QMap<K, T>有一对便捷函数,keys() 和values(),它们特别有用当处理小数据集时。 它们返回map的键和值的QList。
Maps通常是single-valued: 如果为存在的键赋新值,那么旧值就会被新值替换掉,确保没有两个条目共享同一个键。 多个键值对拥有相同的键也是可能的,通过使用insertMulti()函数或者是便捷子类QMultiMap<K, T>。 QMap<K, T>的重载函数values(const K &)返回一个QList,它包含指定键对应的所有的值。 例如:
QMultiMap<int, QString> multiMap; multiMap.insert(1, "one"); multiMap.insert(1, "eins"); multiMap.insert(1, "uno"); QList<QString> vals = multiMap.values(1);
QHash<K, T>是将键值对存储在哈希表中的结构。 它的接口和QMap<K, T>几乎相同,但是它对K模板参数要求不同而且它的检索速度要比QMap<K, T>更快。 另外不同的是,QHash<K, T>是无序的。
除了容器中值类型的一些标准要求外,QHash<K, T>中的K类型还被要求提供operator==()并且它被全局函数qHash()所支持,此函数返回一个键的哈希值。 Qt已经为整型,指针类型,QChar,QString,和QByteArray提供qHash()函数。
QHash<K, T>为内部hash表自动分配bucket中的一个素数,并重新调整大小当插入或删除条目。 调用reserve()来指定预期的条目数或者根据当前条目数调用squeeze()来收缩hash表可以提升性能。 一般的习惯是用期望的最大条目数调用reserve(),然后插入数据,最后调用squeeze()减少内存占用如果条目数比期望的要少。
Hashes通常是single-valued,但是同样的键可以有多个值,通过使用insertMulti()函数或QMultiHash<K, T>便捷子类。
除了QHash<K, T>,Qt还提供一个QCache<K, T>类,它能用来缓存伴有键的对象,而且Qt还提供一个QSet<K>容器,只用来存储键。 在内部,这两个类都依赖QHash<K, T>并且它们对K类型的要求和QHash<K, T>一样。
遍历关联式容器的最简单的方法是使用Java风格的迭代器。 因为迭代器必须既要接触键又要接触值,Java风格关联式容器迭代器与序列式容器的工作稍微有些不同。 主要的不同是next()和previous()函数返回一个代表键值对的对象,而不是简单的一个值。 通过对象的key()和value()来存取键和值。 例如:
QMap<QString, int> map; ... int sum = 0; QMapIterator<QString, int> i(map); while (i.hasNext()) sum += i.next().value();
如果我们需要同时存取键和值,我们可以简单地忽略next()或previous()的返回值并用迭代器的key()和previous()函数,操作在最后一个被跳过的条目上。
QMapIterator<QString, int> i(map); while (i.hasNext()) { i.next(); if (i.value() > largestValue) { largestKey = i.key(); largestValue = i.value(); } }
可变迭代器有一个setValue()函数用来修改当前条目的值:
QMutableMapIterator<QString, int> i(map); while (i.hasNext()) { i.next(); if (i.value() < 0.0) i.setValue(-i.value()); }
STL风格的迭代器也提供key()和value()函数。 使用nonconst迭代器类型,value()返回一个non-const引用,允许我们在迭代的时候修改值。 注意尽管这些迭代器被成为“STL风格”,但它们与STL的map<K, T>迭代器有明显的不同,STL的是基于pair<K, T>的。
foreach循环也能用于关联式容器,但只工作在键值对的值上。 如果我们同时需要条目的键和值,我们可以在嵌套的foreach循环中调用keys()和values(const K &),像下面那样:
QMultiMap<QString, int> map; ... foreach (QString key, map.keys()) { foreach (int value, map.values(key)) { do_something(key, value); } }