STL标准模板库内容整理
STL标准模板库(重点):C++工具(类模板与函数模板)
STL标准模板库
本质上就是数据结构和算法
C语言标准库未直接提供
C++标准库直接提供
- 定义:高效C程序库,含基本数据结构和算法,属C标准库,采用泛型编程
泛型编程:抽象数据类型,用泛型代替具体类型,编写通用代码
六大组件
容器(重要):存储数据(数据结构)
- 序列式容器:vector、list、deque等
- 关联式容器:set、map等
- 无序关联式容器:unordered_set、unordered_map等
迭代器:访问容器元素,泛型指针(例:vector::iterator)
算法:操作容器元素的普通函数(例:std::sort)
适配器:适配作用
- 容器适配器:stack、queue、priority_queue
- 迭代器适配器
- 函数适配器:bind、bind1st、bind2nd、function等
函数对象:实现定制化操作
空间配置器:管理内存(使用、原理、源码)
六大组件
容器
作用:存放数据
1、序列式容器
模型理解
array
- 静态数组,大小固定的数组
vector
动态数组
push_back
- GCC:2倍增长
- VC++:1.5倍增长
支持随机访问迭代器
逻辑结构和物理结构一致
deque
双端队列
支持随机访问迭代器
逻辑结构
连续空间
- 双端开口
物理结构
在内存中的表现形式
由多个片段构成的
- 片段内部是连续的,但是片段之间不连续
- 由中控器进行操作,连接N个片段
forward_list
- 单链表
- 支持前向访问迭代器
list
双向链表
支持双向访问迭代器
物理结构
- 循环双向链表
逻辑结构
- 双向链表
基本操作
初始化:无参、count个value、迭代器范围、拷贝/移动、大括号范围
访问首地址
vector<int> nums1 = {1,2,3,4,5}; - //vector对象的首地址 - cout << &nums1 << endl;
//查看vector首元素的地址 - cout << &*nums1.begin() << endl; - cout << &nums1[0] << endl; - cout << &nums1.at(0) << endl;//有越界的判断 - cout << &nums1.front() << endl; - cout << nums1.data() << endl;
遍历:均支持迭代器和增强for循环;vector与deque支持下标,list不支持
auto it = nums.begin();
list
::iterator it2; - it2 = nums.begin()
auto & ele : nums
尾部插入删除:三者均支持
头部插入删除:deque与list支持,vector不支持(vector头部操作复杂度O(N))
源码阅读(了解)
- vector:迭代器为_Tp*,含动态扩容机制
- deque:迭代器较复杂,含多个指针,缓冲区大小与元素类型相关
insert操作(重要):均支持在任意位置插入,返回首插入元素迭代器;vector插入可能因扩容导致迭代器失效,需更新
扩容
- //(1) t < n - m 不会扩容
- //(2) n - m < t < m 按照2*size()进行扩容
- //(3) n - m < t, m < t 按照t + m进行扩容
插入方式
(it,80)
(it,5,80)
(it,.begin(),.end())
- 迭代
(it,{80})
- 大括号
erase操作(重要):删除单个或多个元素,返回被删元素后一位迭代器;list和deque需更新迭代器,vector删除连续元素可能漏元素
- 从逻辑的角度上来说,对应的元素已经变成了被删元素的下一位
元素清空:均有clear、size;vector与deque有shrink_to_fit;vector有capacity
其他成员函数:swap、resize、front、back
emplace_back函数:尾部直接构造对象,比push_back高效(emplace与insert类似)
list的特殊操作(重要)
- sort函数:排序,可自定义比较规则(底层归并排序)
- reverse函数:反转元素顺序(与vector的reserve区分)
- unique函数:去重(需先排序)
- merge函数:合并两个有序list(合并后原list元素清空)
- remove/remove_if函数:移除等于value的元素/符合条件的元素
- splice函数:移动元素到指定位置(注意范围交叉问题,可实现LRU)
线性容器总结
时空效率
1、需要频繁的在容器中间位置添加/删除元素
list
- O(1)
2、需要频繁的在容器头部添加/删除元素
- deque/list
3、需要频繁的在容器尾部添加/删除元素
vector/deque/list
- vector
4、需要频繁的在容器头部/尾部添加/删除元素
deque/list
- deque
5、需要频繁的访问任意位置上的元素
vector/deque
- vector
2、关联式容器
共性
底层实现
红黑树
- 近似的平衡二叉树
查找元素的时间复杂度为O(logN)
- 二分查找
默认情况下会按照升序的方式进行排列
- 如果希望自定义排序方式,可以定制第二个模板参数
不能修改关键字的值
支持的是双向访问迭代器
set
特征:存key,key唯一,默认升序
- 不能存储重复的关键字key
- 不支持下标访问运算符
构造:无参、迭代器范围、拷贝、初始化列表
操作:查找、insert(无头部/尾部操作)、erase;不支持下标和修改元素
insert
- 直接插入一个元素的版本,其返回值是一个std::pair
执行查找操作,查看是否有元素
- count/find
自定义类型:需通过模板特化、运算符重载、函数对象定义比较规则
模板特化
- less
运算符重载
- <
函数对象定义
- compare
multiset
特征:存key,key可重复,默认升序
- 可以存储重复的key
- 范围查找
操作
查找功能(count、find)、插入功能(insert)、删除功能(erase)与set类似
有bound系列函数(lower_bound、upper_bound、equal_range)
equal_range(value)返回一个pair对象,包含两个迭代器:
- first:指向第一个等于value的元素。
- second:指向最后一个等于value的元素的下一个位置。
lower_bound(value):返回指向第一个不小于value的元素的迭代器。
upper_bound(value):返回指向第一个大于value的元素的迭代器。
自定义类型:与set相同
map
特征:存key-value,key唯一,默认按key升序
- 存放的关键字key不重复
操作:查找、insert(插入pair)、erase
执行查找操作,查看是否有元素
- count/find
map是具备下标的,其他三种关联式容器没有下标
支持下标访问运算符
1、查找key对象的value
- cout <<cities["100"] << endl
2、如果查询时对用的key不存在,会直接创建该key的记录
3、可以修改key对应的value
- cities["010"] = "武汉"
自定义类型:需通过模板特化、运算符重载、函数对象定义比较规则
multimap
特征:存key-value,key可重复,默认按key升序
- 存放的关键字可以重复
- 返回查找
自定义类型:需通过模板特化、运算符重载、函数对象定义比较规则
3、无序关联式容器
共性
底层实现
hash table
桶 + 单链表
加载因子
- 0.5
hash函数的设计
查找元素的时间复杂度O(1)
空间复杂度
- O(N)
支持前向访问迭代器
存放的元素是无序的
针对于自定义类型
必须给出Hash函数
1、自定义函数对象
- 函数对象的形式
2、扩展std::hash的模板特化版本
模板的特化
- Hash的默认采用的是std::hash
3、要将函数调用运算符设计成const版本
必须重载operator=
第三个模板参数KeyEqual的传参有三种方式:模板的特化、函数对象的形式、运算符重载
- 模板的特化
- 函数对象的形式
- 运算符重载
unordered_set
不能存放关键字相同的元素
不能使用下标访问运算符
执行查找操作,查看是否有元素
- count/find
unordered_map
不能存放关键字相同的元素
可以使用下标访问运算符
- 与map类似
执行查找操作,查看是否有元素
- count/find
unordered_multiset
- 可以存放关键字相同的元素
unordered_multimap
- 可以存放关键字相同的元素
萃取技巧
迭代器
迭代器本身的抽象级别要高于容器
迭代器设计模式的作业
- 将容器的底层实现隐藏起来
作用:对容器中的元素进行访问
广义的指针
种类
输入迭代器
Input Iterator
++/==/!=/*/->
- 可读
- 不需要关注写操作
输出迭代器
Output Iterator
++/*/=
- 可写
前向访问迭代器
Forward Iterator
- ++/==/!=/*/->/=
双向访问迭代器
Bidirectional Iterator
- ++/--/==/!=/*/->/=
随机访问迭代器
Random Access Iterator
- +/+=/-/-=/*/->/++/--/</>/<=/>=/==/!=/=
适配器
1、容器适配器
- stack
- queue
- priority_queue
2、迭代器适配器
流迭代器
- istream_iterator
- ostream_iterator
反向迭代器
- reverse_iterator
插入迭代器
- back_insert_iterator/back_inserter
- front_insert_iterator/front_inserter
- insert_iterator/inserter
3、函数适配器
- std::bind
- std::mem_fn
算法
作用:通过迭代器对容器中的元素进行操作
在操作时,只跟迭代器进行交互,与容器无关
算法在设计时,就不需要考虑容器,实现了算法与容器的解耦
分类
非修改式的序列操作
- 遍历
- 查找
修改式的序列操作
- 拷贝copy
- 替换replace
- 删除erase
排序
- 快排
- 堆排
最大最小值
二分查找
集合操作
- 差集、并集、补集、对称差分、子集
内存分配与释放有关的
- 空间配置器
函数对象(仿函数)
作用:针对于容器中的元素要做定制化操作的时候,需要借助于函数对象完成
包括
std::function
- 函数名
- 函数指针
- 重载了函数调用运算符的类创建的对象
std::function + std::bind
- 基于对象的思想
- 取代虚函数的地位
lambda表达式
空间配置器
作用:分配和释放内存
默认的空间配置器
底层实现(面试精华)
解决的问题
1、多线程
2、内存不足的措施
3、内存碎片的问题
外部碎片
堆空间
- 希望消除外部碎片的影响,节省内存
内部碎片
- 无法优化
os对于内存的管理
页式管理
- 4KB
段式管理
针对于程序
- 代码段
- 读写段
段页式管理
池的技术
- 进程池
- 线程池
- 内存池
- 数据库连接池
std::allocator
特点
针对容器来说,空间的分配与对象的创建是分开进行的,并不是绑定在一起的
是与new表达式不同的
对于批量元素进行操作
对象的创建
- construct
分配内存
- allocate
实现方式
一级配置器
- 当申请的空间大于128字节时,直接使用malloc/free
二级配置器
当申请的空间小于等于128字节时,采用16个自由空闲链表 + 内存池进行管理
16个自由空闲链表
- 指针数组管理
内存池
- 两个指针进行管理
源码
接口层
std::allocator
- allocate
- deallocate
- construct
- destroy
实现层
_Alloc
一级配置器
二级配置器
- 所有的容器最终都会调用它完成空间的分配和释放
总结
针对容器
- 容器操作是批量数据
以空间换时间
- 只有第一次申请空间时,会调用malloc,之后要申请小内存时,以O(1)时间复杂度分配内存
- 释放内存时,只有大于128字节的空间使用free,小于等于128字节的空间直接挂靠到相应的自由空闲链表之上,重复使用
减少系统调用malloc/free是使用频率
- 系统调用的开销比普通函数要大很多
