Qt—容器类

Qt库提供了一组通用的基于模板的容器类( container classes) 。这些容器类可以用来存储指定类型的项目(items),例如,如果需要一个 QString类型的大小可变的数组,那么可以使用QVector< QString> 。在《C十+ Primer》一书中作者就强力推荐使用vector类型和迭代器来取代一般的数组,除非vector无法达到必要的速度要求时才使用数组 。与STL(Standard Template Library,C++的标准模板库)中的容器类相比,Qt中的这些容器类更轻量、更安全,也更容易使用 。如果不熟悉STL或者更喜欢使用Qt way来进行编程,那么就可以使用这些容器类来代替STL的类 。本节内容可以在帮助中参考Container Classes关键字 。
Qt的容器介绍 Qt提供了一些顺序容器:QList,QLinkedList , QVector , QStack 和QQueue 。因为这些容器中的数据都是一个接一个线性存储的,所以称为顺序容器 。对于大多数应用程序而言,使用最多而且最好用的是QList,尽管它是一个数组列表,但是可以快速在其头部和尾部进行添加操作 。如果需要使用一个链表,那么可以使用QLinkedList ;如果希望数据项可以占用连续的内存空间,那么可以使用QVector 。而 QStack 和QQueue分别提供了后进先出(LIFO)和先进先出(FIFO)语义 。
Qt还提供了一些关联容器:QMap, QMultiMap,QHash,QMultiHash和 QSet 。因为这些容器存储的是<键,值>对,比如QMap,所以称为关联容器 。其中,Multi容器用来支持一个键多个值的情况 。表对常用的容器类进行了介绍 。另外,还有QCache和 QContiguousCache,它们提供了对缓存存储中对象的高效散列查找 。


下面对最常用的QList和QMap进行介绍,其他几个容器可以参照这二个进行操作,因为他们的接口函数很相似
新建项目,模板选择Qt控制台应用(Qt Console Application),项目名称为mycontainers 。这里只是为了演示容器类的使用,所以没有使用图形界面,这样只需要建立控制台程序就可以了 。完成后将main.cpp文件更改如下:
#include #include #include int main(int argc, char *argv[]){QCoreApplication a(argc, argv);QList list;list << "aa" << "bb" << "cc"; // 插入项目if(list[1] == "bb") list[1] = "ab";list.replace(2, "bc");// 将“cc”换为“bc”qDebug() << "the list is: ";// 输出整个列表for(int i=0; i QList是一个模板类,它提供了一个列表 。
QList< T>实际上是一个T类型项目的指针数组,所以它支持基于索引的访问,而且当项目的数目小于1000时,可以实现在列表中间进行快速地插入操作 。
QList,提供了很多方便的接口函数来操作列表中的项目,例如,插入操作insert()、替换操作replace(),移除操作removeAt(),移动操作move()、交换操作swap() ,在表尾添加项目append()、在表头添加项目 prepend(),移除第一个项目removeFirst(),移除最后一个项目removeLast(),从列表中移除一项并获取这个项目takeAt()及相应的takeFirst()和 takeLast(),获取一个项目的索引in-dexOf(),判断是否含有相应的项目contains()以及获取一个项目出现的次数count()等 。
QList可以使用“<<”操作符向列表中插入项目,也可以使用“[]”操作符通过索引来访问一个项目,其中项目是从0开始编号的 。不过,对于只读的访问,另一种方法是使用at()函数,它比“[]”操作符要快很多 。
程序中使用了一些常用的函数,读者不用一下子把整个程序都写出来再运行,而是写一部分就运行一次并查看结果 。

