STL源码剖析——容器

南笙酒味 提交于 2020-03-12 07:31:32

一.模板特化

针对任何模板参数更进一步的条件限制所设计出来的一个特化版本,如:

template<typename T>
class C{...};//泛化版本,可以接受T为任何型别
template<typename T>
class C<T*>{...};//特化版本,仅适合于T为原生指针时的情况

二.设计容器必须定义的型别

  • value_type
  • difference_type
  • reference_type
  • pointer_type
  • iterator_category:
    其中设计实现了只读/只写迭代器,允许写入型,可双向移动,随机访问迭代器这5类
    总结
    traits编程技法(特性萃取)大量的实现大大提高了STL设计的便利性。需要注意设计正确的型别是迭代器的职责,而设计正确的迭代器则属于容器的职责。

三.deque

deque与vector的差别

  • deque除了可以像vector那样在尾部以O(1)的时间复杂度完成插入和删除之外,还可以实现在头部以O(1)的代价插入元素
  • deque没有所谓的容量的概念,这个和它底层的空间组织形式有关,所以不提供reserve成员函数来限定容量
  • deque的底层实现原理限制了对他的使用范围,尤其使得迭代器设计极其复杂,所以在选择使用对应的容器时要综合考虑,尤其是设计到排序等使用场景是尽量使用vector容器

deque的底层构造图示
deque构造
deque的迭代器设计
对于每一个缓冲区拥有对应的迭代器指向缓冲区的开始和结束位置,对于每一个缓冲区,其在对应的中控器map中也有对应的指向指针。。。,同时还维护start与finish两个迭代器分别指向第一个缓冲区的第一个元素和最后一个缓冲区的最后一个元素(尾后)
deque作为stack与queue的队列
stack与queue的使用特性决定了他们更适合于使用双端开口的容器作为底层实现,所以我们可以使用deque于list作为他们的底层实现,默认是deque,又由于queue与stack的使用属性,所以设计的时候并未为他们设计私人的迭代器。

四.心心念念的优先队列

原来所谓的优先队列确实是我们都知道的堆,但其默认使用vector作为底层容器,若未指定具体的权值规则,会按照大根堆的方式来设计优先队列。因此他本质上也和queue一样,也是容器配接器,没有自己的迭代器,因为用不着呀!原本还以为是自己维护空间来着,最后要了解几个建堆的函数:

  • push_heap

default (1)

template <class RandomAccessIterator>
  void push_heap (RandomAccessIterator first, RandomAccessIterator last);

custom (2)

template <class RandomAccessIterator, class Compare>
  void push_heap (RandomAccessIterator first, RandomAccessIterator last,
                   Compare comp);

注意在使用push_heap之前应该保证新元素已经位于底层容器的尾部,否则函数的行为将是未定义的,对于第一个版本,默认是构造大根堆的形式,其实就是缺省使用了less<T>,若要使用小根堆可以调用第二个函数,使用STL中的greater<T>

  • pop_heap
    具体构造就不写了,需要注意的是它仅仅是将pop出去的头部元素放在底层容器的尾后而已,如果想要真正的移除它最好调用底层容器的pop_back方法。
  • sort_heap
    本质就是多次调用pop_heap操作,最终实现堆排序的过程,这和我们使用数组时的堆排序过程一毛一样,下面是伪代码:
while(last-first>1){
    pop_heap(first,last--);
}
  • make_heap
    就是将一个序列构造成为一个堆,一般是针对vector进行类似的操作
    需要注意的是上面的四个函数都有最上面push_heap的版本2类型模板函数,支持自定义建堆

五.hashtable的构造

常用的hash函数:直接映射法,平方取中法,除余法
解决hash冲突的方法:开放寻址法(线性、二次探测),拉链法,新建缓冲区法
线性探测法存在主集团的文件,尤其是大量hash的时候;二次探测法则解决了主集团冲突的问题,但是仍旧存在次集团的问题(毕竟仍然有可能两个元素有一样的hash值,之后平方相加减的问题依旧存在)
hashtable的桶子
常常只有在讨论拉链法时我们才会将其叫做桶子,而且桶子的个数要设置为质数个,这样可以减小hash冲突的概率。并且桶子维护的链表不是STL中的list而是自己设计的hashtable_node,至于桶子的聚合体则仍然是vector,以便hash具有动态增长的能力,这也就是为什么我们说hash的阔容方式和vector一样的根本原因之所在。
各种set与map
神奇的C++ STL,设计的也太巧妙了,瞧一瞧源码就会发现hashset与hashmap的相关操作都会调用hashtable的基础接口,而hashtable就是我们前面说的以vector为底层容器的桶式拉链法hashtable,至于在C++11 中要特别注意学习使用以下四个hash容器:
unordered_map与unordered_map
unordered_multimap与unordered_multiset

//unordered_multiset平常使用只要指定型别即可
template < class Key,                         // unordered_multiset::key_type/value_type
           class Hash = hash<Key>,            // unordered_multiset::hasher
           class Pred = equal_to<Key>,        // unordered_multiset::key_equal
           class Alloc = allocator<Key>       // unordered_multiset::allocator_type
           > class unordered_multiset;
//unordered_multimap平常只要指定键值与实值的类型即可
template < class Key,                                    // unordered_multimap::key_type
           class T,                                      // unordered_multimap::mapped_type
           class Hash = hash<Key>,                       // unordered_multimap::hasher
           class Pred = equal_to<Key>,                   // unordered_multimap::key_equal
           class Alloc = allocator< pair<const Key,T> >  // unordered_multimap::allocator_type
           > class unordered_multimap;

此处省去unordered_set与unordered_map的定义式,来源于cplusplus.com,若深入下去就会发现对于这些容器的实现底层都是基于hashtable的,只不过unordered类型的和unordered_multi类型的两类容器底层采用的插入方式不同罢了,unordered_set不支持重复元素,而unordered_multiset则支持。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!