STL小记
\(STL\)
之前面试自信认为了解STL,然后被怼成狗。现在重学。
基本组成
六大组件:
容器:一些封装数据结构的模板类,例如vector向量容器、list列表容器。
算法:STL提供了非常多的数据结构算法,他们被设计成一个个模板函数,这些算法在std命名空间中定义,大部分在algorithm中,少部分位于头文件numeric中。
迭代器:完成容器的读写,容器和算法之间的胶合剂。
函数对象(仿函数):使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了
适配器:可以使类的接口适配(模板)成用户指定的形式,从而让原本不能一起工作的两个类工作在一起。
内存分配器:为容器类模板提供自定义的内存申请和释放功能。
容器
- 它就是一些模板类的集合,但和普通模板类不同的是,容器中封装的是组织数据的方法(也就是数据结构)。
- STL 提供有 3 类标准容器,分别是序列容器、排序容器和哈希容器,其中后两类容器有时也统称为关联容器。
- STL的思想和OOP无关,是泛型编程。
迭代器
- 通过迭代器可以在不了解容器内部原理的情况下遍历容器。
- STL中迭代器一个最重要的作用就是作为容器(vector,list等)与STL算法的粘结剂,只要容器提供迭代器的接口,同一套算法代码可以利用在完全不同的容器中,这是抽象思想的经典应用。
- 常用的迭代器按功能强弱分为输入迭代器、输出迭代器、前向迭代器、双向迭代器、随机访问迭代器 5 种。
分类
输入流迭代器:从序列中读取数据
输出流迭代器:像序列中写入数据
前向迭代器:单项遍历
双向迭代器:双向遍历
随机访问迭代器:可以在序列中任意两个位置跳跃
vector
底层实现:
动态的数组,随着元素的增多,会不断的扩容,gcc每次扩大两倍,扩大之后把之前的元素转移到扩大的地方,所以随着不断地扩容,首部迭代器也会发生改变。
迭代器:
vector 容器可以随着存储元素的增加,自行申请更多的存储空间。因此,在创建 vector 对象时,我们可以直接创建一个空的 vector 容器,并不会影响后续使用该容器。
但这会产生一个问题,即在初始化空的 vector 容器时,不能使用迭代器。
除此之外,vector 容器在申请更多内存的同时,容器中的所有元素可能会被复制或移动到新的内存地址,这会导致之前创建的迭代器失效。
查找:
vector 容器可以向普通数组那样访问存储的元素,甚至对指定下标处的元素进行修改。
增加:
push_back()、emplace_back()用法一样。但是底层不一样。push_back() 向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素);而 emplace_back() 在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。
删除:
pop_back() 成员函数的用法非常简单,它不需要传入任何的参数,也没有返回值。
另外,如果不在意容器中元素的排列顺序,可以结合 swap() 和 pop_back() 函数,同样可以实现删除容器中指定位置元素的目的。
如果要删除容器中和指定元素值相同的所有元素,可以使用 remove() 函数,该函数定义在
另外还可以看到,既然通过 remove() 函数删除掉 demo 容器中的多个指定元素,该容器的大小和容量都没有改变,其剩余位置还保留了之前存储的元素。我们可以使用 erase() 成员函数删掉这些 "无用" 的元素。
如果想删除容器中所有的元素,则可以使用 clear() 成员函数
list
deque
关联式容器
关联式容器则大不一样,此类容器在存储元素值的同时,还会为各元素额外再配备一个值(又称为“键”,其本质也是一个 C++ 基础数据类型或自定义类型的元素),它的功能是在使用关联式容器的过程中,如果已知目标元素的键的值,则直接通过该键就可以找到目标元素,而无需再通过遍历整个容器的方式。
map
底层实现:红黑树
迭代器:
标准库为 map 容器配备的是双向迭代器。这意味着,map 容器迭代器只能进行 ++p、p++、--p、p--、*p 操作,并且迭代器之间只能使用 == 或者 != 运算符进行比较。
查找:
map 类模板中还提供了 find() 成员方法,它能帮我们查找指定 key 值的键值对,如果成功找到,则返回一个指向该键值对的双向迭代器;反之,其功能和 end() 方法相同。
map 类模板中对[ ]运算符进行了重载,这意味着,类似于借助数组下标可以直接访问数组中元素,通过指定的键,我们可以轻松获取 map 容器中该键对应的值。
find()和[]的区别:find()查找不到key值会返回尾迭代器,[]会增加键值对。
插入:
1、插入pair<k,v>
map.insert(pair<int, string>(x,str));
map.insert(make_pair<x,str>)
2、插入value_type
map.insert(map<int, string>::value_type (x, str));
3、[]方式
map[x] = str;
(个人最后一个用的多吧)
set
unnordered_系列
使用开链法:在冲突的位置建立链表,链表上就是冲突元素,这个东西称之为bucket,也可以叫桶。
bucket的链表不是list也不是slist,是自己实现的linked-list,bucket本身是放在一个vector中的。unnordered使用的迭代器是单向迭代器。
在hashtable设计bucket的数量上,其内置了28个质数[53, 97, 193,…,429496729],在创建hashtable时,会根据存入的元素个数选择大于等于元素个数的质数作为hashtable的容量(vector的长度),其中每个bucket所维护的linked-list长度也等于hashtable的容量。
如果插入hashtable的元素个数超过了bucket的容量,就要进行重建table操作,即找出下一个质数,创建新的buckets vector,重新计算元素在新hashtable的位置。