在哪种情况下我应该使用特定的STL容器?


184

在C ++的书中,我一直在阅读STL容器,特别是有关STL及其容器的部分。现在,我确实了解它们中的每一个都有它们自己的特定属性,并且我几乎要记住它们的所有属性……但是我还不了解的是,在每种情况下都使用它们。

有什么解释?首选示例代码。


您是指地图,vectot,set等吗?
Thomas Tempelmann,09年

即使在看这个图,我不能说什么是最好的一个用在我的quastion stackoverflow.com/questions/9329011/...
sergiol

2
@sbi:从此标签中删除C ++ Faq标记,并将其添加到更新的C ++ 11(含C ++ 11)中。如何有效地在C ++ 11中选择标准库容器?
Alok保存2012年

Answers:


336

该备忘单提供了不同容器的不错摘要。

请参阅底部的流程图,以作为在不同使用方案中使用的指南:

http://linuxsoftware.co.nz/containerchoice.png

David Moore创建并许可使用CC BY-SA 3.0


14
这个流程图是黄金,我希望我在c#中有类似的东西
Bruno

2
更新的链接:C ++容器备忘单
Bill Door

3
起点一定不能为vector空。stackoverflow.com/questions/10699265/...
eonil

5
现在unordered_map,您有和unordered_set(及其多变体)不在流程图中,但是当您不关心订单而是需要按键查找元素时,它们是不错的选择。他们的查找通常是O(1)而不是O(log n)。
2014年

2
@ shuttle87不仅大小不会改变,而且更重要的是,大小是在编译时确定的,不会改变。
YoungJohn

188

这是一个流程图,其灵感来自于我创建的David Moore的版本(请参见上文),该版本是新标准(C ++ 11)的最新(大部分)。这仅是我个人的看法,这并不是不争的事实,但是我认为这对于本次讨论可能是有价值的:

在此处输入图片说明


4
您可以提供原件吗?这是一个很棒的图表。也许贴在博客或GitHub上?
kevinarpe

1
这是一个很好的图表。虽然有人可以向我解释“持久职位”的含义吗?
IDDQD

3
@STALKER持久位置意味着,如果您有一个指向容器中元素的指针或迭代器,则无论您向容器中添加或删除了什么(只要它指向容器或容器),该指针或迭代器将保持有效(并指向同一元素)。不是有问题的元素)。
Mikael Persson

1
这确实是一个很棒的图表,但是我认为vector (sorted)与其他图表有点不一致。它不是不同类型的容器,只是相同std::vector但已排序。更重要的是,我不明白为什么不能使用a std::set进行有序迭代,如果那是迭代集合的标准行为。当然,如果答案是关于有序访问容器槽的值的话[],那么好的,您只能使用soted来做到std::vector。但无论哪种情况,都应在“需要订单”问题之后做出决定
RAs

1
@ user2019840我想将图表限制为标准容器。应该显示“ flat_set”(来自Boost.Container)或等效项(每个主要库或代码库均具有等效的flat_set,即AFAIK)来代替“排序矢量” 。但是这些都是非标准的,并且是STL中明显的遗漏。而您不想遍历std :: set或std :: map的原因(至少不经常)是因为这样做效率很低
Mikael Persson

42

简单的答案:std::vector除非您有真正的理由,否则请使用所有内容。

当您发现自己在考虑“ Gee,std::vector由于X不能在这里很好地工作”的情况时,请以X为基础。


1
但是..进行迭代时请注意不要删除/插入项...尽可能使用const_iterator避免这种情况..
vrdhn 2012年

11
嗯...我认为人们正在过度使用向量。原因是,“不起作用”的情况不会轻易发生-因此,人们坚持使用最常用的容器,并将其误用于存储列表,队列,...在我看来-与流程图相符-人们应该根据预期用途选择容器,而不要应用“一个似乎适合所有人”的容器。
黑色

13
@Black Point是,即使在理论上应该工作更慢的操作上,向量也通常更快。
Bartek Banachewicz

1
@Vardhan std::remove_if几乎总是优于“迭代时删除”方法。
fredoverflow 2014年

1
一些基准确实可以帮助该讨论减少主观性。
Felix D.

11

查看Scott Meyers撰写的有效STL。很好地解释了如何使用STL。

如果要存储确定数量/不确定数量的对象,并且永远不会删除任何对象,那么向量就是您想要的。它是C数组的默认替换,它的工作原理类似于一个,但不会溢出。您也可以使用reserve()预先设置其大小。

如果要存储数量不确定的对象,但是要添加并删除它们,则可能需要一个列表...因为可以删除元素而不移动任何后续元素-与vector不同。但是,它比向量占用更多的内存,并且您无法顺序访问元素。

如果您要使用一堆元素并仅查找那些元素的唯一值,则将它们全部读取到一个集合中即可完成此操作,并且也会对它们进行排序。

如果您有很多键/值对,并且想按键对它们进行排序,则映射很有用...但是每个键只包含一个值。如果每个键需要多个值,则可以在列表中使用向量/列表作为值,或者使用多图。

它不在STL中,而是在STL的TR1更新中:如果您有很多要通过键查找的键值对,而您不在乎它们的顺序,则可能想要使用哈希-tr1 :: unordered_map。我在Visual C ++ 7.1中使用了它,它被称为stdext :: hash_map。它具有O(1)的查找,而不是map的O(log n)的查找。


