STL 配置器和容器,序列容器和关联容器总结

来源:互联网 发布:spark排序算法 编辑:程序博客网 时间:2024/05/17 20:22

STL中定义了许多容器类型,这些类型在C++中非常实用。但是很多人却并没有理解为什么叫配置器和容器,下面做个总结:

  • 序列容器:数组、vector、list、deque、stack、queue、heap、priority-queue。这些类型存放的内容为value,并且在排放的逻辑上是一种线性结构。其中,stack、queue为配置器。为什么称他们为配置器,而不是容器呢?配置器的意思:修改某物接口,形成另一风貌的,称谓adapter(配置器)。这就需要我们深入源码去逐一分析。 
  1. vector: 代码实现为一块连续的内存,可以理解为动态扩张的array,具有随机访问的特性。但是不适合频繁的随机插入、删除。
  2. list: 代码实现为双向链表结构,适合频繁随机插入、删除的操作。
  3. deque: 双向操作队列,代码实现比较复杂。利用一个map表(非STL的map,而是类似于指针数组),用作块映射,每个地址都指向一块连续的内存,每个连续内存大小相同。deque内部通过迭代器start,finish分别指向第一个块和最后一个块。
  4. stack:栈的特性是单向操作,同一个入口、出口,FILO。在STL中,由于deque具有双向操作的功能,所以没必要单独再去实现一套stack。只需要在deque的基础上封闭一端的操作,就具备了stack的特性。因此,stack是一个adapter。
  5. queue:队列的特性是FIFO,一端进一端出。同样在deque的基础上,分别关闭一端的Input和另一端的Output,就具备了queue的特性。所以,queue也是一个adapter。
  6. heap:作为算法呈现。实现的是堆排序,里面包含了vector。
  7. priority-queue:包含了heap,用来实现优先级排序。

可以看出,实际上queue、stack的底层实现跟deque是一样的。由一段段的定量连续空间组成。

  • 关联容器:set、map、multiset、multimap、hashtable、hash_map、hash_set、hash_multiset、hash_multimap。这些类型存放的内容为key-value,并且key是唯一的,容器内部会根据key构建一个RB-Tree,或是Hash表。通过key去定位value。

(1)为何map和set的插入删除效率比用其他序列容器高?

大部分人说,很简单,因为对于关联容器来说,不需要做内存拷贝和内存移动。说对了,确实如此。set容器内所有元素都是以节点的方式来存储,其节点结构和链表差不多,指向父节点和子节点。结构图可能如下:

  A
   / \
  B C
 / \ / \
  D E F G


因此插入的时候只需要稍做变换,把节点的指针指向新的节点就可以了。删除的时候类似,稍做变换后把指向删除节点的指针指向其他节点也OK了。这里的一切操作就是指针换来换去,和内存移动没有关系。

(2)为何每次insert之后,以前保存的iterator不会失效?

iterator这里就相当于指向节点的指针,内存没有变,指向内存的指针怎么会失效呢(当然被删除的那个元素本身已经失效了)。相对于vector来说,每一次删除和插入,指针都有可能失效,调用push_back在尾部插入也是如此。因为为了保证内部数据的连续存放,iterator指向的那块内存在删除和插入过程中可能已经被其他内存覆盖或者内存已经被释放了。即使时push_back的时候,容器内部空间可能不够,需要一块新的更大的内存,只有把以前的内存释放,申请新的更大的内存,复制已有的数据元素到新的内存,最后把需要插入的元素放到最后,那么以前的内存指针自然就不可用了。特别时在和find等算法在一起使用的时候,牢记这个原则:不要使用过期的iterator。

所以,对于set、map、list而言,当客户端对它进行元素insert、delete操作时,操作之前的迭代器,在操作完成后依然有效(被删除的元素除外)。但是对于vector,则需要保证iterator不要过期。


(3)当数据元素增多时,set的插入和搜索速度变化如何?


对于set/map其实都是调用STL底层的RB-Tree来实现,对于RB-Tree,我们知道它的插入、删除效率很高,logN。所以对于大量元素的场景也是很适用。许多文件系统、分布式调度,都是利用红黑树的查找特性,存储索引或节点信息。

Set 元素不可修改?

Set 作为集合,存储的元素只有一个值,value就是key,key也是value。一般情况,Set的值是只读的,不可以修改。但是并不一定。需要根据SGI STL的版本,具体分析。

笔者用的版本就是可以通过迭代器修改值的。

ite1 = iset.find(23);if (ite1 != iset.end()){cout << "23 found" <<endl;}*ite1 = 9;

编译运行正常!后来通过翻阅set源码,才发现不同版本,对于迭代器的定义不同。只有在把iterator定义为const_iterator类型的版本中,才不允许修改Set元素。

template<class _K, class _Pr = less<_K>,class _A = allocator<_K> >class set {public:typedef set<_K, _Pr, _A> _Myt;typedef _K value_type;struct _Kfn : public unary_function<value_type, _K> {const _K& operator()(const value_type& _X) const{return (_X); }};typedef _Pr value_compare;typedef _K key_type;typedef _Pr key_compare;typedef _A allocator_type;typedef _Tree<_K, value_type, _Kfn, _Pr, _A> _Imp;typedef _Imp::size_type size_type;typedef _Imp::difference_type difference_type;typedef _Imp::reference reference;typedef _Imp::const_reference const_reference;typedef _Imp::iterator iterator;typedef _Imp::const_iterator const_iterator;};


所以,还是建议大家,在使用迭代器的时候,如果确定只读,直接使用const_iterator,最为保险。




0 0