QMap类是一个容器类,它提供了一个基于跳跃列表的字典(a skip-list-based dic-tionary) 。QMap是Qt的通用容器类之一,它存储(键,值)对并提供了与键相关的值的快速查找 。QMap中提供了很多方便的接口函数,例如,插入操作insert(),获取值 value() ,是否包含一个键contains()、删除一个键remove()、删除一个键并获取该键对应的值take(),清空操作 clear()、插入一键多值insertMulti()等 。可以使用“[]”操作符插入一个键值对或者获取一个键的值,不过当使用该操作符获取一个不存在的键的值时,会默认向map中插入该键;为了避免这个情况,可以使用value()函数来获取键的值 。当使用value()函数时,如果指定的键不存在,那么默认会返回0,可以在使用该函数时提供参数来更改这个默认返回的值 。
QMap默认是一个键对应一个值的,但是也可以使用insertMulti()进行一键多值的插入;对于一键多值的情况,更方便的是使用QMap的子类QMultiMap 。
容器也可以嵌套使用,如QMap>,这里键的类型是QString,而值的类型是QList< int> 。注意,后面的“>>”符号之间要有一个空格,不然编译器会将它当作“>>”操作符对待 。各种容器存储的值的类型可以是任何的可赋值的数据类型,该类型需要有一个默认的构造函数、一个复制构造函数和一个赋值操作运算符,像基本的类型(如 int和 double)、指针类型、Qt的数据类型(如 QString 和QDate等),但不包括QObject以及QObject的子类(QWidget、QDialog、QTimer等),不过可以存储这些类的指针,如QList 。也可以自定义数据类型,具体方法可以参考Container Classes文档中的相关内容 。
遍历容器 遍历一个容器可以使用迭代器(iterators)来完成,迭代器提供了一个统一的方法来访问容器中的项目 。Qt的容器类提供了两种类型的迭代器:Java风格迭代器和STL风格迭代器 。如果只是想按顺序遍历一个容器中的项目,那么还可以使用Qt的foreach关键字 。
Java风格迭代器 Java风格迭代器从Qt 4时被引入,使用上比STL风格迭代器要方便很多,但是在性能上稍微弱于后者 。每一个容器类都有两个Java风格迭代器数据类型:一个提供只读访问,一个提供读/写访问,如表所列 。

下面将以QList和QMap为例来进行讲解,而QLinkedList、QVector和QSet与QList的迭代器拥有极其相似的接口;类似的,QHash与QMap的迭代器拥有相同的接口 。
新建Qt控制台应用,项目名称为myiterators 。然后将main. cpp文件更改如下:
#include #include #include #include #include int main(int argc, char *argv[]){QCoreApplication a(argc, argv);QList list;list << "A" << "B" << "C" << "D";QListIterator i(list); // 创建列表的只读迭代器,将list作为参数qDebug() << "the forward is :";while (i.hasNext())// 正向遍历列表,结果为A,B,C,DqDebug() << i.next();qDebug() << "the backward is :";while (i.hasPrevious())// 反向遍历列表,结果为D,C,B,AqDebug() << i.previous();return a.exec();} 这里先创建了一个QList列表list, 然后使用list作为参数创建了列表的只读迭代器 。这时,迭代器指向列表的第一个项目的前面(这里是指向项目“A"的前面) 。然后使用hasNext()函数来检查该迭代器后面是否还有项目,如果还有项目,那么使用next()来跳过这个项目,next()函数会返回它所跳过的项目 。当正向遍历结束后,迭代器会指向列表最后一个项目的后面,这时可以使用hasPrevious( )和previous()来进行反向遍历 。
可以看到,Java风格迭代器是指向项目之间的,而不是直接指向项目 。所以,迭代器或者指向容器的最前面,或者指向两个项目之间,或者指向容器的最后面 。


