STL剖析
1. vector
是一个封装了动态大小数组的顺序容器;数组内容器严格按照线性顺序排序,支持随机访问,因此提供随机访问指针,例如vector::iterator ivite; 并且为了降低空间配置得速度成本,vector实际分配大小要比需求大一点。size()表示目前实际存储的大小,capacity()表示分配的空间大小,是要大于size的,start表示起始地址,finish表示最后一个数据地址,end_of_storage表示分配的最后地址。
扩容: 如果容量不足,容量会扩充至二倍,如果依旧不足,那就直接扩充到足够大的容量。
构造函数
:
- vector():创建一个空vector
- vector(int nSize):创建一个vector,元素个数为nSize
- vector(int nSize,const t& t):创建一个vector,元素个数为nSize,且值均为t
- vector(const vector&):复制构造函数
- vector(begin,end):复制[begin,end)区间内另一个数组的元素到vector中
增加元素:
- void push_back(const T& x):向量尾部增加一个元素X
- void emplace_back(const T&x):向量尾部增加一个元素X,与push_back不同,后者会先构造一个临时对象,然后将其复制或移动到容器中,而emplace是直接在容器尾部调用构造函数构造对象,避免临时对象的创建和拷贝或移动。
删除函数:
- iterator erase(iterator it):删除向量中迭代器指向元素
- iterator erase(iterator first,iterator last):删除向量中[first,last)中元素
- void pop_back():删除向量中最后一个元素
- void clear():清空向量中所有元素
遍历函数:
- reference at(int pos):返回pos位置元素的引用
- reference front():返回首元素的引用
- reference back():返回尾元素的引用
- iterator begin():返回向量头指针,指向第一个元素
- iterator end():返回向量尾指针,指向向量最后一个元素的下一个位置
其他函数:
-
bool empty() const:判断向量是否为空,若为空,则向量中无元素
-
int size() const:返回向量中元素的个数
-
void swap(vector&):交换两个同类型向量的数据
-
void insert():指定位置插入元素,多种重载版本
对于insert函数,插入点后元素个数和要插入的元素个数不同,插入的算法也不相同,很神奇,研究了半天为什么这么做,其实主要就是考虑到finish后面的未初始化内存不能被copybackward函数操作,因此只能使用uninitialized_copy函数先处理这部分,先覆盖掉已经分配但未初始化的内存,但这个复制操作需要消耗性能,因此要尽可能少用。因此对于插入点后元素大于新增元素时,所以只能最后n个数据复制到finish位置后面去,虽然消耗性能,但没办法,剩下一点(前面的数据),就利用copybackward函数移动就好,最后再把新数据插入到指定位置;
但对于插入点后元素数量小于新增元素个数,说明要把最后的元素都移动到finish后面的未初始化内存中去,copybackward函数就办不到了,只能利用uninitialized_copy复制到最后去,在给指定位置填充新增元素,但要注意,uninitialized_fill_n函数性能比fill函数好,但前者只能处理未初始化内存,因此先用uninitialized_fill_n函数填充finsh后的多余未初始化内存区域(图中第一步),最后对于需要覆盖的内存(最后一步),再用fill,充分考虑性能问题。
2. list
其中 <list>
是一个非常重要的容器类,用于存储元素集合,支持双向迭代器。<list>
是 C++ 标准模板库(STL)中的一个序列容器,它允许在容器的任意位置快速插入和删除元素。与数组或向量(<vector>
)不同,<list>
不需要在创建时指定大小,并且可以在任何位置添加或删除元素,而不需要重新分配内存。可以理解为一个双向链表,分散存储在内存空间里,而不是必须存储在一整块连续的内存空间中。list基于双向链表的结构, 他所以可以在序列已知的任何位置快速插入或删除元素(时间复杂度为O(1)
)。并且在 list 容器中移动元素,也比其它容器的效率高。但是不支持随机访问。因此list提供的是双向迭代器。
相比于vector,list迭代器不会因为insert而失效,只会在erase操作时,对应的一个迭代器会失效。而vector存在严重的迭代器失效场景
迭代器:
-
begin() 返回指向容器中第一个元素的双向迭代器。
-
end() 返回指向容器中最后一个元素所在位置的下一个位置的双向迭代器。
-
rbegin() 返回指向最后一个元素的反向双向迭代器。
-
rend() 返回指向第一个元素所在位置前一个位置的反向双向迭代器。
-
cbegin() 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
-
cend() 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
-
crbegin() 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
-
crend() 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
-
empty() 判断容器中是否有元素,若无元素,则返回 true;反之,返回 false。
-
size() 返回当前容器实际包含的元素个数。
-
max_size() 返回容器所能包含元素个数的最大值。这通常是一个很大的值,一般是 232-1,所以我们很少会用到这个函数。
访问:
- front() 返回第一个元素的引用。
- back() 返回最后一个元素的引用。
- assign() 用新元素替换容器中原有内容。
- emplace_front() 在容器头部生成一个元素。该函数和 push_front() 的功能相同,但效率更高。
- push_front() 在容器头部插入一个元素。
- pop_front() 删除容器头部的一个元素。
- emplace_back() 在容器尾部直接生成一个元素。该函数和 push_back() 的功能相同,但效率更高。
- push_back() 在容器尾部插入一个元素。
- pop_back() 删除容器尾部的一个元素。
- emplace() 在容器中的指定位置插入元素。该函数和 insert() 功能相同,但效率更高。
- insert() 在容器中的指定位置插入元素。
- erase() 删除容器中一个或某区域内的元素。
- swap() 交换两个容器中的元素,必须保证这两个容器中存储的元素类型是相同的。
- resize() 调整容器的大小。
- clear() 删除容器存储的所有元素。
- splice() 将一个 list 容器中的元素插入到另一个容器的指定位置。
- remove(val) 删除容器中所有等于 val 的元素。
- remove_if() 删除容器中满足条件的元素。
- unique() 删除容器中相邻的重复元素,只保留一个。
- merge() 合并两个事先已排好序的 list 容器,并且合并之后的 list 容器依然是有序的。
- sort() 通过更改容器中元素的位置,将它们进行排序。
- reverse() 反转容器中元素的顺序
3.deque
<deque>
是标准模板库(STL)的一部分,它提供了双端队列(double-ended queue)的实现。双端队列是一种允许在两端进行插入和删除操作的线性数据结构。它提供了快速的随机访问能力,同时允许在两端进行高效的插入和删除操作。这使得 <deque>
成为处理需要频繁插入和删除元素的场景的理想选择。
与vector区别:vector虽然技术上也提供了两端数据的插入和删除,但本质是单向的连续线性空间,头部操作需要移动所有元素,效率太低‘deque允许常数时间内对端头元素增删;deque没有容量,是动态的以分段连续空间组合而成。随时可以增加一段新的空间链接起来。
虽然提供随即迭代器,但效率远不如vector,因此数组操作尽可能使用vector。并且在deque中进行sort,会先把数据拷贝在vector中,进行sort,然后在放回deque中,效率底下,因此deque只适合于在两端增删的场景——stack和queue
-
deque()
默认构造函数,创建一个空的 deque
容器。deque(size_type n)
创建一个包含 n
个默认值元素的deque
容器。deque(size_type n, const T& value)
创建一个包含 n
个值为value
的deque
容器。deque(initializer_list<T> il)
使用初始化列表 il
构造deque
容器。operator=
赋值操作符,赋值给 deque
容器。assign()
用新值替换 deque
容器中的所有元素。at(size_type pos)
返回 pos
位置的元素,并进行范围检查。operator[](size_type pos)
返回 pos
位置的元素,不进行范围检查。front()
返回第一个元素的引用。 back()
返回最后一个元素的引用。 begin()
返回指向第一个元素的迭代器。 end()
返回指向末尾元素后一位置的迭代器。 rbegin()
返回指向最后一个元素的逆向迭代器。 rend()
返回指向第一个元素之前位置的逆向迭代器。 empty()
检查容器是否为空。 size()
返回容器中的元素个数。 max_size()
返回容器可容纳的最大元素个数。 clear()
清除容器中的所有元素。 insert(iterator pos, const T& value)
在 pos
位置插入value
元素。erase(iterator pos)
移除 pos
位置的元素。push_back(const T& value)
在容器末尾添加 value
元素。pop_back()
移除容器末尾的元素。 push_front(const T& value)
在容器前端添加 value
元素。pop_front()
移除容器前端的元素。 resize(size_type count)
调整容器大小为 count
,多出部分用默认值填充。swap(deque& other)
交换两个 deque
容器的内容。get_allocator()
返回一个用于构造双端队列的分配器对象的副本。
stack: 栈,先进后出,一端进出,不允许遍历,这种改变容器接口,得到另一种结构的称为配接器
==queue:==队列,先进先出,一端只进,另一端只出,
4.set
关联容器,它存储了一组唯一的元素,并按照一定的顺序进行排序。<set>
提供了高效的元素查找、插入和删除操作。它是基于红黑树实现的,因此具有对数时间复杂度的查找、插入和删除性能。有序、不能重复。同时迭代器失效只存在于被删除元素。
insert(元素)
: 插入一个元素。erase(元素)
: 删除一个元素。find(元素)
: 查找一个元素。size()
: 返回容器中元素的数量。empty()
: 检查容器是否为空。
5.unordered_set
提供了一种基于哈希表的容器,用于存储唯一的元素集合。与 set
不同,unordered_set
不保证元素的排序,但通常提供更快的查找、插入和删除操作。
无序、不能重复、有哈希函数
insert(元素)
: 插入一个元素。erase(元素)
: 删除一个元素。find(元素)
: 查找一个元素。size()
: 返回容器中元素的数量。empty()
: 检查容器是否为空。
6. map
<map>
是标准模板库(STL)的一部分,它提供了一种关联容器,用于存储键值对(key-value pairs)。map
容器中的元素是按照键的顺序自动排序的,这使得它非常适合需要快速查找和有序数据的场景。有序、键值对、唯一、双向迭代。底层红黑树
7.unordered_map
提供了一种基于哈希表的键值对容器。与 std::map
不同,unordered_map
不保证元素的排序,但通常提供更快的查找速度。
无序、键值对、唯一
8.priority_queue
优先队列,缺省由高到低,由maxheap实现,底层是一个vector的完全二叉树。同样是一种适配器;
push:先利用push_back放入底层vector中,在利用push_heap重新排序,实现优先队列的插入
pop:先利用pop_heap,把最大(最小)元素放到vector尾部,再利用pop_back弹出。
优先队列只有顶部top元素能被访问,因此不提供迭代器
9. slist(旧版,目前C++标准库不支持)
单向链表,对应迭代器是单向的,虽然功能不如list,但slist内存小,某些操作更快,特定需求下比list性能好。
单向链表特点决定,某个节点插入数据,只能插入该节点之后,这与STL其他容器操作相反(例如list,某个点插入新数据,新数据是在原始节点之前的,vector也是),因此提供insert_after,erase_after。同时,链表尾部(back)增删数据显然效率低下,因此slist只提供push_front,pop_front。也就是虚拟头节点后增删数据,因此如果给一个slist插入数据1,2,3,4。实际单链表数据是相反的4,3,2,1有没有栈的味道?
其他容器操作相反(例如list,某个点插入新数据,新数据是在原始节点之前的,vector也是),因此提供insert_after,erase_after。同时,链表尾部(back)增删数据显然效率低下,因此slist只提供push_front,pop_front。也就是虚拟头节点后增删数据,因此如果给一个slist插入数据1,2,3,4。实际单链表数据是相反的4,3,2,1有没有栈的味道?