标准STL序列容器
:vector、string、deque和list。
标准STL关联容器
:set、multiset、map和multimap。
非标准序列容器
slist和rope。slist是一个单向链表,rope本质上是一个重型字符串。(rope是一个重型string)
非标准关联容器
hash_set、hash_multiset、hash_map和hash_multimap。
vector<char>可以作为string的替代品。
vector作为标准关联容器的替代品。
有时候vector可以在时间和空间上都表现得比标准关联容器好。
几种
标准非STL容器
,包括数组、bitset、valarray、stack、queue和priority_queue。
这是所有的选项,而且可以考虑的范围和可以在它们之间的选择一样丰富。不过,STL的大多数讨论只限于容器世界的一个很窄的视野,忽略了很多关于选择适当容器的问题。就连标准都介入了这个行动,提供了以下的在vector、deque和list之间作选择的指导方案:
vector、list和deque提供给程序员不同的复杂度,因此应该这么用:vector是一种可以默认使用的序列类型,当很频繁地对序列中部进行插入和删除时应该用list,当大部分插入和删除发生在序列的头或尾时可以选择deque这种数据结构。
如果主要关心的是算法复杂度,这个方案是有理由的建议,但需要关心更多东西。我们检查一些可以补充算法复杂度的重要的容器相关问题,但首先需要介绍一种STL容器的分类方法,是连续内存容器和基于节点的容器的区别。
连续内存容器
(也叫做
基于数组的容器
)在一个或多个(动态分配)的内存块中保存它们的元素。如果一个新元素被插入或者已存元素被删除,其他在同一个内存块的元素就必须向上或者向下移动来为新元素提供空间或者填充原来被删除的元素所占的空间。这种移动影响了效率和异常安全。标准的连续内存容器是vector、string和deque。非标准的rope也是连续内存容器。
基于节点的容器
在每个内存块(动态分配)中只保存一个元素。容器元素的插入或删除只影响指向节点的指针,而不是节点自己的内容。所以当有东西插入或删除时,元素值不需要移动。表现为链表的容器——比如list和slist——是基于节点的,所有的标准关联容器也是(它们的典型实现是平衡树)。非标准的hash容器使用不同的基于节点的实现。
如果
需要可以在容器的任意位置插入一个新元素
,则要使用序列容器,关联容器做不到。
如果不需要关心元素在容器中的顺序
,hash容器就是可行的选择。否则,避免使用hash容器。
如果必须使用标准C++中的容器
,就可以除去hash容器、slist和rope。
需要哪一类迭代器
?如果必须是随机访问迭代器,在技术上就只能限于vector、deque和string,但也可能会考虑rope。如果需要双向迭代器,就用不了slist和hash容器的一般实现。
当插入或者删除数据时,是否非常在意容器内现有元素的移动?
如果是,就必须放弃连续内存容器。
容器中的数据的内存布局需要兼容C吗?
如果是,就只能用vector。
查找速度很重要吗?
如果是,就应该看看hash容器,排序的vector和标准的关联容器。
介意如果容器的底层使用了引用计数吗?
如果是,就得避开string,因为很多string的实现是用引用计数。也不能用rope,因为权威的rope实现是基于引用计数的。
需要插入和删除的事务性(transactional)语义吗?
也就是说,需要有可靠地回退(roll back)插入和删除的能力吗?如果是,就需要使用基于节点的容器。如果需要多元素插入(比如,以范围的方式)的事务性语义,就应该选择list,因为list是唯一提供多元素插入事务性语义的标准容器。事务性语义对于写异常安全代码来说非常重要。(事务性语义也可以在连续内存容器上实现,但会有一个性能开销,而且代码不那么直观。
要把迭代器、指针和引用的失效次数减到最少吗?
如果是,就应该使用基于节点的容器,因为在这些容器上进行插入和删除不会使迭代器、指针和引用失效(除非它们指向你删除的元素)。一般来说,在连续内存容器上插入和删除会使所有指向容器的迭代器、指针和引用失效。
需要具有有以下特性的序列容器吗:1)可以使用随机访问迭代器;2)只要没有删除而且插入只发生在容器结尾,指针和引用的数据就不会失效?
这个一个非常特殊的情况,但如果遇到这种情况,deque就是理想的容器。(有趣的是,当插入只在容器结尾时,deque的迭代器也可能会失效,deque是唯一一个“在迭代器失效时不会使它的指针和引用失效”的标准STL容器。)