QListIterator没有提供向列表中插入或者删除项目的函数,要完成这些功能,就必须使QMutableListlterator 。这个类增加了insert()函数来完成插入操作,remove( )
函数完成删除操作,setValue()函数完成设置值操作 。在前面程序中的“returna.exec();"前添加如下代码:
QMutableListIterator j(list);j.toBack();// 返回列表尾部while (j.hasPrevious()) {QString str = j.previous();if(str == "B") j.remove();// 删除项目“B”}j.insert("Q");// 在列表最前面添加项目“Q”j.toBack();if(j.hasPrevious()) j.previous() = "N";// 直接赋值j.previous();j.setValue("M");// 使用setValue()进行赋值j.toFront();qDebug()<< "the forward is :";while (j.hasNext())// 正向遍历列表,结果为Q,A,M,NqDebug() << j.next();return a.exec(); 可以使用remove()函数来删除上一-次跳过的项目,使用insert()函数在迭代器指向的位置插入一个项目,这时迭代器会位于添加的项目之后,比如这里添加“Q”后,迭代器指向“Q”和“A”之间 。使用QMutableListIterator类的next() 和previous()等函数时会返回列表中项目的一个非const引用,所以可以直接对其赋值;当然也可以使用setValue()函数进行赋值,这个函数是对上一-次跳过的项目进行赋值的 。除了这里讲到的这些函数外,还有findNext( )和findPrevious()函数可以用来实现项目的查找 。
现在可以运行程序,运行结果已经在上面的程序中注释出来了 。
与QListIlterator 类似, QMapIterator提供了toFront( ). toBack( ). hasNext( )、next()、peekNext( )、hasPrevious( )、previous( )和peekPrevious()等函数 。可以在next()、peekNext()、previous( )和peekPrevious()等函数返回的对象上分别使用key()和value()函数来获取键和值 。
还是那样项目,main.cpp
#include #include #include #include int main(int argc, char *argv[]){QCoreApplication a(argc, argv);QMap map;map.insert("Paris", "France");map.insert("Guatemala City", "Guatemala");map.insert("Mexico City", "Mexico");map.insert("Moscow", "Russia");QMapIterator i(map);while(i.hasNext()) {i.next();qDebug() << i.key() << " : " << i.value();}if(i.findPrevious("Mexico")) qDebug() << "find 'Mexico'";// 向前查找键的值QMutableMapIterator j(map);while (j.hasNext()) {if (j.next().key().endsWith("City")) // endsWith()是QString类的函数j.remove();// 删除含有“City”结尾的键的项目}while(j.hasPrevious()) {j.previous();// 现在的键值对为(paris,France),(Moscow,Russia)qDebug() << j.key() << " : " << j.value();}return a.exec();} 其中,QMap中存储了一些(首都,国家)键值对,然后删除了包含以“City”字符串结尾的键的项目 。对于QMap的遍历,可以先使用next()函数,然后再使用key()和value()来获取键和值的信息 。因为这里很多函数与前面例子中的用法相似,这里就不再过多讲解 。现在运行一下程序,从遍历结果可以看到,QMap是按照键的顺序来存储数据的,比如这里是按照键的字母顺序排列的 。
STL风格迭代器 STL风格迭代器兼容Qt和STL的通用算法(generic algorithms),而且在速度上.进行了优化 。每-一个容器类都有两个STL风格迭代器类型:一个提供了只读访问,另一个提供了读/写访问,如表所列 。因为只读迭代器比读/写迭代器要快很多,所以应尽可能使用只读迭代器 。

下面仍然以QList和QMap为例进行相关内容的讲解 。新建Qt控制台应用,项目名称为myiterators3 。然后将main. cpp文件更改如下:
#include #include #include #include int main(int argc, char *argv[]){QCoreApplication a(argc, argv);QList list;list << "A" << "B" << "C" << "D";QList::iterator i;// 使用读写迭代器qDebug() << "the forward is :";for (i = list.begin(); i != list.end(); ++i) {*i = (*i).toLower();// 使用QString的toLower()函数转换为小写qDebug() << *i;// 结果为a,b,c,d}qDebug() << "the backward is :";while (i != list.begin()) {--i;qDebug() << *i;// 结果为d,c,b,a}QList::const_iterator j; // 使用只读迭代器qDebug() << "the forward is :";for (j = list.constBegin(); j != list.constEnd(); ++j)qDebug() << *j;// 结果为a,b,c,dreturn a.exec();} STL风格迭代器的API模仿了数组的指针,例如,使用“十十”操作符来向后移动迭代器使其指向下一个项目、使用“