QVector与QList的区别

84

我有一个整数列表,需要进行迭代,但使用数组不够好。
vectorslists有什么区别,我选择类型之前需要了解哪些内容?

为了明确起见,我已经阅读了QT文档,但这是我所知道的全部内容:

QList<T>QLinkedList<T>QVector<T>提供类似的功能。以下是概述:

  • 对于大多数情况,QList是要使用的正确类。它基于索引的接口比QLinkedList's基于迭代器的接口更方便,并且由于内存存储方式的原因,通常比QVector更快。它还会扩展到可执行文件中的代码较少。
  • 如果您需要真正的链表,保证在列表中间具有恒定时间插入,并且具有项目而不是索引的迭代器,请使用QLinkedList
  • 如果您希望项目占用相邻的内存位置,请使用QVector
5个回答

126

QVector很大程度上类似于std::vector,你可以从名称中猜测到。 QList更接近于boost::ptr_deque,尽管与std::list有明显联系。它不直接存储对象,而是存储指向它们的指针。你可以在两端快速插入,并且重新分配只涉及指针移动而不是复制构造函数,但会失去实际std::dequestd::vector的空间局部性,并获得许多堆分配。它确实需要一些决策来避免小对象的堆分配,以恢复空间局部性,但据我所知,它仅适用于比int小的东西。

QLinkedList类似于std::list,并具有所有其缺点。一般来说,这应该是您最后选择的容器。

QT库非常喜欢使用QList对象,因此在您自己的代码中也应优先考虑使用它,有时可以避免一些不必要的繁琐操作。额外的堆使用和实际数据的随机定位在某些情况下理论上可能会有所损失,但通常是不可感知的。因此,我建议在性能分析表明需要更改为QVector之前使用QList。如果您希望连续分配很重要[即:您正在与期望T[]而不是QList<T>的代码进行交互],这也可以成为从一开始就使用QVector的原因。


如果您询问容器的一般信息,并只是将QT文档作为参考,则上述信息较少有用。

std::vector是可以调整大小的数组。所有元素都存储在一起,您可以快速访问单个元素。缺点是插入只在一端有效率。如果在中间或开头放置某些内容,则必须复制其他对象以腾出空间。在大O符号表示法中,插入到末尾的时间复杂度为O(1),插入到任何其他位置的时间复杂度为O(N),随机访问的时间复杂度为O(1)。

std::deque类似,但不能保证对象相邻存储,并且允许在两端插入为O(1)。它还需要分配较小的内存块,这有时很重要。随机访问的时间复杂度为O(1),在中间插入的时间复杂度为O(N),与vector相同。空间局部性比std::vector差,但对象倾向于聚集在一起,因此您会获得一些好处。

一个std::list是一个链表。它需要最多的内存开销,但提供快速的插入,只要您事先知道需要插入的位置。它不提供对单个元素的随机访问,因此必须以O(N)迭代。但一旦到达那里,实际插入就是O(1)。 std::list的最大优点是可以快速地将它们拼接在一起...如果您将整个值范围移动到不同的std::list中,则整个操作是O(1)。它也更难使列表中的引用失效,这有时可能很重要。
通常情况下,我更喜欢std::deque而不是std::vector,除非我需要能够将数据传递给期望原始数组的库。 std::vector保证连续,因此&v[0]适用于此目的。我不记得上一次使用std::list是什么时候了,但几乎肯定是因为我需要更强的引用有效性保证。

就此而言,std::deque对于引用有相当好的保证。迭代器很容易失效,但成员指针对于deque上的操作非常强大。 - Ben
很棒的答案。但我有一个额外的问题:哪种迭代速度更快?我有一组对象,它们并没有经常插入或删除,但我需要尽可能快地迭代这些对象。哪个更快? - birgersp
好的信息。我发现以下声明最有用...“QT库非常偏爱使用QList对象”。在我的用例中,我正在处理一个QTableWidget,它喜欢QList和QListString。因此,让你的用例决定你在QVector和QList之间做出的决策。 - panofish
1
你有实际对比过 std::dequestd::vector 的性能吗?你会感到惊讶的... - Marc Mutz - mmutz
4
请注意,已更新文档以响应Qt开发人员的投诉,称QList不是一个好的默认容器建议。现在文档说明:**QVector 应该是您的首选容器。[...] 然而,在整个Qt API中均使用 QList 用于传递参数和返回值。使用 QList 与这些API进行交互。**" (来自QList文档) - AntonyG
此外,在Qt 6中,QVector已被移除并替换为QList的别名,而QList已被替换为QVector的实现,从而使这两个类相同,并等同于旧的QVector。 - LoPiTaL

