I.容器类简介
存储容器(containers)有时候也被称为集合(collections),是能够在内存中存储其它特定类型的对象,通常是一些常用的数据结构,一般是通用模板类的形式。C++ 提供了一套完整的解决方案,作为标准模板库(Standard Template Library)的组成部分,也就是常说的 STL。
Qt 提供了另外一套基于模板的容器类。相比 STL,这些容器类通常更轻量、更安全、更容易使用。如果你对 STL 不大熟悉,或者更喜欢 Qt 风格的 API,那么你就应该选择使用这些类。当然,你也可以在 Qt 中使用 STL 容器,没有任何问题。
II.容器类特性
Qt 的容器类都不继承QObject,都提供了隐式数据共享、不可变的特性,并且为速度做了优化,具有较低的内存占用量等。另外一点比较重要的,它们是线程安全的,他们作为制度容器时可以被多个线程访问。这些容器类是平台无关的,即不因编译器的不同而具有不同的实现;隐式数据共享,有时也被称作“写时复制(copy on write)”,这种技术允许在容器类中使用传值参数,但却不会出现额外的性能损失。遍历是容器类的重要操作。Qt 容器类提供了类似 Java 的遍历器语法,同样也提供了类似 STL 的遍历器语法,以方便用户选择自己习惯的编码方式。相比而言,Java 风格的遍历器更易用,是一种高层次的函数;而 STL 风格的遍历器更高效,同时能够支持 Qt 和 STL 的通用算法。最后一点,在一些嵌入式平台,STL 往往是不可用的,这时你就只能使用 Qt 提供的容器类,除非你想自己创建。顺便提一句,除了遍历器,Qt 还提供了自己的 foreach 语法(C++ 11 也提供了类似的语法,但有所区别,详见这里的 foreach 循环一节)。
I.容器分类
Qt 提供了顺序存储容器:QList,QLinkedList,QVector,QStack和QQueue。对于绝大多数应用程序,QList是最好的选择。虽然它是基于数组实现的列表,但它提供了快速的向前添加和向后追加的操作。如果你需要链表,可以使用QLinkedList。如果你希望所有元素占用连续地址空间,可以选择QVector。QStack和QQueue则是 LIFO 和 FIFO 的。
Qt 还提供了关联容器:QMap,QMultiMap,QHash,QMultiHash和QSet。带有“Multi”字样的容器支持在一个键上面关联多个值。“Hash”容器提供了基于散列函数的更快的查找,而非 Hash 容器则是基于二分搜索的有序集合。
另外两个特例:QCache和QContiguousCache提供了在有限缓存空间中的高效 hash 查找。
II.各个容器类详解
1)顺序容器类
QList:这是至今为止提供的最通用的容器类。它将给定的类型 T 的对象以列表的形式进行存储,与一个整型的索引关联。QList在内部使用数组实现,同时提供基于索引的快速访问。我们可以使用 QList::append()和QList::prepend()在列表尾部或头部添加元素,也可以使用QList::insert()在中间插入。相比其它容器类,QList专门为这种修改操作作了优化。QStringList继承自QList。如下例所示:
QLisiaList;//使用Qlist定义一个字符串列表的容器
**aList.append("Monday");
aList.append("Tuesday");
aList.append("Wednesday");
QString str=aList[0];**
Qlist 以下标索引的方式对数据项进行访问。QList用于添加,插入,替换,移除和删除数据的函数有insert()、replace()、removeAt()、move()、swap()、append()、prepend()、removeFirst()、removeLast()。QList的isEmpty()函数在数据项为空时返回true,size()函数返回数据项个数。Qlist容器类还可以作为函数参数进行传递。
QLinkedList:类似于 QList,除了它是使用遍历器进行遍历,而不是基于整数索引的随机访问。对于在中部插入大量数据,它的性能要优于QList。同时具有更好的遍历器语义(只要数据元素存在,QLinkedList的遍历器就会指向一个合法元素,相比而言,当插入或删除数据时,QList的遍历器就会指向一个非法值)。
QVector:用于在内存的连续区存储一系列给定类型的值。在头部或中间插入数据可能会非常慢,因为这会引起大量数据在内存中的移动。
QStack:这是QVector的子类,提供了后进先出(LIFO)语义。相比QVector,它提供了额外的函数:push(),pop()和top()。
**QStackstack;
stack.push(10);
stack.push(20);
stack.push(30);
while(!stack.isEmpty())
cout<
QQueue:这是QList的子类,提供了先进先出(FIFO)语义。相比QList,它提供了额外的函数:enqueue(),dequeue()和head()。
QQueuequeue;
queue.enqueue(10);
queue.enqueue(20);
queue.enqueue(30);
while(!queue.isEmpty())
cout<
2)关联容器类
QSet:是基于散列表的集合模板类,他的存储顺序是不一定的,查找值的速度非常快。QSet内部是用QHash实现的。
定义QSet容器和输入数据的例子如下:
Qsetset;
set<<"dog"<<"cat"<<"tiger";
测试一个值是否包含在这个集合,用contains()函数,操作如下:
if(!set.contains("cat")){………………}
QMap
定义QMap
QMapmap;
map["one"]=1; //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”);
注意:如果映射表中没有找到指定的键,会返回一个缺省构造值,如值的类型是字符串,则返回一个空的字符串。
在使用value()函数查找键值时,还可以指定一个缺省的返回值,如下:
timeout=map.value(“TIMEOUT”,30);//没找到键值,返回30。
QMultiMap
QMultiMap是QMap的子类,大多数QMap的函数都可以使用,但是也有几个特殊的,QMultiMap::insert()等效与QMap::insertMulti(),QMultiMap::replace()等效于QMap::insert()。QmultiMap使用实例如下所示:
QMultiMapmap1,map2,map3
map1.insert("plenty",100);
map1.insert("plenty",1000);//map1.size()==2
map1.insert("plenty",100);//map2.size()==1
map1.insert("plenty",100);//map3.size()=3;
QMultiMapn不提供[]操作符号,使用value()函数访问最新插入的键的单个值。如果想要获取一个键对应的所有值,使用value()函数,返回值是QList类型:
Qlistvalue=map.values(“plenty”);
注意:容器中的数据必须是可赋值数据类型。所谓可赋值数据类型,是指具有默认构造函数、拷贝构造函数和赋值运算符的类型。绝大多数数据类型,包括基本类型,比如 int 和 double,指针,Qt 数据类型,例如QString、QDate和QTime,都是可赋值数据类型。但是,QObject及其子类(QWidget、QTimer等)都不是。也就是说,你不能使用QList这种容器,因为QWidget的拷贝构造函数和赋值运算符不可用。如果你需要这种类型的容器,只能存储其指针,也就是QList
迭代器为访问容器类里的数据项提供了统一的方法,Qt 有两种迭代器类:Java 类型的迭代器和 STL 类型的迭代器。两者比较,Java 类型的迭代器更易于使用,且提供一些高级功能,而 STL 类型的迭代器效率更高。
I.Java 类型迭代器
对于每个容器类,有两个 Java 类型迭代器:一个用于只读操作,一个用于读写操作,各个Java 类型的容器类见表 1。
QMap 和 QHash 等关联容器类的迭代器用法相冋,QList 和 QLinkedList、QSet 等容器类的用法相同,所以下面只以 QMap 和 QList 为例介绍迭代器的用法。
顺序容器类的迭代器的使用
Java 类型迭代器的指针不是指向一个数据项,而是在数据项之间,迭代器指针位置示意图如图 2 所示。
下面是遍历访问一个 QList 容器的所有数据项的典型代码:
QList list;
list << "A" << "B" << "C" << "D";
QListIterator i (list);//创建一个QList容器类的只读迭代器i
while (i.hasNext())//hasNext()判读在迭代器指针后面是否还有数据
qDebug () << i.next ();
如果在遍历过程中要对容器类的数据进行修改,需要使用 QMutableListlterator如下面的示例代码为删除容器中数据为奇数的项:
QList list;
list <<1<<2<<3<<4<<5;
QMutableListIterator i (list);
while (i.hasNext()) {
if (i.next() % 2 != 0)
i.remove();
}
remove() 函数移除 next() 函数刚刚跳过的一个数据项,不会使迭代器失效。setValue() 函数可以修改刚刚跳过去的数据项的值。
关联容器类的迭代器的使用
对于关联容器类 QMap,使用 QMapIterator 和 QMutableMapIterator 迭代器类,它们具有表 3 所示的所有函数,主要是增加了 key() 和 value() 函数用于获取刚刚跳过的数据项的键和值。
例如,下面的代码将删除键(城市名称)里以“City”结尾的数据项:
QMap map;
map.insert("Paris", "France");
map.insert("New York", "USA");
map.insert("Mexico City", "USA");
map.insert("Moscow", "Russia");
...
QMutableMapIterator i(map);
while (i.hasNext ()) {
if (i.next().key().endsWith("City"))
i.remove();
}
如果是在多值容器里遍历,可以用 findNext() 或 findPrevious() 查找下一个或上一个值,如下面的代码将删除上一示例代码中 map 里值为“USA”的所有数据项:
QMutableMapIterator i(map);、
while (i.findNext("USA"))
i.remove();
II.STL类型迭代器
STL 迭代器与 Qt 和 STL 的原生算法兼容,并且进行了速度优化。
对于每一个容器类,都有两个 STL 类型迭代器:一个用于只读访问,一个用于读写访问。无需修改数据时一定使用只读迭代器,因为它们速度更快。
注意,在定义只读迭代器和读写迭代器时的区别,它们使用了不同的关健字,const_iterator 定义只读迭代器,iterator 定义读写迭代器。此外,还可以使用 const_reverse_iterator 和 reverse_iterator 定义相应的反向迭代器。
STL 类型的迭代器是数组的指针,所以“++”运算符使迭代器指向下一个数据项,运算符返回数据项内容。与 Java 类型的迭代器不同,STL 迭代器直接指向数据项,STL 迭代器指向位置示意图如图 5 所示。
begin() 函数使迭代器指向容器的第一个数据项,end() 函数使迭代器指向一个虚拟的表示结尾的数据项,end() 表示的数据项是无效的,一般用作循环结束条件。
下面仍然以 QList 和 QMap 为例说明 STL 迭代器的用法,其他容器类迭代器的用法类似。
顺序容器类的迭代器的用法
下面的示例代码将 QList list 里的数据项逐项输出:
QList list;
list << "A" << "B" << "C" << "D";
QList::const_iterator i;
for (i = list.constBegin(); i != list.constEnd(); ++i)
qDebug() << *i;
constBegin() 和 constEnd() 是用于只读迭代器的,表示起始和结束位置。
若使用反向读写迭代器,并将上面示例代码中 list 的数据项都改为小写,代码如下:
QList::reverse_iterator i;
for (i = list.rbegin(); i != list.rend(); ++i)
*i = i->toLower();
}
关联容器类的迭代器的用法
对于关联容器类 QMap 和 QHash,迭代器的操作符返回数据项的值。如果想返回键,使用 key() 函数。对应的,用 value() 函数返回一个项的值。
例如,下面的代码将 QMap
QMap map;
...
QMap::const_iterator i;
for (i = map.constBegin(); i != map.constEnd(); ++i)
qDebug () << i.key () << ':' << i.value ();
补充:
如果只想要遍历容器中的所有项,可以使用foreach关键字。foreach是头文件中定义的宏,语法是:foreach(variable,container)
使用foreach的代码比使用迭代器更简洁。如:遍历QLinkedList的实例代码:
QLinkedListlist;
……
QString str;
foreach(str,list)
qDebug()<
用于迭代的变量也可以在foreach中定义,foreach语句也可以使用花括号,可以使用break退出迭代,如下所示:
QLinkedListlist;
……
foreach(const QString str,list)
{
if(str.isEmpty()){break;}
qDebug()<
对于QMap和QHash,foreach会自动访问“键------值”对里的值,无需调用value(),如果需要反问键则调用keys(),如下所示:
QMapmap;
……
foreach(const QString &str,map.keys())
qDebug()<map;
……
foreach(const QString &str,map.uniqueKeys()){
foreach(int i,map.value(str))
qDebug()<