QT不仅支持C++的STL模板库,同时自己也定义了一套容器类和与之操作的算法类,使用QT定义的这一套库,可以使在各个平台的表现相同。QT的这些容器被设计为更轻便,更安全和更容易使用。容器类是隐含共享(implicitly)的,可重入(reentrant)的和线程安全的。为容器遍历提供了两种迭代器(Java-style,STL-style),并且QT提供了foreach关键字,更加方便元素遍历。
一.连续容器:
QVector
它是QT里最常见的容器类,它对应STL中的Vector
QList
可以参考帮助文档,查看QList的具体使用方法,以下列出部分使用示例:
1.创建各种类型的vector:
QVector
QVector
2.创建一定数目项的vector
QVector
3.带初始化的创建vector
QVector
也可以使用fill赋值:
QVector
vector.fill("Yes");
// vector: ["Yes", "Yes", "Yes"]
vector.fill("oh", 5);
// vector: ["oh", "oh", "oh", "oh", "oh"]
4.QVector像c++的Vector一样提供[]下标操作,并从0项开始。还提供at()的只读操作,at()比[]更快,因为它不会导致深度复制。
if (vector[0] == "Liz")
vector[0] = "Elizabeth";
for (int i = 0; i < vector.size(); ++i) {
if (vector.at(i) == "Alfonso")
cout << "Found Alfonso at position " << i << endl;
}
5.你可以使用indexOf,lastIndexOf来查询获取某项值的索引:
QVector
vector << "A" << "B" << "C" << "B" << "A";
vector.indexOf("B"); // returns 1
vector.indexOf("B", 1); // returns 1
vector.indexOf("B", 2); // returns 3
vector.indexOf("X"); // returns -1
vector.lastIndexOf("B"); // returns 3
vector.lastIndexOf("B", 3); // returns 3
vector.lastIndexOf("B", 2); // returns 1
vector.lastIndexOf("X"); // returns -1
也可以用contains()查看是否包含某元素,返回bool值。
6.通过append,operator<<,prepend,insert添加元素。(对于较大的vector来说,在开头和中间插入项都是相当耗时的。这种情况更适合使用QLinkedList
QVector
vector.append("one");
vector.append("two");
vector.append("three");
// vector: ["one", "two", "three"]
QVector
vector << “one” << “two” << “three”;
// vector: ["one", "two", "three"]
QVector
vector.prepend("one");
vector.prepend("two");
vector.prepend("three");
// vector: ["three", "two", "one"]
QVector
vector << "alpha" << "beta" << "delta";
vector.insert(2, "gamma");
// vector: ["alpha", "beta", "gamma", "delta"]
7.size() resize() isEmpty() capacity()等和容器大小相关操作。
8.相关转化:toList() toStdVector()
QVector
vect << "red" << "green" << "blue" << "black";
QList
// list: ["red", "green", "blue", "black"]
QVector
vector << 1.2 << 0.5 << 3.14;
std::vector
(以下容器操作函数的使用将不再累述,和查阅帮助文档,并且和QVector的使用方法是一样的。)
QLinkedList
前面提到,它适合随机插入项,其原因是它的链式结构。他提供了常量时间的插入删除,却不能提供快速的随机存取操作。不提供[]操作,它的遍历元素是通过迭代器完成的。
QList
它是个数组列表,结合了上面两种结构的优点,它支持随机存取,在它的任意一端插入和删除都是非常快速的并且对于千项以上的列表,在中间插入和删除也是很快的。学过数据结构的都会清楚这三者的结构区别。如果非要每个项元素都相邻那就只能用QVector。
QString
它是QList
QStack
他们是栈和队列结构的实现,QStack提供pop() push() swap() top()操作,它继承自QVector
QQueue
二.关联容器
QSet
它提供一个键值对集合,可以快速的进行查找,
QMap
QMap是一个以升序键顺序存储键值对的数据结构,QMultiMap是QMap基础上提供可以存储多值的maps,这样就是说一个键对应多个值了。
下面是创建一个QString-int的maps
QMap
可以这样插入值
map["one"] = 1;
map["three"] = 3;
map["seven"] = 7;
也可以这样:
map.insert("twelve", 12);
查询一个值使用[] 或者value(“**”)
int num1 = map["thirteen"];
int num2 = map.value("thirteen");
查询是否存在一个值:
if (map.contains("TIMEOUT"))
timeout = map.value("TIMEOUT");
一般推荐使用contains() value()而不是[]。
QHash
QHash
QHash为它的内部哈希表自动分配最初的存储区域,并在有项被插入或者删除时重新划分所分配的区域大小。也可以调用reserve()或者squeeze()来指定或者压缩希望存储到哈希表的项的数目,以进行性能调整。通常的做法是利用我们预期的最大的项的数目来调用reserve(),然后插入数据,最后如果有多出的项,则调用squeeze()以使内存减到最小。
三.迭代器
对于每种容器都有两种风格的迭代器——java风格和STL风格。Java风格的更易于使用而以很少量性能作为了代价,而STL风格的可以结合STL的算法从而更加强大。
这里我们主讲QList和QMap的迭代器为例。
Java-Style:
Java风格的迭代器分为两种:只读迭代器,读写迭代器。只读迭代器就是Q*Iterator
Containers |
Read-only iterator |
Read-write iterator |
QList |
QListIterator |
QMutableListIterator |
QLinkedList |
QLinkedListIterator |
QMutableLinkedListIterator |
QVector |
QVectorIterator |
QMutableVectorIterator |
QSet |
QSetIterator |
QMutableSetIterator |
QMap |
QMapIterator |
QMutableMapIterator |
QHash |
QHashIterator |
QMutableHashIterator |
Java风格迭代器的有效位置:
下面是一个典型的使用例子:
QList
list << "A" << "B" << "C" << "D";
QListIterator
while (i.hasNext())
qDebug() << i.next();
下面展示如何向后遍历
QListIterator
i.toBack();
while (i.hasPrevious())
qDebug() << i.previous();
如果左边有项那么hasPrevious()将返回true。previous()返回迭代器左边的项并且往前移一个位置。可以看如图:
下表是QListIterator 的API及说明
Function |
Behavior |
toFront() |
迭代器移到最前,第一项的前 |
toBack() |
迭代器移到最后,最后一项的后面 |
hasNext() |
如果不是list的最后,就返回true |
next() |
返回下一项,并迭代器向后移一位 |
peekNext() |
返回下一项,迭代器并不移动 |
hasPrevious() |
如果不是list的最前,就返回true |
previous() |
返回前一项,并迭代器向后移一位 |
peekPrevious() |
返回前一项,迭代器并不移动 |
下面是Mutable iterator读写迭代器使用说明:
QList
QMutableListIterator
while (i.hasNext()) {
if (i.next() % 2 != 0)
i.remove();
}
下面是QMap的迭代器示例,用法和前面是类似的:
QMap
map.insert("Paris", "France");
map.insert("Guatemala City", "Guatemala");
map.insert("Mexico City", "Mexico");
map.insert("Moscow", "Russia");
...
QMutableMapIterator
while (i.hasNext()) {
if (i.next().key().endsWith("City"))
i.remove();
}
QMap
QHash
QMapIterator
while (i.hasNext()) {
i.next();
hash.insert(i.key(), i.value());
}
STL-Style:
STL风格是迭代器不仅支持Qt的通用算法,还兼容STL的。
和java风格的类似,它也有两种风格的迭代器,只读的(const_iterator)和读写的(iterator)。
Containers |
Read-only iterator |
Read-write iterator |
QList |
QList |
QList |
QLinkedList |
QLinkedList |
QLinkedList |
QVector |
QVector |
QVector |
QSet |
QSet |
QSet |
QMap |
QMap |
QMap |
QHash |
QHash |
QHash |
用过c++ STL库的就对此很容易上手。下面是QListIterator的例子:
QList
list << "A" << "B" << "C" << "D";
QList
for (i = list.begin(); i != list.end(); ++i)
*i = (*i).toLower();
STL风格的迭代器可允许的位置与java风格的有所不同
遍历需要我们自己增加缩减迭代器,例如:
QList
for (i = list.constBegin(); i != list.constEnd(); ++i)
qDebug() << *i;
QList
list << "A" << "B" << "C" << "D";
QList
while (i != list.begin()) {
--i;
*i = (*i).toLower();
}
下面是QMap的例子:
QMap
...
QMap
for (i = map.constBegin(); i != map.constEnd(); ++i)
qDebug() << i.key() << ":" << i.value();
四.Foreach关键字
下面这个用foreach去遍历QLinkedList
QLinkedList
...
QString str;
foreach (str, list)
qDebug() << str;
可以再循环里使用break
QLinkedList
...
foreach (const QString &str, list) {
if (str.isEmpty())
break;
qDebug() << str;
}
QMap 和 QHash 中,如果你想遍历键和值,你可以用iterators(更快),或者这样写:
QMap
...
foreach (const QString &str, map.keys())
qDebug() << str << ":" << map.value(str);
或者:
QMultiMap
...
foreach (const QString &str, map.uniqueKeys()) {
foreach (int i, map.values(str))
qDebug() << str << ":" << i;
}
五.类容器类(Container-Like Class)
QVarLengthArray
C++不支持在栈内存中提供可变长度的数组,例如下面:
int myfunc(int n)
{
int table[n + 1]; // WRONG
...
return table[n];
}
只能在堆内存中实现:
int myfunc(int n)
{
int *table = new int[n + 1];
...
int ret = table[n];
delete[] table;
return ret;
}
但是如果myfunc在应用程序内循环中调用非常频繁,那么堆内存分配将会变得缓慢,这种情况,QT为我们提供了QVarLengthArray来解决。
int myfunc(int n)
{
QVarLengthArray
...
return array[n];
}
值得注意的是,1.它的API是低水平的(low-level)的,他没有提供迭代器,和QVector的功能函数。2.如果值是基本类型,它将不会初始化内存。3.QVector使用隐含共享作为内存的优化,QVarLengthArray并没有提供,然而,它因为减少了经常性消费而显得性能稍微好些,特别是在紧凑的循环里。总的来说,它是为了方便用户使用在很少部分情况。
QCache
提供一个cache去存储Key-T键值对的对象。例如:
QCache
插入对象到cache
Employee *employee = new Employee;
employee->setId(37);
employee->setName("Richard Schmit");
...
cache.insert(employee->id(), employee);
QCache的好处是自动获取的对象的拥有权(ownership)。你可以指定插入对象的花费,totalCost() maxCost()。maxCost()默认是100。
QCache
QContiguousCache
QContiguousCache是一个提供连续Cache存储器的模板类。和QCache不同的是,它要求一个约束——相邻(Contiguous)。这有利于用户交互界面最普遍的数据需求。这样的约束使它比QCache消耗更少的内存和处理器周期。
简单的使用QContiguousCache的方式是使用append() prepend()
MyRecord record(int row) const
{
Q_ASSERT(row >= 0 && row < count());
while(row > cache.lastIndex())
cache.append(slowFetchRecord(cache.lastIndex()+1));
while(row < cache.firstIndex())
cache.prepend(slowFetchRecord(cache.firstIndex()-1));
return cache.at(row);
}
可以查看文档中Contiguous Cache Example的例子。
QPair
这个在STL中也是有的(pair)用来储存键值对。它用得更多的是做为函数的返回值。
看下面这个例子:存储一个QString键double值的QPair
QPair
使用first second来修改值
pair.first = "pi";
pair.second = 3.14159265358979323846;
六.算法复杂性比较
Constant time: O(1). 常数时间复杂度
Logarithmic time: O(log n). 对数时间复杂度
Linear time: O(n). 线性时间复杂度
Linear-logarithmic time: O(n log n). 线性对数时间复杂度
Quadratic time: O(n²). 平方时间复杂度
顺序容器类操作时间复杂度比较:
Index lookup |
Insertion |
Prepending |
Appending |
|
QLinkedList |
O(n) |
O(1) |
O(1) |
O(1) |
QList |
O(1) |
O(n) |
Amort. O(1) |
Amort. O(1) |
QVector |
O(1) |
O(n) |
O(n) |
Amort. O(1) |
关联容器时间复杂度比较:
|
Insertion |
|||
Average |
Worst case |
Average |
Worst case |
|
QMap |
O(log n) |
O(log n) |
O(log n) |
O(log n) |
QMultiMap |
O(log n) |
O(log n) |
O(log n) |
O(log n) |
QHash |
Amort. O(1) |
O(n) |
Amort. O(1) |
O(n) |
QSet |
Amort. O(1) |
O(n) |
Amort. O(1) |
O(n) |