87

事情已经改变了

现在我们使用的是Qt 5.8,因此文档也随之改变。它对这个问题提供了一个清晰而不同的答案:

QVector 应该是你的默认首选。由于 QVector<T> 总是按顺序在内存中存储其项,而 QList<T> 则除非 sizeof(T) <= sizeof(void*) 并且使用 Q_DECLARE_TYPEINFO 声明 T 为 Q_MOVABLE_TYPEQ_PRIMITIVE_TYPE,否则将在堆上分配其项,因此 QVector<T> 通常会比 QList<T> 更好性能。

请参阅使用 QList 的优缺点进行解释。然而,在 Qt API 中,QList 用于传递参数和返回值。请使用 QList 与这些API进行交互。


5
应将此选项上升,并现在接受为答案。 - ymoreau
3
在Qt 6中,QVector已被删除并替换为QList的别名,而QList则被替换为QVector的实现,从而使得这两个类相同且等同于旧的QVector。 - LoPiTaL
除了让事情变得混乱,那是什么原因呢?你有链接吗? - KcFnMi
@KcFnMi Qt解释:"在过去,Qt有非常不同的实现方式[...]:QVector是一个自然而直接的类似数组的容器,而QList在其实现上相对特殊,以便很好地适应Qt自身定义和使用的类型。随着Qt 6对现有类型的更新[...],似乎在这些类之间保持实现差异没有多少好处。此外,QVector在许多情况下被证明是更好的选择。 因此,在Qt 6中,QVector和QList被统一,并使用QVector的模型..." - undefined

13

QVector类似于std::vectorQLinkedList类似于std::listQList是一种基于索引的向量,但内存位置不保证(类似于std::deque)。


3

来自QtList文档:

  • 大多数情况下使用QList。对于具有数千个项目的结构,使在中间插入变得高效,并提供索引访问。由于内部数组的两端预先分配了内存,因此prepend()append()非常快速。 QList<T>是类型为T的指针数组。如果T具有指针或类似Qt共享指针的类型,则对象直接存储在数组中。

  • 在需要大量使用append()insert()添加新项且其大小大于指针的情况下,请优先使用QVector。因为QVector会在单个堆分配中为其项目分配内存。对于QList,插入或追加新项需要在堆上对新项进行内存分配。简而言之,如果您希望项目占用相邻的内存位置,或者如果您的项目大于指针并且希望避免在插入时单独在堆上分配它们的开销,则使用QVector


-3

QVector 就像一个可以改变大小(增加或减少)的数组,但它带有繁重的事务和计算以及时间。

例如,如果您想要添加一个项目,将创建一个新数组,所有项目都将复制到新数组中,新项目将添加到末尾,然后删除旧数组。删除也是如此。

然而,QLinkedList 使用指针工作。因此,当创建新项时,只需分配新的内存空间并链接到唯一的内存块即可。由于它使用指针,因此更快且更有效。

如果您有不希望大小发生太多变化的项目列表,则QVector 可能很好,但通常QLinkedList 用于大多数目的。


4
QVector并不像你所说的那样带有繁重的事务处理,因为它预分配了一定空间,并且具有很好的增加/缩小策略来进行附加和弹出操作。虽然在插入操作或者在前面添加元素时可能需要移动数据范围,但是由于它们在内存中是连续的,因此这个过程非常快速(与QList不同,cache可以很好地处理连续数据,而不是散布在堆中的碎片)。你对于QLinkedList的第二个说法是错误的,它实际上非常罕见。也许你把它跟QList混淆了? - DerManu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接