C++ STL总结

C++ STL分为5部分
容器,迭代器,空间适配器,函数对象,泛型算法,适配器

一、容器
理解容器的作用:
容器的主要作用是用于存储对象(这里说的对象时指的是包含基本数据类型的数据和复合数据类型实例的对象),提供一种工具来减少我们平时一些数据结构,比如说链表,队列,栈的一些操作和基础数据结构的封装。

容器分为三类:
顺序容器:比如说vetor,list,deque
(1)向量容器:vector,底层数据结构是内存连续的数组,可以动态调整数组大小(以二倍增长,先申请新的内存,然后将原来的数据放到新的内存中,释放原来的内存,将数组头指针更新为新的内存区域),未初始化时大小为0,,随机访问效率高,头部增删,中间增删操作需要移动整个数组,开销比较大。查找效率高 使用时需要引入vector头文件 在堆上分配内存
push_back O(1)
insert(it, val) O(n)
erase() O(n)
pop_back O(1)
swap() 效率

(2)双向链表list底层维护一个双向链表,内存不连续,增删数据代价小,创建时默认开辟头结点
各list操作的时间复杂度 在堆上分配内存
push_back O(1)
insert(it, val) O(1)
erase() O(1)
pop_back O(1)
swap() 效率
push_front O(1)
pop_front O(1)
splice(链表的切片)

(3)双端队列deque,我们看如下定义,来分析它的底层,内存分段连续, 8-16-32 2倍增长一维, oldSize/2 8/2 = 4 16/2 = 8
是一个分段连续的二维数组,初始第一维为8,第二维为4
C++ STL总结_第1张图片

动态开辟的二维数组 8个 4
默认没开辟空间
支持[]访问,在头尾的增删操作效率高;还有较高的随机访问效率;它支持在两端的操作:push_back,push_front,pop_back,pop_front等,并且在两端操作和List操作效率差不多。

vector只是在序列的尾端插入数据时插入时效率才高,而deque的分段管理使得不管在尾部还是前端也提供常数时间的insert和erase操作,而且在体积增长方面,vector更加有效

对比:
vector在尾部的添加删除元素,访问速度(指的是随机访问)快
list可以快速的在所有地方进行增删元素,但是只提供最后和最前面元素的快速访问
deque在最开始和最后面添加元素一样快,并提供随机随机访问方法像vector一样随机访问,但是由于自身内存部分连续的原因,效率要低于vector的随机访问

使用场景
1,如果需要高效的随机存取,不在乎插入删除的效率,推荐使用vector
2,如果你需要大量的插入和删除,而不关心随机存取,则推荐使用list
3,如果需要随机存取,而且关心两端的插入和删除,则应该使用deque

关联容器:
所谓关联容器:就是存在类似于关系数据库中的Key value键值对,按照key 进行存储对应的value值
而关联容器又分为set(集合)和map两大部分.

set:
底层数据结构:红黑树 增删查时间复杂度:O(log2n)
应用特点:数据排序了,非常合适范围查找
分为muitiset和set,unordered_set
set由节点组成的红黑树,每个节点都包含着一个元素,节点之间以某种作用于元素对的谓词排列,没有两个不同的元素能够拥有相同的次序,并且是有序的
C++ STL总结_第2张图片
mutiset:允许存在两个次序相等的元素的集合 

C++ STL总结_第3张图片
unordered_set无序set
C++ STL总结_第4张图片

map分为三类 一帮的map和unorderded_map,multimap
map是有序的,底层是红黑树,它是真正意义上的key-value键值对,因此在初始化时需要指定key,value的类型,key-value之间使用hash映射函数进行key到value的映射,常见的映射函数有:
·直接取余法:f(x):= x mod maxM ; maxM一般是不太接近 2^t 的一个质数。
·乘法取整法:f(x):=trunc((x/maxX)*maxlongit) mod maxM,主要用于实数。
·平方取中法:f(x):=(x*x div 1000 ) mod 1000000); 平方后取中间的,每位包含信息比较多。
解决hash conflict的方法有:链地址法,再哈希法,线性探测法
hash因子:已存元素个数/hash table表项个
而对于关联容器而言,特别是map三兄弟,它们都是key-value类型,所以它们的迭代器在设计上就与顺序容器的不同,map兄弟的迭代器是pair类型的,有first和last两个成员,first表示key,last表示值
并且在给map插入的时候需要进行make_pair打包一下,具体如下
C++ STL总结_第5张图片
可以看出map是有序的,并且默认排序方式是从小到大
unorderded_map是无序的,底层是真正意义上的hash_table(hash_table需要查找效率,解决hash冲突的方法),在插入时还是需要make_pair打包
底层数据结构:哈希表(链地址法、拉链法) 增删查的时间复杂度:接近O(1)【为什么是接近,因为哈希冲突/哈希碰撞是不可能避免的】
应用特点:快速的增删查,但是哈希表的数据是无序的,非常不适合范围查找
建议:80-90%都是使用哈希表,只有应用场景中需要数据有序,范围查找的时候,才会用红黑树实现的有序关联容器

multimap也是类似的,只不过提供多重映射,允许键值重复的存储

二.容器适配器
先理解什么是适配器,适配器就是一个中间层,比如你的电视机是三座的,而你的插座是二座的,现在你需要一个中间层,即插在二座的一个带有三插插板的一个中间件。这个中间件就是适配器。

STL有三种容器适配器stack,queue,priority_queue分别包装了deque,deque,和vector,三种适配器都实现对应名字的功能,stack的先进后出,queue先进先出,priority_queue默认维护一个小根堆,当然也可以指定greater<>函数对象改变默认构造方式,是它变为一个大根堆。

