STL(Standard Template Library),即标准模板库。该库提供了常用的数据结构和算法。
STL三种基本组件:
1、容器(container):容器是容纳、包含一组元素的对象。容器类库包括7种基本容器:向量(vector)、双端队列(deque)、列表(list)、集合(set)、多重集合(multiset)、映射(map)和多重映射(multimap)。其中容器又分为顺序容器和关联容器。
顺序容器:将一组具有相同的类型的元素已严格的线性形式组织起来(vector、deque、list)。
关联容器:具有根据一组索引来快速提取元素的能力(set、,multiset、map、multimsp)。
2、迭代器(iterator):迭代器提供了访问容器种每个元素的方法。对迭代器可以使用“++”运算符来获得指向下一个元素的迭代器,可以使用“*”运算符访问一个迭代器所指向的元素,也可以使用“->”运算符来访问类的成员,和指针的使用比较相似。指针本身也是一种迭代器,迭代器就是泛化的指针。
3、算法(algorithm):STL种包含七十多种算法,包括查找、排序、消除、计数等算法。
除了上面的三种基本组件,STL还包括三个辅助组件。
1、函数对象(function object):函数对象又被称为“仿函数”,可以像使用函数那样去使用它。重载“()”运算符的类的对象都可以被当作函数对象使用,函数对象是泛化的函数。用来扩展算法。
2、适配器(adapter):用于为已有对象提供新的接口的对象,适配器本身一般并不提供新的功能,只是为了改变对象的接口而存在的。
3、分配器(allocator):负责空间的配置和管理。
一、STL容器的底层实现和适用的场景
目录
(6)hash_set(哈希集合)和hash_multiset(哈希多重集合)
(7)hash_map(哈希映射)和hash_multimap(哈希多重映射)
二、适配器(adapTTTTTTTTTTTTTTTTT TTTTTTTTT T ter)
(1)vector(向量)
vector的底层实现为数组,和一般的数组不同的是,一般的数组在需要在定义时指定的数组的大小,一旦定义好之后数组的大小就不能发生改变,所以需要程序员在定义时,就确定数组的大小。而vector的大小是动态的,即可以在适用时自动扩容(只能增大,不能减少,即一旦容量升上去,就不会在降下来。)因为vector的底层是数组实现的,所以要求内存必须连续。但内存分配时,并不是在原数组的地址上向后扩容,而是会重新选择一块空间,将原来的数组拷贝过来(原因是:该数组原来地址的后面不一定有足够的空间)。所以vector在扩容上就比较浪费时间,所以在使用vector时,最好先指定数组的大小,以避免频繁的扩容浪费时间。在删除容器中元素时,和普通的数组的处理方式相同,将从该元素后面的元素全部向前移动一个位置,所以如果vector的元素类型时类时,就会导致对象的构造和析构,就会浪费大量的时间。所以当需要使用vector存储类时,一般建议使用对象的指针。
总结:适用于数据的频繁数据随机访问,且不需要频繁的插入和删除。
(2)list(链表)
list的底层实现为双向链表,和普通的链表相同,即插入和删除的时间复杂度都是O(1),但不支持数据元素的随机访问。
总结:适用于数据的频繁插入和删除,且不需要频繁的随机访问。
(3)deque(双向队列)
deque是一种双向开口(先入先出,即:头删尾插)的连续性空间。所谓的连续性,不过时让用户感觉为连续的,实际上是不连续的。duque的底层采用了“中央控制器”和缓冲区的结合方式,对外造成了整体连续的假象。“中央控制器”实际上就是使用了map。map占用一小段连续的空间,其中每个元素都是一个指针,用来记录每个缓冲区的地址,而且缓冲区的大小是固定的,默认为512b。所以每一块中存储的元素个数是相同的。
deque支持[]运算符,支持数据随机访问,在队尾插入和队头删除的效率都是O(1)。
总结:适用于需要随机存取,而且两端的数据需要插入和删除。
(4)set(集合)和 multiset(多重集合)
set的底层的数据结构是采用insert_unique方式插入红黑树(自平衡二叉查找树)
set总结:适用于去除数据中重复的元素的情况。
multiset的底层数据结构是采用insert_equal方式插入的红黑树。
(5)map(映射)和multimap(多重映射)
map的底层数据结构和set相同使用的是insert_unique方式插入红黑树,和set不同map的元素是键值对,键值对由键值(key)和实值(value)构成的,set的元素的只有一个实值。
map的用法很多:容器内元素频繁的查找,且数据的下标不一定是整形的情况。
这里需要注意的map和set内部的采用的结构是红黑树,所以会对加入容器的数据自动排序。
(6)hash_set(哈希集合)和hash_multiset(哈希多重集合)
hash_set和hash_multiset的底层实现是哈希表,由于哈希表是通过散列函数对数据进行分配空间的,所以在数据的查找上的速度为常数级。它和set和multiset的区别是它对 存入容器的数据不排序。
(7)hash_map(哈希映射)和hash_multimap(哈希多重映射)
hash_map和hash_multimap的底层实现是哈希表,它和map和multimap的区别也是对容器中的数据不排序。
这里需要注意一点:现在的c++11规定无序容器的同一命名规则为unordered_xx,所以hash_set,hash_multimap,hash_map和hash_multimap在现在c++11中的名称为:unordered_set,unordered_multiset,unordered_map和unordered_multimap。
二、适配器(adapter)
适配器的底层并没有具体数据结构实现,而是对其他STL的组件进行的一定的修改和封装。适配器的优点就在于可以让程序员选择最合适的容器来使用。
适配器分为三类:1、容器适配器 2、迭代器适配器 3、仿函数适配器
1、容器适配器
(1)stack(栈,默认基于deque(双向队列)实现)
(2)queue(队,默认基于deque(双向队列)实现)
(3)priority_queue(优先队列,默认基于vector(向量)实现)
三、分配器(allocaotr)
负责空间配置与管理。从实现的角度来看,配置器是一个实现动态空间配置、空间管理、空间释放的模板类。
STL分配器将内存管理中的分配和释放分开,其中内存分配交给alloc::allocate()负责,内存释放由alloc::dealloccate()负责;对像的构造由::construct()负责,对象的析构::destroy()负责。
同时为了提高内存管理的效率,减少申请内存造成的内存碎片问题,STL采用了两级配置器,当分配的内存大于等于128kb时,会使用第一级的空间配置器,在第一级空间适配器中内存是利用malloc()、realloc(),free()函数进行分配和释放的。当空间小于128k时,采用第二级空间配置器,第二级空间配置器采用了内存池技术,通过空闲链表来管理内存。