简介

 QVector是Qt的一个通用容器类。
 它将其项存储在相邻的内存位置,并提供快速地、基于索引的访问(QVector可以看做是一个封装了一个数组的模板类[],它内部维护一个数组,并且提供给外部一些访问的方法)。

QList/QLinkedList/QVector/QVarLengthArray选择讨论

 QList/QLinkedList/QVector/QVarLengthArray提供了类似的api和功能。它们通常可以互换,但在性能方面有所不同。下面有一些使用建议:

  1. 如果你不清楚用哪种容器,那么建议你使用QVector。QVector通常比QList有更好的性能,因为QVector总是将它的项按顺序存储在内存中,而QList在堆上分配项,除非sizeof(T)<=sizeof(void*),并且T已经使用Q_DECLARE_TYPEINFO声明为Q_MOVABLE_TYPE或Q_PRIMITIVE_TYPE。参阅QList优缺点。
  2. 不过QList在Qt API中普遍被用来传递参数和返回值。与这些API交互时,使用QList。
  3. 如果你确实需要一个真正的链表,它可以保证在链表中插入耗时为常数时间。并对项使用迭代器(iterator)而不是索引,那么你可以使用QLinkedList。

Note:QVector和QVarLengthArray都保证了兼容C语言的数组布局。但是QList没有这么做。如果你的应用程序必须与C API进行对接,那么这点可能需要注意。
Note:只要引用的项仍然在容器中,那么QLinkedList中的迭代器和QList分配在堆上的引用(heap-cllocating)将会一直存在。对QVector中的迭代器和引用和非堆分配的QList(non-heap-allocating)来说,情况相反。

QVector简单用法

 下面是一个存储整数的QVector和存储QString的QVector的一个示例。

	QVector<int> integerVector;
	QVector<QString> stringVector;

 QVector将它的项存储在vector(数组)中。通常,vector在创建时会带一个初始大小,下面代码是一个包含200个元素的QVector:

	QVector<QString> vector(200);

 vector中的元素会被自动初始化成一个默认构造值(default-constructed value)。如果你想要使用其他的值初始化vector,就通过第二个参数将其传递给构造函数。

	QVector<QString> vector(200, "Pass");

 你还可以使用fill()来用某个值填充vector。
 QVector使用从0开始(0-based)的索引,就像c++数组一样。要访问特定索引位置的项,可以使用操作符[]()。在非常量vector中,操作符[]()返回一个能被用作左值的引用:

	if (vector[0] == "Lix")
		vector[0] = "xxx";

 如果是只读访问(read-only),可以使用另一种写法at():

	for (int i = 0; i < vector.size(); ++i) {
      if (vector.at(i) == "Alfonso")
          cout << "Found Alfonso at position " << i << endl;
	}

 at()比操作符执行更快,因为他不会进行深拷贝(deep copy)。
 访问存储在QVector的数据还可以使用data()。该函数返回指向vector中第一项的指针,你可以使用这个指针直接访问和修改vector中的元素。如果你需要将QVector传递给一个接受C++数组的函数,这个指针会很有用。
 如果你想要查找vector中某个特定的值,使用indexOf()或者lastIndexOf()。前者从指定索引位置开始向前搜索,后者向后搜索。若找到匹配项,则返回匹配项索引。否则返回-1,例如:

	int i = vector.indexOf("harumi");
	if (i != -1)
		cout << "First occurrence of Harumi is at position"<< i <<endl;

 如果只是想检查vector是否包含某个值,使用contains()。如果想知道某个值在vector中出现的次数,可以使用count()。
 QVector提供了增删查改函数:insert()/replace()/remove()/prepend()/append()。对于存储量大的vector,这些函数可能会比较慢(线性时间-linear time),除了append()和replace(),因为它们需要将vector中的许多项在内存中移动一个位置(这点可以参考数组,因为是连续的顺序表,若要在中间插入或移除数据,许多元素都得移动)。如果你想要一个提供快速插入、删除的容器类,那么可以使用QList或者QLinkedList。
 和普通的c++函数不同,QVector可以通过调用resize()随时调整大小。如果新的大小大于旧的大小,QVector可能需要重新分配整个vector。QVector试图通过预分配两倍于实际数据需要的内存来减少重新分配。
 如果你预先知道QVector包含多少项,那么你可以调用reserve(),要求QVector预先分配一定数量的内存。你还可以调用capacity()来查明QVector实际分配了多少内存。
Note:使用非const操作符和函数会导致QVector对数据进行深拷贝,这是由于隐式共享(implicit sharing)。
 QVector存储的值类型必须是可分配的数据类型。这涵盖了大多数的数据类型,但是编译器不允许你将QWidget存储为值。取而代之的是,你可以存一个QWidget *。一些函数有额外的要求,例如,indexOf()和lastIndexOf()希望值类型支持==()运算符。这些需求是在每个函数的基础上进行编写的。
 和其他的容器一样,QVector提供了java风格的迭代器(QVectorIterator和QMutableVectorIterator)和stl风格的iterator(QVector::const_iterator和QVector::iterator)。不过在实际运用中,很少用到,因为你可以在QVector中使用索引。
 除了QVector,Qt也提供QVarLengthArray,这是一个非常低级的类,几乎没有优化。
 QVector不支持inserting、prepending、replacing它自己的值。如果这么做会引起应用程序报错而中止。

实际运用注意

Qt容器内存释放

  1. QVector::clear(),在Qt5.6之前,它会释放vector中的内存。但是从5.7开始,它的容量(QVector中维护的数组的空间)会被保留。要释放所有的容量,你可以这么做:

       QVector<T> v ...;
       QVector<T>().swap(v);
       Q_ASSERT(v.capacity() == 0);
    

    或者使用squeeze()

    	v.clear();
    	v.squeeze();
    

    这点很重要,在QVector中添加元素,如果只是调用clear,不释放空间,那之后的使用过程中,它一直会占着容器中最大占用量时的空间。(第一次往容器中添加了100个元素,然后clear了,你第二次添加了50个元素,此时容器内数组还占着100个元素的空间,用vector.capacity()可以检验)

  2. QVector存储的类型为指针的情况下,当QVector销毁时,存储的指针指向的空间并不会被释放,需要手动释放那些空间。比如可以这样使用:

    	for(int i = 0; i < v.size(); i++)
    	{
    		if (v[i] != nullptr)
    		{
    			delete v[i];
    			v[i] = nullptr;
    		}
    	}
    	v.clear();
    	v.squeeze();
    

    不过,Qt中提供了qDeleteAll专门用于清除容器的内存。可以往里面传入首尾迭代器或者容器来完全释放容器内存。

总结

 QVector是Qt编程中常用的容器,对它有个大致的认识很重要。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