当然,它们的底层我们也可以自己指定,但是由于priority_queue要求支持[]所以我们可以建立在vector或者deque上,不能建立在list上vector由于要求;stack要求数据先进后出,所以可以使用任何一种数据结构,因为它们都提供了pop_back,push_back方法;queue特点是先进先出,因此要求提供pop_front的操作,所以vector不能作为queue的底层数据结构
指定容器的底层数据结构:

stack提供了5中方法
1.empty()  堆栈为空则返回真 
2.pop()  移除栈顶元素 
3.push()  在栈顶增加元素 
4.size()  返回栈中元素数目 
5.top()  返回栈顶元素 
队列提供了6种
1.back()  返回一个引用,指向最后一个元素 
2.empty()  如果队列空则返回真 
3.front()  返回第一个元素 
4.pop()  删除第一个元素 
5.push()  在末尾加入一个元素 
6.size()  返回队列中元素的个数 
优先级队列提供了5种方法
1.empty()  如果优先队列为空,则返回真 
2.pop()  删除第一个元素 
3.push()  加入一个元素 
4.size()  返回优先队列中拥有的元素的个数 
5.top()  返回优先队列中有最高优先级的元素
prioirty_queue可以用于大数top k问题

三、迭代器
迭代器提供对于容器遍历,访问的包装接口。
一般我们遍历数组访问数组元素的时候,都写出类似下面的代码
for(int i=0;i
{
cout<
}

但是现在使用STL的容器,有些就不能使用上面的方法了,瓶颈在于map底层是红黑树,需要使用迭代器进行访问和遍历
for(vector:: iterator it=vec.begin();it!=vec.end();++it)
{
cout<<*it<
}
或者是C++11的语法
for(vector it:vec)
{
cout<<*it<
}
对于顺序容器来说,迭代器是对于指针的封装(因为顺序容器的访问本质在于按照指针进行遍历),
如下
C++ STL总结_第6张图片
关联容器map
C++ STL总结_第7张图片
迭代器分为三类:
正向迭代器 iterator begin()/end()
反向迭代器 reverse_iterator rbegin()/rend()
插入行迭代器 =》 泛型算法 copy(first, last, dest);
back_insert_iterator => back_inserter => push_back
front_insert_iterator => front_inserter => push_front
insert_iterator => inserter => insert(it, val);
istream_iterator => cin
迭代器部分主要由头文 ,组成。是一个很小的头文件,它包括了贯穿使用在STL中的几个模板的声明,中提供了迭代器使用的许多方法,而对于 的描述则十分的困难,它以不同寻常的方式为容器中的 元素分配 存储空间,同时也为某些算法执行期间产生的临时对象提供机制,中的主要部分是 模板类 allocator,它负责产生所有容器中的默认分配器。
四、泛型算法
泛型其实是指C++的模板编程而言的,类型变成了参数,只能应用于STL容器中,因为大多数泛型算法都是针对于容器的迭代器而言的,如下
C++ STL总结_第8张图片
_FwdIt就是迭代器
泛型算法提供无差异的容器操作,使得代码复用提高,使用更加统一
算法部分主要由 头文件 组成。是所有STL头文件中最大的一个(然而它很好理解),它是由一大堆模版函数组成的,可以认为每个函数在很大程度上都是独立的,其中常用到的功能范围涉及到 比较、交换、查找、遍历操作、复制、修改、移除、反转、排序、合并等等体积很小,只包括几个在序列上面进行简单数学运算的模板函数,包括加法和乘法在序列上的一些操作。中则定义了一些 模板类,用以声明 函数对象。
sort(first, last)
sort(first, last, Compare); // greater/less
find(first, last, val);
find_if(first, last, Compare(val)); // greater/less + bind1st/bind2nd
copy(first, last, dest);
编写自定义的greater和Less泛型算法
template
inline bool greaterFunc(T a, T b)
{
return a > b;
}
template
inline bool lessFunc(T a, T b)
{
return a < b;
}
正向迭代器(从前往后遍历) iterator vec.begin() vec.end()
反向迭代器(保留,从后往前遍历) reverse_iterator vec.rbegin() vec.rend()
插入型迭代器 => 用在泛型算法当中的,可以通过迭代器给容器添加元素
前插型迭代器 =》 push_front
后插型迭代器 =》 push_back
按位置插入型迭代器 =》 insert
流迭代器 =》 用在泛型算法当中的
输入流迭代器 istream_iterator
输出流迭代器 ostream_iterator
五、函数对象
函数对象其实和普通对象没什么差别,只不过是在函数对象类中实现了对于()运算符的重载运算操作而已,比如下面的
template
class MyBase
{
public:
typedef T value_type;
};
template
class MyGreater : public MyBase
{
public:
bool operator()(T a, T b)
{
return a > b;
}
};
template
class MyLess : public MyBase
{
public:
bool operator()(T a, T b)
{
return a < b;
}
};
另外:C++ STL 库比较元素大小相关的函数对象,只有二元的函数对象。但是泛型算法,有的只需要一元函数对象

二元函数对象 + () ======》 一元函数对象
C++ STL库里面 函数对象的另外一个应用
绑定器 都是绑定高元函数对象 高元的函数对象+绑定器 =》低元的函数对象
bind1st =》 把二元函数对象的operator()函数的第一个参数进行绑定
bind2nd =》 把二元函数对象的operator()函数的第二个参数进行绑定
取反器
not1 =》 给一元函数对象取反 ture -> false
not2 =》 给二元函数对象取反 false -> true









你可能感兴趣的:(C++)