什么是STL
六大组件
容器(Container)
各种数据结构,如vector、list、deque、set、map等,⽤来存放数据,从实现⻆度来看,STL容器是⼀种class template。
算法(Algorithm)
各种常⽤的算法,如sort、find、copy、for_each。从实现的⻆度来看,STL算法是⼀种function template。
迭代器(Iterator)
扮演了容器与算法之间的胶合剂,共有五种类型,从实现⻆度来看,迭代器是⼀种将operator* , operator-> , operator++,operator–等指针相关操作予以重载的class template。
仿函数(Function object)
⾏为类似函数,可作为算法的某种策略。从实现⻆度来看,仿函数是⼀种重载了operator()的class 或者class template。
适配器(Adaptor)
⼀种⽤来修饰容器或者仿函数或迭代器接⼝的东⻄。
STL提供的queue 和 stack,虽然看似容器,但其实只能算是⼀种容器配接器,因为它们的底部完全借助deque, 所有操作都由底层的deque供应。
空间配置器(Allocator)
负责空间的配置与管理。从实现⻆度看,配置器是⼀个实现了动态空间配置、空间管理、空间释放的class template。
六大组件的交互关系
容器通过空间配置器取得数据存储空间
算法通过迭代器存储容器中的内容
仿函数可以协助算法完成不同的策略的变化
适配器可以修饰仿函数
STL优点
高可重用性
STL 中⼏乎所有的代码都采⽤了模板类和模版函数的⽅式实现,这相⽐于传统的由函数和类组成的库来说提供了更好的代码重⽤机会。
高性能
高移植性
将数据和操作分离
数据由容器管理,操作由算法定义。迭代器在两者之间充当“粘合剂”,以使算法可以和容器交互运作。
容器
常见的容器
序列容器
vector
list
deque
array
forward_list
关联容器
set
mutiset
map
mutimap
unordered_set
unordered_mutiset
unordered_map
unordered_mutimap
带muti的是多重集合,允许重复;
带unordered的是无序集合;
带set是普通集合,带map是键值对集合;
set,map,unordered_set,unordered_map支持快速查找、插入和删除
适配器容器
stack:是⼀个基于底层容器的栈实现,默认使用deque
queue:是⼀个基于底层容器的队列实现,默认使用deque
priority_queue:是⼀个基于底层容器的优先队列实现,默认使用vector
vector容器
底层实现
vector在堆中分配了一段连续的内存空间来存放元素
三个迭代器
first:指向vector中对象的起始字节位置
last:指向当前最后一个元素的末尾字节
end:指向整个vector容器所占用空间的末尾字节
扩容过程
固定扩容
机制:每次扩容的时候在原 capacity 的基础上加上固定的容量
优点:固定扩容⽅式空间利⽤率⽐较⾼。
缺点:考虑⼀种极端的情况,vector每次添加的元素数量刚好等于每次扩容固定增加的容量 + 1,就会造成⼀种情况,每添加⼀次元素就需要扩容⼀次,⽽扩容的时间花费⼗分⾼昂。所以固定扩容可能会⾯临多次扩容的情况,时间复杂度较⾼。
成倍扩容
机制:
每次扩容的时候原 capacity 翻倍
优点:⼀次扩容 capacity 翻倍的⽅式使得正常情况下添加元素需要扩容的次数⼤⼤减少(预留空间较多),时间复杂度较低;
缺点:因为每次扩容空间翻倍,⽽很多空间没有利⽤上,空间利⽤率不如固定扩容。
list容器
每个元素都是放在⼀块内存中,他的内存空间可以是不连续的,通过指针来进⾏数据的访问
list是⼀个环状的双向链表,同时它也满⾜STL对于“前闭后开”的原则,即在链表尾端可以加上空⽩节点
deque容器
双向队列,全称为double-ended queue,C++中deque是stack和queue默认的底层实现容器
deque是⼀个双端开⼝的连续线性空间,其内部为分段连续的空间组成,随时可以增加⼀段新的空间并链接
deque采⽤⼀块map作为主控,其中的每个元素都是指针,指向另⼀⽚连续线性空间,称之为缓存区,这个区才是
⽤来储存数据的,如下图所示。
容器间的区别
vector和list的区别
- 底层实现
vector使用动态数组实现,list使用双向链表 - 随机访问
vector是顺序内存,⽀持随机访问,list不⾏ - 内存扩容
vector⼀次性分配好内存,不够时才进⾏翻倍扩容;list每次插⼊新节点都会进⾏内存申请 - 性能
vector随机访问性能好,插⼊删除性能差;list随机访问性能差,插⼊删除性能好
map和set的区别
相同
都是C++的关联容器,只是通过它提供的接⼝对⾥⾯的元素进⾏访问,底层都是采⽤红⿊树实现。
不同
set:⽤来判断某⼀个元素是不是在⼀个组⾥⾯。
map:映射,相当于字典,把⼀个值映射成另⼀个值,可以创建字典。
map和unordered_map的区别
区别
map底层是基于红⿊树实现的,因此map内部元素排列是有序的。
⽽unordered_map底层则是基于哈希表实现的,因此其元素的排列顺序是杂乱⽆序的。
map优缺点
优点
有序性,这是map结构最⼤的优点,其元素的有序性在很多应⽤中都会简化很多的操作。
查找、删除、增加等⼀系列操作时间复杂度稳定,都为O(logn )。
缺点
查找、删除、增加等操作平均时间复杂度较慢,与n相关。
unordered_map优缺点
优点
查找、删除、添加的速度快,时间复杂度为常数级O(1)。
缺点
查找、删除、添加的时间复杂度不稳定,取决于哈希函数。极端情况下可能为O(n)。
适配器
stack和queue
栈与队列被称之为duque的配接器,其底层是以deque为底部架构。通过deque执⾏具体操作
heap和priority_queue
heap(堆)
建⽴在完全⼆叉树上,分为两种,⼤根堆,⼩根堆,其在STL中做priority_queue的助⼿,即,以任何顺序将元素推⼊容器中,然后取出时⼀定是从优先权最⾼的元素开始取,完全⼆叉树具有这样的性质,适合做priority_queue的底层
priority_queue(优先队列)
也是配接器。其内的元素不是按照被推⼊的顺序排列,⽽是⾃动取元素的权值排列,确省情况下利⽤⼀个max-heap完成,后者是以vector—表现的完全⼆叉树。
迭代器
左闭右开
在STL迭代器中,一般采用[ …. )的表示方法,也就是(iter1,iter2)中iter2表示的是最后一个元素后面的一个元素。这样做的好处在于循环时更干净利落。
迭代器有什么作⽤?什么时候迭代器会失效
作用
迭代器为不同类型的容器提供了统⼀的访问接⼝, 隐藏了底层容器的具体实现细节, 允许开发者使⽤⼀致的语法来操作不同类型的容器。
什么时候会失效
对于序列容器vector,deque来说,使⽤erase后,后边的每个元素的迭代器都会失效,后边每个元素都往前
移动⼀位,但是erase返回下⼀个有效的迭代器。
对于关联容器map,set来说,使⽤了erase后,当前元素的迭代器失效,但是其结构是红⿊树,删除当前元
素,不会影响下⼀个元素的迭代器,所以在调⽤erase之前,记录下⼀个元素的迭代器即可。
对于list来说,它使⽤了不连续分配的内存,并且它的erase⽅法也会返回下⼀个有效的迭代器,因此上⾯两种
⽅法都可以使⽤。
push_back 和 emplace_back 的区别
push_back
⽤于在容器的尾部添加⼀个元素。
container.push_back(value);
container
是⼀个⽀持 push_back
操作的容器,例如 std::vector
、std::list
等,⽽ value
是要添加的元素的值。
emplace_back
⽤于在容器的尾部直接构造⼀个元素。
container.emplace_back(args);
其中 container 是⼀个⽀持 emplace_back
操作的容器,⽽ args
是传递给元素类型的构造函数的参数。与 push_back
不同的是,emplace_back
不需要创建临时对象,⽽是直接在容器中构造新的元素。
区别:
push_back
接受⼀个已存在的对象或⼀个可转换为容器元素类型的对象,并将其复制或移动到容器中。emplace_back
直接在容器中构造元素,不需要创建临时对象。
emplace_back
通常⽐ push_back
更⾼效,因为它避免了创建和销毁临时对象的开销。
emplace_back
的参数是传递给元素类型的构造函数的参数,⽽ push_back
直接接受⼀个元素。