我听说过一些轶事,暗示微软的hash_map实施不是很好。希望他们的表现更好unordered_map
Mark Ransom

3
在列表中-“您无法顺序访问元素。” -我想你的意思是你不能随机访问,或直接索引元素....
托尼德尔罗伊

^是的,因为顺序访问正是a的list功能。那里有明显的错误。
underscore_d

7

我重新设计了流程图,使其具有3个属性:

  1. 我认为STL容器分为2个主要类。基本容器以及那些利用基本容器实施策略的容器。
  2. 首先,流程图应将决策过程划分为我们应该决定的主要情况,然后详细说明每种情况。
  3. 一些扩展的容器可能会选择其他基本容器作为其内部容器。流程图应考虑可以使用每个基本容器的情况。

流程图: 在此处输入图片说明

此链接中提供了更多信息。


5

只简单地提到迄今很重要的一点是,如果你需要连续的存储器(如C数组给出),则只能使用vectorarraystring

array如果在编译时知道大小,则使用此方法。

使用string,如果你只需要使用字符类型的工作,需要一个字符串,而不是只是一个通用的容器。

使用vector在所有其他情况下(vector应该是在大多数情况下,默认选择容器的反正)。

使用这三种方法,您可以使用data()成员函数获取指向容器第一个元素的指针。


3

这完全取决于您要存储的内容以及要对容器执行的操作。以下是一些我最常使用的容器类示例(非常不详尽):

vector:紧凑的布局,每个包含的对象几乎没有内存开销。高效地迭代。附加,插入和擦除操作可能很昂贵,尤其是对于复杂的对象。便宜地通过索引查找包含的对象,例如myVector [10]。在C中使用数组的地方使用。在许多简单对象(例如int)的地方使用Good。reserve()在向容器添加很多对象之前,请不要忘记使用。

list:每个包含的对象的内存开销较小。高效地迭代。附加,插入和擦除很便宜。使用您在C中使用链表的位置。

set(和multiset):每个包含的对象的显着内存开销。使用您需要快速找出该容器是否包含给定对象或有效合并容器的位置。

map(和multimap):每个包含的对象的显着内存开销。使用要存储键值对并快速按键查找值的位置。

zdan建议的备忘单上的流程图提供了更详尽的指导。


对于列表“每个所包含对象的内存开销很小”不是正确的。std :: list被实现为双链表,因此每个存储对象维护2个指针,这是不可忽视的。
汉娜哈利勒(Hanna Khalil)

我仍将每个存储对象的两个指针视为“小”。
竞标

比起什么?std :: forward_list是一个容器,主要建议为每个对象存储较少的元数据(仅一个指针)。而std :: vector每个对象保存0个元数据。因此,与其他容器相比,2个指针是不可商议的
Hanna Khalil

这完全取决于对象的大小。我已经说过,向量具有“紧凑的布局,每个包含的对象几乎没有内存开销”。我仍然要说list与set和map相比具有较小的内存开销,并且比vector的内存开销稍大。我不太确定您要尝试提高TBH的程度!
出价

由于动态分配,所有基于模式的容器都倾向于具有相当大的开销,这种开销很少是免费的。除非您当然使用的是自定义分配器。
MikeMB '17年

2

我学到的一个教训是:尝试将其包装在一个类中,因为一天更改容器类型可能会带来很多惊喜。

class CollectionOfFoo {
    Collection<Foo*> foos;
    .. delegate methods specifically 
}

当您想中断有人在此结构上执行x的操作时,它不会花很多时间,并且节省了调试时间。

为工作选择理想的数据结构:

每个数据结构都提供一些操作,这些操作可能会改变时间复杂度:

O(1),O(lg N),O(N)等

本质上,您必须做出最大的猜测,即最应该执行哪些操作,并使用具有该操作的数据结构为O(1)。

很简单,不是吗(-:


5
这不是我们为什么要使用迭代器的原因吗?
2012年

@PlatinumAzure甚至迭代器也应该是成员typedef ..如果您更改容器类型,则还必须更改所有迭代器定义...尽管确实在c ++ 1x中已解决!
vrdhn 2012年

4
对于一个好奇的人,这是C ++ 11中的修复程序: auto myIterator = whateverCollection.begin(); // <-- immune to changes of container type
Black

1
将一个typedef Collection<Foo*> CollectionOfFoo;就足够了?
Craig McQueen 2013年

5
您以后再改变主意,而只是委托给另一个容器是不太可能的:注意容器无关代码的错觉
fredoverflow 2014年


1

我在另一个问题中回答了这个问题,该问题被标记为该问题的重复项。但是我觉得很高兴参考一些有关选择标准容器的决定的好文章。

正如@David Thornley回答的那样,如果没有其他特殊需要,std :: vector是必经之路。这是C ++的创建者Bjarne Stroustrup在2014年博客中提供的建议。

这是文章https://isocpp.org/blog/2014/06/stroustrup-lists的链接

并引用其中的内容

而且,是的,我的建议是默认使用std :: vector。

在评论中,用户@NathanOliver还提供了另一个不错的博客,该博客具有更具体的度量。https://baptiste-wicht.com/posts/2012/12/cpp-benchmark-vector-list-deque.html

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.