Qt的容器类
说白了,这就是一个类。这个类的特别之处就是,用来存储其他的类或者数据。存储基础的int,float类型,也可以是QString,QDate类型。
Qt的容器类分为顺序容器(sequential containers)和关联容器(associative containers)。
顺序容器类
Qt的顺序容器类有QList、QLinkedList、QVector、QStack和QQueue。
1.QList
QList是最常用的容器类,虽然它是以数组列表(array-list)的形式实现的,但是在其前或后添加数据非常快,QList以下标索引的方式对数据项进行访问。
QList用于添加、插入、替换、移动、删除数据项的函数有:insert()、replace()、removeAt()、move()、swap()、append()、prepend()、removeFirst()和removeLast()等。
QList提供下标索引方式访问数据项,如同数组一样,也提供at()函数,例如:
QList<QString> list;
list << "one" << "two" << "three";
QString str1=list[1]; //str1=="two"
QString str0=list.at(0); //str0=="one"
QList的isEmpty()函数在数据项为空时返回true,size()函数返回数据项的个数。
QList是Qt中最常用的容器类,很多函数的参数传递都是采用QList容器类,例如QAudioDeviceInfo的静态函数availableDevices()的函数原型是:
QList<QAudioDeviceInfo> QAudioDeviceInfo::availableDevices(QAudio::Mode mode)
其返回数据就是QAudioDeviceInfo类型的QList列表。
2.QLinkedList
QLinkedList是链式列表(linked-list),数据项不是用连续的内存存储的,它基于迭代器访问数据项,并且插入和删除数据项的操作时间相同。
除了不提供基于下标索引的数据项访问外,QLinkedList的其他接口函数与QList基本相同。
3.QVector
QVector提供动态数组的功能,以下标索引访问数据。
QVector的函数接口与QList几乎完全相同,QVector的性能比QList更高,因为QVector的数据项是连续存储的。
4.QStack
QStack是提供类似于堆栈的后入先出(LIFO)操作的容器类,push()和pop()是主要的接口函数。例如:
QStack<int> stack;
stack.push(10);
stack.push(20);
stack.push(30);
while (!stack.isEmpty())
cout << stack.pop() << endl;
程序会依次输出30,20,10。
5.QQueue
QQueue是提供类似于队列先入先出(FIFO)操作的容器类。enqueue()和dequeue()是主要操作函数。例如:
QQueue<int> queue;
queue.enqueue(10);
queue.enqueue(20);
queue.enqueue(30);
while (!queue.isEmpty())
cout << queue.dequeue() << endl;
程序会依次输出10,20,30。
关联容器类
Qt还提供关联容器类QMap、QMultiMap、QHash、QMultiHash和QSet。
QMultiMap和QMultiHash支持一个键关联多个值,QHash和QMultiHash类使用散列(Hash)函数进行查找,查找速度更快。
1.QSet
QSet是基于散列表的集合模板类,它存储数据的顺序是不定的,查找值的速度非常快。QSet 内部就是用QHash实现的。
定义QSet容器和输入数据的实例代码如下:
QSet<QString> set;
set << "dog" << "cat" << "tiger";
测试一个值是否包含于这个集合,用contains()函数,示例如下:
if (!set.contains("cat"))
...
2.QMap
QMap<Key, T>提供一个字典(关联数组),一个键映射到一个值。QMap存储数据是按照键的顺序,如果不在乎存储顺序,使用QHash会更快。
定义QMap<QString, int>类型变量和赋值的示例代码如下:
QMap<QString, int> map;
map["one"] = 1;
map["two"] = 2;
map["three "] = 3;
也可以使用insert()函数赋值,或remove()移除一个键值对,示例如下:
map.insert("four", 4);
map.remove("two");
要查找一个值,使用运算符“[ ]”或value()函数,示例如下:
int num1 = map["one"];
int num2 = map.value("two");
如果在映射表中没有找到指定的键,会返回一个缺省构造值(default-constructed values),例如,如果值的类型是字符串,会返回一个空的字符串。
在使用value()函数查找键值时,还可以指定一个缺省的返回值,示例如下:
timeout = map.value("TIMEOUT",30);
这表示如果在map里找到键“TIMEOUT”,就返回关联的值,否则返回值为30。
3.QMultiMap
QMultiMap是QMap的子类,是用于处理多值映射的便利类。
多值映射就是一个键可以对应多个值。QMap正常情况下不允许多值映射,除非使用QMap::insertMulti()添加键值对。
QMultiMap是QMap的子类,所以QMap的大多数函数在QMultiMap都是可用的,但是有几个特殊的,QMultiMap::insert()等效于QMap::insertMulti(),QMultiMap::replace()等效于QMap::insert()。
QMultiMap使用示例如下:
QMultiMap<QString, int> map1, map2, map3;
map1.insert("plenty", 100);
map1.insert("plenty", 2000); // map1.size() == 2
map2.insert("plenty", 5000); // map2.size() == 1
map3 = map1 + map2; // map3.size() == 3
QMultiMap不提供“[ ]”操作符,使用value()函数访问最新插入的键的单个值。如果要获取一个键对应的所有值,使用values()函数,返回值是QList类型。
QList<int> values = map.values("plenty");
for (int i = 0; i < values.size(); ++i)
cout << values.at(i) << endl;
4.QHash
QHash是基于散列表来实现字典功能的模板类,QHash<Key, T>存储的键值对具有非常快的查找速度。
QHash与QMap的功能和用法相似,区别在于以下几点:
QHash比QMap的查找速度快;
在QMap上遍历时,数据项是按照键排序的,而QHash的数据项是任意顺序的;
QMap的键必须提供“<”运算符,QHash的键必须提供“==”运算符和一个名称为qHash()的全局散列函数。
5.QMultiHash
QMultiHash是QHash的子类,是用于处理多值映射的便利类,其用法与QMultiMap类似。
容器类的迭代
就是我们使用的迭代器。
迭代器(iterator)为访问容器类里的数据项提供了统一的方法,Qt有两种迭代器类:Java类型的迭代器和STL类型的迭代器。
Java类型的迭代器更易于使用,且提供一些高级功能,而STL类型的迭代器效率更高。
Qt还提供一个关键字foreach(实际是< QtGlobal >里定义的一个宏)用于方便地访问容器里所有数据项。
1.Java类型迭代器
1.Java类型迭代器总表
对于每个容器类,有两个Java类型迭代器:一个用于只读操作,一个用于读写操作,各个Java类型的容器类见表。
表 Java类型的迭代器类
容器类 | 只读迭代器 | 读写迭代器 |
---|---|---|
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> |
QMap和QHash等关联容器类的迭代器用法相同,QList和QLinkedList、QSet等容器类的用法相同,所以下面只以QMap和QList为例介绍迭代器的用法。
2.顺序容器类的迭代器的使用
Java类型迭代器的指针不是指向一个数据项,而是在数据项之间,迭代器指针位置示意图如图所示。
图 Java类型迭代器位置示意图
下面是遍历访问一个QList容器的所有数据项的典型代码。
QList<QString> list;
list << "A" << "B" << "C" << "D";
QListIterator<QString> i(list);
while (i.hasNext())
qDebug() << i.next();
QList< QString>容器对象list作为参数传递给QListIterator< QString >迭代器i的构造函数,i用于对list作只读遍历。起始时刻,迭代器指针在容器第一个数据项的前面(图中数据项“A”的前面),调用hasNext()判断在迭代器指针后面是否还有数据项,如果有,就调用next()跳过一个数据项,并且next()函数返回跳过去的那个数据项。
也可以反向遍历,示例代码如下:
QListIterator<QString> i(list);
i.toBack();
while (i.hasPrevious())
qDebug() << i.previous();
QListIterator用于移动指针和读取数据的函数见表2。
表2 QListIterator常用函数
函数名 | 功能 |
---|---|
void toFront() | 迭代器移动到列表的最前面(第一个数据项之前) |
void toBack() | 迭代器移动到列表的最后面(最后一个数据项之后) |
bool hasNext() | 如果迭代器不是位于列表最后位置,返回true |
const T & next() | 返回下一个数据项,并且迭代器后移一个位置 |
const T & peekNext() | 返回下一个数据项,但是不移动迭代器位置 |
bool hasPrevious() | 如果迭代器不是位于列表的最前面,返回true |
const T & previous() | 返回前一个数据项,并且迭代器前移一个位置 |
const T & peekPrevious() | 返回前一个数据项,但是不移动迭代器指针 |
QListIterator是只读访问容器内数据项的迭代器,若要在遍历过程中对容器的数据进行修改,需要使用QMutableListIterator。例如下面的示例代码为删除容器中数据为奇数的项。
QList<int> list;
list <<1 <<2<<3<<4<<5;
QMutableListIterator<int> i(list);
while (i.hasNext()) {
if (i.next() % 2 != 0)
i.remove();
}
remove()函数移除next()函数刚刚跳过的一个数据项,不会使迭代器失效。
setValue()函数可以修改刚刚跳过去的数据项的值。
3.关联容器类的迭代器的使用
对于关联容器类QMap,使用QMapIterator和QMutableMapIterator迭代器类,它们具有表2所示的所有函数,主要是增加了key()和value()函数用于获取刚刚跳过的数据项的键和值。
例如,下面的代码将删除键(城市名称)里以“City”结尾的数据项。
QMap<QString, QString> map;
map.insert("Paris", "France");
map.insert("New York", "USA");
map.insert("Mexico City", "USA");
map.insert("Moscow", "Russia");
...
QMutableMapIterator<QString, QString> i(map);
while (i.hasNext()) {
if (i.next().key().endsWith("City"))
i.remove();
}
如果是在多值容器里遍历,可以用findNext()或findPrevious()查找下一个或上一个值,如下面的代码将删除上一示例代码中map里值为“USA”的所有数据项。
QMutableMapIterator<QString, QString> i(map);
while (i.findNext("USA"))
i.remove();
2.STL类型迭代器
1.STL类型迭代器总表
STL迭代器与Qt和STL的原生算法兼容,并且进行了速度优化。具体类型见表3。
对于每一个容器类,都有两个STL类型迭代器:一个用于只读访问,一个用于读写访问。无需修改数据时一定使用只读迭代器,因为它们速度更快。
表3 STL类型的迭代器类
容器类 | 只读迭代器 | 读写迭代器 |
---|---|---|
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 |
注意
在定义只读迭代器和读写迭代器时的区别,它们使用了不同的关键字,const_iterator定义只读迭代器,iterator定义读写迭代器。此外,还可以使用const_reverse_iterator和reverse_iterator定义相应的反向迭代器。
STL类型的迭代器是数组的指针,所以“++”运算符使迭代器指向下一个数据项,“*”运算符返回数据项内容。与Java类型的迭代器不同,STL迭代器直接指向数据项,STL迭代器指向位置示意图如图2所示。
图2 STL类型迭代器位置示意图
begin()函数使迭代器指向容器的第一个数据项,end()函数使迭代器指向一个虚拟的表示结尾的数据项,end()表示的数据项是无效的,一般用作循环结束条件。
下面仍然以QList和QMap为例说明STL迭代器的用法,其他容器类迭代器的用法类似。
2.顺序容器类的迭代器的用法
下面的示例代码将QList list里的数据项逐项输出。
QList<QString> list;
list << "A" << "B" << "C" << "D";
QList<QString>::const_iterator i;
for (i = list.constBegin(); i != list.constEnd(); ++i)
qDebug() << *i;
constBegin()和constEnd()是用于只读迭代器的,表示起始和结束位置。
若使用反向读写迭代器,并将上面示例代码中list的数据项都改为小写,代码如下:
QList<QString>::reverse_iterator i;
for (i = list.rbegin(); i != list.rend(); ++i)
*i = i->toLower();
}
3.关联容器类的迭代器的用法
对于关联容器类QMap和QHash,迭代器的“*”操作符返回数据项的值。如果想返回键,使用key()函数。对应的,用value()函数返回一个项的值。
例如,下面的代码将QMap<int, int> map中所有项的键和值输出。
QMap<int, int> map;
...
QMap<int, int>::const_iterator i;
for (i = map.constBegin(); i != map.constEnd(); ++i)
qDebug() << i.key() << ':' << i.value();
Qt API包含很多返回值为QList或QStringList的函数,要遍历这些返回的容器,必须先复制。由于Qt使用了隐式共享,这样的复制并无多大开销。例如下面的代码是正确的。
const QList<int> sizes = splitter->sizes();
QList<int>::const_iterator i;
for (i = sizes.begin(); i != sizes.end(); ++i)
...
提示:
隐式共享(Implicit Sharing)是对象的管理方法。一个对象被隐式共享,只是传递该对象的一个指针给使用者,而不实际复制对象数据,只有在使用者修改数据时,才实质复制共享对象给使用者。如在上面的代码中,splitter->sizes()返回的是一个QList列表对象sizes,但是实际上代码并不将splitter->sizes()表示的列表内容完全复制给变量sizes,只是传递给它一个指针。只有当sizes发生数据修改时,才会将共享对象的数据复制给sizes,这样避免了不必要的复制,减少了资源占用。
而下面的代码是错误的。
QList<int>::const_iterator i;
for (i = splitter->sizes().begin(); i != splitter->sizes().end(); ++i)
...
对于STL类型的迭代器,隐式共享还涉及另外一个问题,即当有一个迭代器在操作一个容器变量时,不要去复制这个容器变量。
3.foreach关键字
如果只是想遍历容器中所有的项,可以使用foreach关键字。foreach是头文件中定义的一个宏。使用foreach的句法是:
foreach (variable, container)
使用foreach的代码比使用迭代器更简洁。例如,使用foreach遍历一个QLinkedList的示例代码如下:
QLinkedList<QString> list;
...
QString str;
foreach (str, list)
qDebug() << str;
用于迭代的变量也可以在foreach语句里定义,foreach语句也可以使用花括号,可以使用break退出迭代,示例代码如下:
QLinkedList<QString> list;
...
foreach (const QString &str, list) {
if (str.isEmpty())
break;
qDebug() << str;
}
对于QMap和QHash,foreach会自动访问“键——值”对里的值,所以无需调用values()。如果需要访问键则可以调用keys(),示例代码如下:
QMap<QString, int> map;
...
foreach (const QString &str, map.keys())
qDebug() << str << ':' << map.value(str);
对于多值映射,可以使用两重foreach语句,示例代码如下:
QMultiMap<QString, int> map;
...
foreach (const QString &str, map.uniqueKeys()) {
foreach (int i, map.values(str))
qDebug() << str << ':' << i;
}
注意:
foreach关键字遍历一个容器变量是创建了容器的一个副本,所以不能修改原来容器变量的数据项。