希望能在这里整理一下,首先看了一下《STL源码剖析》中对STL六大组件是这样介绍的:
容器(containers):各种数据结构,用来存放数据。从实现的角度看,容器是一种class template。
算法(algorithms):各种常用算法,如sort,search,copy...从实现角度看,算法是一种function template。
迭代器(iterators):扮演容器与算法之间的胶合剂,是所谓的泛型指针。共有五种类型,及其他的衍生变化。从实现角度看,迭代器是一种将operator*,operator->,operator++,operator--等指针相关操作予以重载的class template。所有STL容器都附带有自己的专属容器——是的,只有容器设计者才知道如何遍历自己的元素。
仿函数(functors):行为类似函数,可以作为算法的某种策略。从实现角度看,仿函数是重载了一种operator()的class或class template。
配接器(adapters):一种用来修饰容器或仿函数或迭代器接口的东西。配接器的实现技术很难一言以蔽之,不许逐一分析。
配置器(allocators):负责空间配置与管理。从实现的角度看,配置器是一个实现了动态空间配置,空间管理,空间释放的class template。
记得我第一次看这里的时候,对这些组件是没弄懂的,只是囫囵吞枣。看样子书还是要重复的看才行,隔一段时间再看,一些不懂的东西可能就豁然开朗了。
六大组件之间的关系如下:
根据数据在容器中的排列特性,可分为序列式容器和关联式容器:
容器
序列式容器
所谓序列式容器,其中的元素都可序,但未必有序。
vector
vector其实和标准库中的数组array十分相似。若在用C++编程中,你想到了用数组,那比较好的建议是你可以用vector。他们之间的区别是,数组是静态的,空间分配需要程序员自己来管理;而vector是动态的,由它的内部机制自己管理内存空间。
vector的内部机制,关键在于其对空间大小的控制以及重新分配时的数据移动效率。vector采用的是线性的连续空间,在新增元素时,如果超过当时的容量,则容量会扩充至现有的两倍。不是简单的增加一个元素空间,因为在连续空间中扩充空间是一个比较复杂的过程,需要重新配置、移动数据、释放原空间。但是,在erase元素时,容量大小是不会变的。
从效率上看,由于vector是连续的,所以随机读取效率很高,但是insert操作,效率比较低。若需要频繁的进行插入,vector不是很好的选择。
list
list的数据结构是一个循环双向链表,所以只需要一个指针,可以遍历整个链表。list的这种结构,使插入的效率比较高。
list和vector是两个比较常用的容器,选择哪一个必须视情况而定。list提供的元素操作很多,这里稍微列举一些:
push_front \ pop_front :插入\移除头结点
push_back \ pop_back :插入\移除尾节点
clear:移除所有节点
remove(value):将数值为value的所有元素移除
unique:移除数值相同的连续元素。只有连续相同的元素,才会被移除剩余一个。
splice(iterator position, list<T,Allocator>& x ):将x移动到pos位置之前,x必须不同于*this,x中的元素会删除
splice ( iterator position, list<T,Allocator>& x, iterator i ):将i所指元素,移动到pos之前,pos和i可以是同一个list,在x中i会被删除
splice ( iterator position, list<T,Allocator>& x, iterator first, iterator last ):将[first, last]内的所有元素移动到pos之前,pos和[first, last]可以指向同一个list,但pos不能在[first, last]之内。[first, last]将从x中删除。
merge(list& x):将x合并到*this身上,两个list的内容必须经过递增排序
reverse():将sort的内容逆向重置。
sort():list不能使用STL的算法sort,必须使用自己的成员函数sort()
list有一个重要性质:insert和splice都不会造成原有的list迭代器失效。这在vector中是不成立的,因为vector的insert操作会造成空间重新配置,原有的迭代器全部失效。要理解这句话,要结合实例思考一下。
deque
deque 顾名思义是双端队列,可以在头尾两端分别做元素的插入和删除操作。从逻辑上看,deque是一种双向开口的连续线性空间。实际上,它是由分段的连续空间组合而成。跟vector不一样,它没有容量的概念。在扩充空间的时候,不用像vector那样重新配置、移动数据、释放原空间。deque由一段一段的定量连续空间组成,一旦有必要在deque两端及头部或尾部新增加空间,便配置一段定量的连续空间,串接在整个deque的头部或尾部。
queue和stack其实是非容器,准确说应该是配接器。它们底层是用其他容器实现的,默认通过deque,用list也可以。这里暂且把他俩放在容器这块。
stack
queue
queue是一种“先进先出”的数据结构。只允许底端加入元素,顶端取出元素,没有其他方法可以存取queue的其他元素,即不可以实现遍历。queue和stack一样,被归类为配接器,不应该属于容器。也可以通过既有容器deque,list作为它的底部结构。queue也没有迭代器。
关联式容器
set
set的特性是,所有元素都会根据元素的键值进行自动排序。set元素的键值就是实值,不允许两个元素有相同的键值。不能根据set的迭代器改变set的元素值,因为元素的实值就是set的键值,改变会破坏排序规则。
set拥有和list某些相同的性质:当进行元素新增操作或删除操作后,操作之前的所有迭代器都依然有效,除了被删除的那个元素的迭代器。
set采用的底层机制是红黑树--RB-tree。红黑树是一种平衡二叉搜索树,自动排序的效果很不错。
注意一点:在set中一般不用stl中的find算法,一般用set提供的find方法更有效率。
multiset的特性以及用法 和set完全一样,唯一区别是multiset允许键值重复。因为它的插入操作采用的是RB-tree的insert_equal,而不是insert_unique。
map
map的特性是,所有的元素会根据元素的键值自动排序,map的所有元素都是pair,pair的第一元素视为键值,第二元素视为实值。map不允许元素拥有相同的键值,不可改变map的键值,但可以修正元素的实值。
map在进行新增操作或删除操作之后,操作之前的迭代器也都依然有效。因为map和set一样,底层也是采用红黑树——RB-tree来实现。
multimap的特性以及用法 和map完全一样,唯一区别是multimap允许键值重复。因为它的插入操作采用的是RB-tree的insert_equal,而不是insert_unique。
priority_queue
priority_queue 是一个拥有权值的queue,其内的元素并非按照被推入的次序排列,而是依照元素的权值排列。在默认情况下,priority queue底层是利用max heap完成,大顶堆是通过vector实现的完全二叉树——complete binary tree。
heap并不属于STL容器组件,它是个幕后英雄,扮演priority_queue的助手。binary heap是一种完全二叉树,有max heap和min heap之分,STL中采用的是max heap——大顶堆。所以priority queue允许用户以任何次序将元素推入容器内,但取出时一定从优先级最高的元素开始取。
可以思考,priority queue为什么不采用list或binary search tree作为底层机制?
若使用list,元素插入达到常数级别,但取出极值,需要对整个list线性扫描;也可先对list进行排序,这时取极值很快,但插入操作只有线性表现。
若使用binary search tree, 插入和取极值的时间复杂度都可达到o(log(n)), 但一来二叉查找树的输入需要足够的随机性(没弄懂);二来binary search tree的实现不容易。
hashtable
hash table实现是通过hash function实现的,但hash function 会带来“碰撞”问题。避免元素“碰撞”的方法比较多,常用的是二次线性探测和开链法。在STL中,hash table采用的是开链法。
hash table内的元素为bucket, 每个bucket维护一个link list,但list并不是用stl的list和slist,而是自己定义hash table node。buckets聚合体则是由vector来完成,以便有动态扩展的能力。template<class Value>
struct __hashtable_node{
__hashtable_node* next;
Value val;
}
注意一点,hashtable的迭代器没有后退操作。
hashtable的模版参数很多,要正确运用它不太容易。
template<class Value, class Key, //节点的实值型别
class HashFcn, //节点的键值型别
class ExtractKey, //从节点中取出键值的方法(函数或仿函数)
class EqualKey, //判断键值相同与否的方法(函数或仿函数)
class Alloc>//空间配置器,默认std::alloc
class hashtable{
....
}
虽然开链法(separate chaining)并不要求表格大小为质数, 但SGI STL仍以质数来设计表格大小。并且先将28个质数计算好(大约两倍的关系), 以备随时访问, 同时提供一个函数,用来查询28个质数中,最接近某数并大于某数的质数。
hash_set
hash_set是以hashtable为底层机制,RB-tree有自动排序功能而hash table没有,因此set的元素有自动排序功能,而hash_set没有。这是它俩的唯一区别,其他操作是一样的。
hash_multiset与multiset的操作基本一样,唯一区别也是无自动排序功能。
hash_map
hash_map底层机制也是hash table,与map的区别也是五排序功能。hash_multimap,亦如此。