Qt提供了多个基于模板的容器类,这些容器类可用于支持指定的数据类型,和STL容器相比,它们更加轻巧,安全,使用方便。这些容器类在很多方面进行了优化,例如优化速度,降低内存消耗,减少代码膨胀。它们都是隐式共享,写时复制并且它们支持线程可重入,在只读情况下,多线程访问同一对象是线程安全的。
在访问容器中的数据项时,可以使用两种风格的迭代器:Java风格迭代器和STL风格迭代。Qt还提供了foreach关键字来方便的访问容器中的所有数据。
Qt提供如下的顺序容器:QList,QLinkedList,QVector,QStack和QQueue。对于大多应用,使用QList是很好的选择。QList是一个数组链表,其在表头和表尾插入数据的速度很快;如果你需要一个真正的链表,可以使用QLinkedList;QVector在内存开辟了连续的空间;QStack和QQueue则对应LIFO和FIFO操作。
Qt也提供一些关联容器:QMap,QMultiMap, QHash,QMultiHash,和QSet。前缀为"Multi"容器意味着支持重复键值。QHash具有更快的查找效率。
容器 | 说明 |
---|---|
QList | 这是个经常使用的容器类。尽管使用数组链表来实现,但通过索引来访问数据项使其访问速度很快。可以使用QList::append() and QList::prepend()在链表的首尾进行添加,使用Qlist::insert()在链表的中间插入。与其他容器类相比,QList经过高度了优化。 |
QLinkedList | 除了只能使用迭代器来访问表中的数据项之外,QLinkedList与QList相似。在数据量比较大的链表中间插入数据时,QLinkedList比QList具有更高的性能。 |
QVector | 这个类是个动态数组,在内存中开辟连续的空间。在数组头部和中间插入数据的速度很慢,因为可能导致大量的数据项在内存中移动。 |
QStack | 该类继承QVector,提供LIFO操作。 |
QQueue | 该类继承QList,提供了FIFO操作。 |
QSet | 该类提供了数据项不允许重复的集合,查找速度很快。 |
QMap | 该类提供以字典形式的键值对。一般的每个key对应一个value(也可以设置重复key)。QMap会根据key值进行排序,如果对顺序没有要求,可以用速度更快的QHash进行替换。 |
QMultiMap | 该类继承QMap,允许重复key。 |
QHash | 该类的API与QMap几乎一样,但是其提供更快的查找速度并且无序存储。 |
QMultiHash | 继承QHash,支持重复key。 |
Qt容器类中的数据项需要提供默认的构造函数,复制构造以及赋值运算符。大部分希望存储在容器中的数据类型都被覆盖,例如基本数据类型int,double,pointer以及Qt的QString,QDate和QTimer,但是不包括QObject和其子类。因为QObject的复制构造和赋值运算符都被禁止,如果你想将该类型存入容器中,可以使用指针而不是对象本身。
Qt容器大部分文档都提到了默认值。例如QVector会使用自动默认构造函数初始化数据项,如果QMap不存在对应key,则其value()函数会返回调用默认构造函数的值。Qt容器对于大部分类型是使用默认构造函数创建对象,但对于基本类型int,double,pointer等Qt会将它们初始化为0。
对于每个容器类,都有两种STL风格的迭代器类型:一种是只读迭代器,另一种是读写迭代器。应尽可能的使用只读迭代器因为它别读写迭代器的速度更快。STL迭代器就类似于数组指针。例如++运算符将迭代器指向下一个元素,运算符返回迭代器指向的数据项。实际上,对于QVector和QStack存储在连续的内存中,读写迭代器的类型就相当于T*,只读迭代器的类型相当于const T*。
Qt有几十个函数会返回QList,QStringList。像这样复制一个容器看起来似乎消耗比较大,实际上不然,这是由于采用了名为隐式共享的优化过程。这意味着复制一个Qt容器的速度大致就像复制一个简单指针一样快。若几个容器对象共享同一数据,只有在某个对象对数据进行改变,数据才会被实际复制,由于这个原因隐式共享有时也称写时复制。Qt对所有的容器和许多其他类都使用了隐式共享,使得这些类不论是作为函数的参数还是返回值,都可以非常有效的传递。隐式共享的原理运用了引用计数。例如:
QString str1 = “hello world!”;
QString str2 = str1;
此时str1和str2都指向内存中相同的数据。数据结构保存一个引用计数,以指出有多少QString指向相同的数据。因为str1和str2都指向相同的数据,所以引用计数为2。
str2[0]=’d’;
当修改str2时,它会进行深拷贝,以确保str1和str2指向不同的数据。此时,str1的数据的引用计数为1,str2的数据引用计数也为1。引用计数为1表示数据未被共享。
str1=str2;
当将str2赋给str1时,str1的数据引用计数降为0,str1的数据在内存中释放。此时,str1和str2指向相同的数据,引用计数为2。
在使用隐式共享类的时候有两点需要注意:
下面以QMap为例,验证隐式共享:
//创建一个测试类打印默认构造,复制构造,析构
MyItem::MyItem()
{
qDebug()<<"MyItem";
}
MyItem::MyItem(const MyItem &obj)
{
qDebug()<<"MyItem Copy()";
}
MyItem::~MyItem()
{
qDebug()<<"MyItem delete";
}
当map数据项未发生改变时:
MyItem item;
QMap<int, MyItem> map;
map.insert(1, item);
map.insert(2, item);
map.insert(3, item);
QMap<int, MyItem> map1 = map;
打印输出为:
MyItem
MyItem Copy()
MyItem Copy()
MyItem Copy()
当map数据项发生改变时:
MyItem item;
QMap<int, MyItem> map;
map.insert(1, item);
map.insert(2, item);
map.insert(3, item);
QMap<int, MyItem> map1 = map;
map1.insert(1, item);
打印输出为:
MyItem
MyItem Copy()
MyItem Copy()
MyItem Copy()
MyItem Copy()
MyItem Copy()
MyItem Copy()
使用可读写iterator测试:
MyItem item;
QMap<int, MyItem> map;
map.insert(1, item);
map.insert(2, item);
map.insert(3, item);
QMap<int, MyItem> map1 = map;
QMap<int, MyItem>::iterator ite = map.begin();
打印输出:
MyItem
MyItem Copy()
MyItem Copy()
MyItem Copy()
MyItem Copy()
MyItem Copy()
MyItem Copy()
由结果可知,当数据未发生改变时,两个map复制构造了3个对象,并且两个map中数据项地址完全一致,证明运用了隐式共享同一内存。当map1数据发生改变,则重新开辟空间并进行深拷贝,两个map中数据项的地址不再一致。而两个map数据未改变,使用begin()函数,该函数返回可读写的iterator,此时也会进行深拷贝。使用[]运算符结果与读写iterator一致。