Qt容器类的好处在于,它提供了平台无关的行为,以及隐式数据共享技术。所谓平台无关,即Qt容器类不因编译器的不同而具有不同的实现;所谓“隐式数据共享”,也可以称作“写时复制copy on write”,这种技术允许在容器类中使用传值参数,而不会发生额外的性能损失。
目录[隐藏] |
顺序容器
QVector<T>
QVector<T>,即向量。QVector<T>是一个类似数组的容器,它将数据存储在连续内存区域。对于获取随机位置的数据以及在尾部插入数据时效率很高,而在中间和头部插入数据时开销很大。
QVector提供了下标 [] 运算符。
在尾部添加数据使用 append() 函数,也可以用<<运算符代替append()函数。
QVector中的基本类型及指针被初始化为0,而对于类则调用其默认构造函数。
QLinkedList<T>
QLinkedList<T>是一个链表,使用指针连接起所有数据,数据的插入和删除很快,但是随机位置值的访问会很慢。
没有提供[]操作符,只能使用append()函数、<<操作符或者迭代器进行数据的添加。
QList<T>
综合了QVector<T>和QLinkedList<T>最重要的优点:
支持[]运算符;
在头部或尾部的插入/删除操作很迅速,而尺寸在1000以下时,在中间的插入/删除操作也很迅速。
除非我们需要进行在很大的集合的中间位置的添加、删除操作,或者是需要所有元素在内存中必须连续存储,否则我们应该一直使用Qlist<T>。
QStringList
QList<QString>的子类,提供针对QString的很多特殊操作。
QStack<T>
堆栈,具有push(), pop(), top()函数。
QQueue<T>
队列,具有enqueue(), dequeue(), head()函数。
迭代器
Qt支持两种风格的迭代器——Java-style和STL-style,Java-style的迭代器更容易使用,而STL-style的迭代器可以同Qt和STL中的算法联合使用,更为强大。
Java-style 迭代器
每个顺序容器类,都有两个Java-style的迭代器类型:只读迭代器和读写迭代器。
只读遍历器有QVectorIterator<T>,QLinkedListIterator<T>和QListIterator<T>三种;
读写遍历器同样也有三种,即QMutableVectorIterator<T>,QMutableLinkedListIterator<T>和QMutableListIterator<T>。
在使用Java-style的迭代器时,要清楚的第一件事情就是:迭代器并不直接指向容器中的元素,而是指向元素之前或之后的位置。迭代器被初始化时指向容器中第一个元素之前;若迭代器的右侧有元素存在,hasNext()函数返回true;next()函数返回位于迭代器右侧的元素,并将迭代器向右方移动一个元素的位置;hasPrevious()和previous()函数执行反方向的操作。
remove()函数总是删除最近一次被跳过的那个元素。
setValue()函数总是对最近一次被跳过的那个元素执行更新操作。
insert()函数在迭代器当前指向的位置处插入新元素,并将迭代器指向新元素后续元素的位置。如:
QMutableListIterator<double> i(list);
while (i.hasNext()) {
if (i.next() < 0.0)
//i.remove(); //删除
//i.setValue(value);//更改
//i.insert(value);//插入
}
STL-style 迭代器
每个顺序容器类,都有两个STL-style的迭代器类型:Container<T>::iterator和Container<T>::const_iterator,后一个返回的是只读的指针类型。
begin()函数返回指向第一个元素的STL风格的遍历器,例如list[0],而end()函数则会返回指向最后一个之后的元素的STL风格的遍历器,当容器为空时,begin()和end()的结果相同。
通常通过调用isEmpty()来检查容器是否为空,而不是通过比较begin()和end()的结果。
可以对STL-style的iterator使用+、-、*这三个运算符,类似于指针的用法。
某些Qt函数的返回值是容器类;如果需要使用STL-style的迭代器来对这样的返回值进行遍历,必须保存返回值的一个副本,并在副本上完成遍历,否则会可能会导致所谓的"dangling iterator"。
如:
QList<int> list = splitter->sizes();
QList<int>::const_iterator i = list.begin();
while (i != list.end()) {
doSomething(*i);
++i;
}
而如果你直接使用返回值,就像下面的代码:
// WRONG
QList<int>::const_iterator i = splitter->sizes().begin();
while (i != splitter->sizes().end()) {
doSomething(*i);
++i;
}
注意,若使用java-style的只读迭代器,在这种情况下会隐式的完成复制的工作,保证迭代器总是在副本上进行遍历操作。
隐式共享
Qt中的隐式共享机制的美妙之处在于它鼓励程序员在返回对象时采用传值这种简明的方式而不是引用或指针。
STL与此相反,鼓励程序员使用non-const引用来传递vector以避免将函数返回值的复制开销。
Qt中所有的容器都采用了implicit sharing机制;此外很多其他类QByteArray,QBrush,QFont,QImage,QString也采用了该机制——这保证这些类在以传值方式进行传递时有很高的效率,无论是作为参数还是函数返回值。
如果我们只进行读操作,数据是不会被复制的,只有当这些需要复制的数据需要进行写操作,这些数据才会被真正的复制,而这一切都是自动进行的,也正因为这个原因,隐式数据共享有时也被称为“写时复制”。隐式数据共享不需要我们做任何额外的操作,它是自动进行的。
在Qt提供的隐式共享机制下,对vector或list执行只读操作时,采用at()而不是[]运算符是一个更好的选择。类似的,尽可能的使用constBegin()和constEnd()以避免不必要的拷贝操作。
foreach循环
Qt提供了一种不使用遍历器进行遍历的方法:foreach循环。这实际上是一个宏,使用代码如下所示:
QLinkedList<Movie> list;
Movie movie;
...
foreach (movie, list) {
if (movie.title() == "Citizen Kane") {
std::cout << "Found Citizen Kane" << std::endl;
break;
}
}
Qt中使用宏实现了foreach循环,有两个参数,第一个是单个的对象,成为遍历对象,相当于指向容器元素类型的一个指针,第二个是一个容器类。它的意思很明确:每次取出容器中的一个元素,赋值给前面的遍历元素进行操作。
关联容器
容器中存储的一般是二元组,而不是单个的对象。二元组一般表述为<Key-Value>,也就是“键-值对”。
QMap
QMap<K, T>是一种键-值对的数据结构,它实际上使用跳表skip-list实现,按照K进行升序的方式进行存储。
使用QMap<K, T>的insert()函数可以向QMap<K, T>中插入数据。
插入和删除操作中都可以使用[]运算符,其下标为K;为避免创建不必要的空值,推荐用vlaue()而不是[]从QMap中取值。
QMap<K, T>中的K和T可以是基本数据类型,如int,double,可以是指针,或者是拥有默认构造函数、拷贝构造函数和赋值运算符的类。并且K必须要重载<运算符,因为QMap<K, T>需要按K升序进行排序。
QMap<K, T>提供了keys()和values()函数,可以获得键的集合和值的集合。这两个集合都是使用QList作为返回值的。
Map是单值类型的,也就是说,如果一个新的值分配给一个已存在的键,则旧值会被覆盖。如果你需要让一个key可以索引多个值,可以使用QMultiMap<K, T>。这个类允许一个key索引多个value,如:
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>
QHash<K, T>是使用散列存储的键-值对,提供的接口和QMap很相似。
QHash<K, T>的查找速度比QMap<K, T>快很多,并且它的存储是不排序的。对于QHash<K, T>而言,K的类型必须重载了==操作符,并且必须被全局函数qHash()所支持,这个函数用于返回key的散列值。Qt已经为int、指针、QChar、QString和QByteArray实现了qHash()函数。
QHash<K, T>会自动地为散列分配一个初始大小,并且在插入数据或者删除数据的时候改变散列的大小。我们可以使用reserve()函数扩大散列,使用squeeze()函数将散列缩小到最小大小(这个最小大小实际上是能够存储这些数据的最小空间)。在使用时,我们可以使用reserve()函数将数据项扩大到我们所期望的最大值,然后插入数据,完成之后使用squeeze()函数收缩空间。
QHash<K, T>同样也是单值类型的,但是你可以使用insertMulti()函数,或者是使用QMultiHash<K, T>类来为一个键插入多个值。另外,除了QHash<K, T>,Qt也提供了QCache<K, T>来提供缓存,QSet<K>用于仅存储key的情况。这两个类同QHash<K, T>一样具有K的类型限制。