STL的容器代表着C++的数据结构
STL的容器可以分为以下几个大类:
一:序列容器, 有vector, list, deque, string.
二 : 关联容器, 有set, multiset, map,multimap,( hash_set, hash_map, hash_multiset, hash_multimap)
括号里的是非标准的,如果是VC8.0的话,在stdext名字空间里。
三: 其他的杂项: stack, queue, valarray, bitset
std::valarray 是表示并操作值数组的类。它支持逐元素数学运算与多种形式的广义下标运算符、切片及间接访问。
bitset一般用于解决内存放不下的问题.
顺序容器(sequential container)内的元素按其位置存储和访问,顾名思义,这些内部元素是顺序存放的;顺序容器内的元素排列次序与元素值无关,而是由元素添加到容器里的次序决定。而关联容器的元素按键(key)排序。
容器类共享部分公共接口。标准库定义的三种顺序容器类型:vector、list、deque,它们的差别仅在访问元素的方式,以及添加或删除元素相关操作的代价。顺序容器适配器包括:stack、queue和priority_queue。容器只定义了少量操作,大多数操作由算法库提供。如果两个容器提供了相同的操作,则它们的接口(函数名和参数个数)应该相同。
标准容器类
说明
顺序性容器
vector
从后面快速的插入与删除,直接访问任何元素
deque
从前面或后面快速的插入与删除,直接访问任何元素
list
双链表,从任何地方快速插入与删除
关联容器
set
快速查找,不允许重复值
multiset
快速查找,允许重复值
map
一对多映射,基于关键字快速查找,不允许重复值
multimap
一对多映射,基于关键字快速查找,允许重复值
容器类型:
vector
容器,支持快速随机访问(连续存储)
list
链表,支持快速插入/删除
deque
双端队列,支持随机访问(连续存储),两端能快速插入和删除
stack
栈
queue
队列
priority_queue
优先级队列
下表为迭代器为所有容器类型所提供的运算:
*iter
返回类型iter所指向的元素的引用
iter->mem
对iter进行解引用,并取得指定成员
++iter
给iter加1,使其指向容器中下一个元素
iter++
–iter
给iter减1,使其指向容器中前一个元素
iter–
iter1 == iter2
当两个迭代器指向同一个容器中的同一元素,或者当它们都指向
iter1 != iter2
同一个容器的超出末端的下一个位置时,两个迭代器相等。
vector和deque容器的迭代器提供了额外的运算:迭代器的算术运算和另一些关系运算,如下表所示:
iter + n
在迭代器上加(减)整数值,将产生指向容器中前面(后面)第n个元素的迭代器;
iter - n
新计算出来的迭代器必须指向容器中的元素或超出容器末端的下一位置。
iter1 += iter2
复合运算:先加(减),再赋值
iter1 -= iter2
iter1 - iter2
只适用于vector和deque
, >=, <, <=
比较迭代器的位置关系;只适用于vector和deque
关系操作符只适用于vector和deque容器,这是因为只有这两种容器为其元素提供快速、随机的访问。它们确保可根据元素位置直接有效地访问指定的容器元素。这两种容器都支持通过元素位置实现的随机访问,因此它们的迭代器可以有效地实现算术和关系运算。
迭代器范围:[first, last)是一个左闭合区间,表示范围从first开始,到last结束,但不包括last。注意:如果first不等于last,则对first反复做自增运算必须能够到达last;否则,即last位于first之前,则将发生未定义行为。
迭代器范围使用左闭合的意义:因为这样可以统一表示空集,就无需特别处理。
另外,使用迭代器时,要特别留意迭代器的可能的失效问题。
访问元素:
back()
返回容器的最后一个元素的引用。如果容器为空,则该操作未定义
front()
返回容器的第一个元素的引用。如果容器为空,则该操作未定义
c[n]
返回下标为n的元素的引用;如果n<0 or n>=size(),则该操作未定义
(注:只适用于vector和deque容器)
at[n]
返回下标为n的元素的引用;如果下标无效,则抛出异常out_of_range异常
(注:只适用于vector和deque容器)
删除元素 :
erase(p)
删除迭代器p所指向的元素。返回一个迭代器,它指向被删除的元素后面的元素。如果p指向容器内最后一个元素,则返回的迭代器指向容器的超出末端的下一个位置;如果p本身就是指向超出末端的下一个位置的迭代器,则该函数未定义
erase(b, e)
删除[b, e)内的所有元素。返回一个迭代器,它指向被删除元素段后面的元素。如果e本身就是指向超出末端的下一个位置的迭代器,则返回的迭代器也指向超出末端的下一个位置。
clear()
删除容器内的所有元素,返回void
pop_back()
删除容器内的最后一个元素,返回void。如果容器为空,则该操作未定义。
pop_front()
删除容器内的第一个元素,返回void。如果c为空容器,则该操作未定义
(注:只适用于list和deque容器)
赋值与swap:
c1 = c2
删除容器c1的所有元素,然后将c2的元素复制给c1。c1和c2的类型必须相同。
c1.swap(c2)
交换内容:调用该函数后,c1中存放的是c2原来的元素,c2中存放的是c1原来的元素。c1和c2的类型必须相同。该函数的执行速度通常要比将c2的元素复制到c1的操作快。
c.assign(b, e)
重新设置c的元素:将迭代器b和e标记的范围内所有的元素复制到c中。b和e必须不是指向c中元素的迭代器。
c.assign(n, t)
将容器c重新设置为存储n个值为t的元素。
注意:assign操作首先删除容器内所有的元素,再将参数所指定的新元素插入到容器中。
swap操作不会删除或插入任何元素,而且保证在常量时间内实现交换。由于容器内没有移动任何元素,因此迭代器不会失效。但要注意这些迭代器指向了另一个容器中的元素。
容器的选用:
vector和deque容器提供了对元素的快速访问,但付出的代价是,在容器的任意位置插入或删除元素,比在容器尾部插入和删除的开销更大,因为要保证其连续存储,需要移动元素;list类型在任何位置都能快速插入和删除,因为不需要保证连续存储,但付出的代价是元素的随机访问开销较大。特征如下:
1)与vector容器一样,在deque容器的中间insert或erase元素效率比较低;
2)不同于vector容器,deque容器提供高效地在其首部实现insert和erase的操作,就像在尾部一样;
3)与vector容器一样而不同于list容器的是,deque容器支持对所有元素的随机访问。
4)在deque容器首部或尾部删除元素则只会使指向被删除元素的迭代器失效。在deque容器的任何其他位置的插入和删除操作将使指向该容器元素的所有迭代器都失效。
容器的比较:
vector (连续的空间存储,可以使用[]操作符)快速的访问随机的元素,快速的在末尾插入元素,但是在序列中间岁间的插入,删除元素要慢,而且如果一开始分配的空间不够的话,有一个重新分配更大空间,然后拷贝的性能开销。
deque (小片的连续,小片间用链表相连,实际上内部有一个map的指针,因为知道类型,所以还是可以使用[],只是速度没有vector快)快速的访问随机的元素,快速的在开始和末尾插入元素,随机的插入,删除元素要慢,空间的重新分配要比vector快,重新分配空间后,原有的元素不需要拷贝。对deque的排序操作,可将deque先复制到vector,排序后在复制回deque。
list (每个元素间用链表相连)访问随机元素不如vector快,随机的插入元素比vector快,对每个元素分配空间,所以不存在空间不够,重新分配的情况。
set:内部元素唯一,用一棵平衡树结构来存储,因此遍历的时候就排序了,查找也比较快的哦。
map :一对一的映射的结合,key不能重复。
stack :适配器,必须结合其他的容器使用,stl中默认的内部容器是deque。先进后出,只有一个出口,不允许遍历。
queue: 是受限制的deque,内部容器一般使用list较简单。先进先出,不允许遍历。
vector 与bitset<> ,前面的可以动态改变长度。
priority_queue: 插入的元素就有优先级顺序,top出来的就是优先级最高的了
valarray 专门进行数值计算的,增加特殊的数学函数。
一些容器选用法则:
1)如果程序要求随机访问元素,则应使用vector或deque容器;
2)如果程序必须在容器的中间位置插入或删除元素,则应采用list容器;
3)如果程序不是在容器的中间位置,而是在容器首部或尾部插入或删除元素,则应采用deque容器;
4)如果只需要在读取输入时在容器的中间位置插入元素,然后需要随机访问元素,则可以在输入时将元素读入到一个list容器中,然后对容器排序,再将排序后的list容器复制到vector容器中。
5)如果程序既需要随机访问,又需要在容器的中间位置插入或删除元素,此时应当权衡哪种操作的影响较大,从而决定选择list容器还是vector或deque容器。注:此时若选择使用vector或deque容器,可以考虑只使用它们和list容器所共有的操作,比如使用迭代器而不是下标,避免随机访问元素等,这样在必要时,可以很方便地将程序改写为使用list容器。
常见的问题
1. 你需要“可以在容器的任意位置插入一个新元素”的能力吗?如果是,你需要序列容器,关联容器做不到。
你关心元素在容器中的顺序吗?如果不,散列容器就是可行的选择。否则,你要避免使用散列容器。
必须使用标准C++中的容器吗?如果是,就可以除去散列容器、slist和rope。(slist和rope。slist是一个单向链表,rope本质上是一个重型字符串。)
你需要哪一类迭代器?如果必须是随机访问迭代器,在技术上你就只能限于vector、deque和string,但你也可能会考虑rope.
当插入或者删除数据时,是否非常在意容器内现有元素的移动?如果是,你就必须放弃连续内存容器
容器中的数据的内存布局需要兼容C吗?如果是,你就只能用vector.
查找速度很重要吗?如果是,你就应该看看散列容器,排序的vector和
标准的关联容器——仅供参考。
● 你介意如果容器的底层使用了引用计数吗?如果是,你就得避开string,因为很多string的实现是用引
用计数。于是你得重新审核你的string,你可以考虑使用vector<char>
。
● 你需要插入和删除的事务性语义吗?也就是说,你需要有可靠地回退插入和删除的能力吗?如果是,你就需要使用基于节点的容器。如果你需要多元素插入的事务性语义,你就应该选择list,因为list是唯一提供多元素插入事务性语义的标准容器。事务性语义对于有兴趣写异常安全代码的程序员来说非常重要。
(事务性语义也可以在连续内存容器上实现,但会有一个性能开销,而且代码不那么直观。要了解这方面的知识,请参考Sutter的《Exceptional C++》。
● 你要把迭代器、指针和引用的失效次数减到最少吗?如果是,你就应该使用基于节点的容器,因为在这些容器上进行插入和删除不会使迭代器、指针和引用失效(除非它们指向你删除的元素)。一般来说,在连续内存容器上插入和删除会使所有指向容器的迭代器、指针和引用失效。
● 你需要具有有以下特性的序列容器吗:1)可以使用随机访问迭代器;2)只要没有删除而且插入只发生在容器结尾,指针和引用的数据就不会失效? 这个一个非常特殊的情况,但如果你遇到这种情况,deque就是你梦想的容器。(有趣的是,当插入只在容器结尾时,deque的迭代器也可能会失效,deque是唯一一个“在迭代器失效时不会使它的指针和引用失效”的标准STL容器。为什么呢?这是由实现所决定的。