C++——STL
STL分为:Algorithm(算法)、Container(容器)、Iterator(迭代器)。
细一点的话:容器、算法、迭代器、仿函数、适配器、空间配置器。
常见的STL容器:
1. 序列容器
- vector(向量): std::vector 是⼀个动态数组实现,提供⾼效的随机访问和在尾部进⾏插⼊/删除操作。
- list(链表): std::list 是⼀个双向链表实现,⽀持在任意位置进⾏插⼊/删除操作,但不⽀持随机访问。
- deque(双端队列): std::deque 是⼀个双端队列实现,允许在两端进⾏⾼效插⼊/删除操作。
- array(数组): std::array 是⼀个固定⼤⼩的数组实现,提供对数组元素的⾼效随机访问。
- forward_list(前向链表): std::forward_list 是⼀个单向链表实现,只能从头到尾进⾏遍历,不⽀持双向访问。
2. 关联容器
- set(集合): std::set 是⼀个有序的集合,不允许重复元素,⽀持快速查找、插⼊和删除。
- multiset(多重集合): std::multiset 是⼀个有序的多重集合,允许重复元素。
- map(映射): std::map 是⼀个有序的键值对集合,不允许重复的键,⽀持快速查找、插⼊和删除。
- multimap(多重映射): std::multimap 是⼀个有序的多重映射,允许重复的键。
- unordered_set(⽆序集合): std::unordered_set 是⼀个⽆序的集合,不允许重复元素,⽀持快速查找、插 ⼊和删除。
- unordered_multiset(⽆序多重集合): std::unordered_multiset 是⼀个⽆序的多重集合,允许重复元素。
- unordered_map(⽆序映射): std::unordered_map 是⼀个⽆序的键值对集合,不允许重复的键,⽀持快速 查找、插⼊和删除。
- unordered_multimap(⽆序多重映射): std::unordered_multimap 是⼀个⽆序的多重映射,允许重复的 键。
3. 容器适配器
- stack(栈): std::stack 是⼀个基于底层容器的栈实现,默认使⽤
- queue(队列): std::deque 。 std::queue 是⼀个基于底层容器的队列实现,默认使⽤ std::deque 。
- priority_queue(优先队列): std::priority_queue 是⼀个基于底层容器的优先队列实现,默认使⽤ std::vector 。
pair容器
保存两个数据成员,⽤来⽣成特定类型的模板。
pair<T1, T2>p;
对map进行插入,元素类型是pair
p.insert({word, 1});
p.insert(pair<string, int>(word, 1));
vector
Vector在堆中分配了⼀段连续的内存空间来存放元素
vector扩容
如果集合已满,在新增数据的时候,就要分配⼀块更⼤的内存,将原来的数据复制过来,释放之前的内存,在插⼊ 新增的元素 所以对vector的任何操作,⼀旦引起空间重新配置,指向原vector的所有迭代器就都失效了
vector有两个函数,⼀个是capacity(),在不分配新内存下最多可以保存的元素个数,另⼀个size(),返回当前已经存储数据的个数。
capacity和size相等时,vector就会扩容,capacity变⼤(翻倍)
扩容有
1、固定扩容
- 机制: 每次扩容的时候在原 capacity 的基础上加上固定的容量,⽐如初始 capacity 为100,扩容⼀次为 capacity + 20, 再扩容仍然为 capacity + 20;
- 缺点: 考虑⼀种极端的情况,vector每次添加的元素数量刚好等于每次扩容固定增加的容量 + 1,就会造成⼀种情况,每添加⼀次元素就需要扩容⼀次,⽽扩容的时间花费⼗分⾼昂。所以固定扩容可能会⾯临多次扩容的情况,时间复杂 度较⾼;
- 优点: 固定扩容⽅式空间利⽤率⽐较⾼。
2、加倍扩容
- 机制: 每次扩容的时候原 capacity 翻倍,⽐如初始capcity = 100, 扩容⼀次变为 200, 再扩容变为 400;
- 优点: ⼀次扩容 capacity 翻倍的⽅式使得正常情况下添加元素需要扩容的次数⼤⼤减少(预留空间较多),时间复杂度较 低;
- 缺点: 因为每次扩容空间翻倍,⽽很多空间没有利⽤上,空间利⽤率不如固定扩容。 在实际应⽤中,⼀般采⽤空间换时间的策略。
list
每个元素都是放在⼀块内存中,他的内存空间可以是不连续的,通过指针来进⾏数据的访问
在哪⾥添加删除元素性能都很⾼,不需要移动内存,当然也不需要对每个元素都进⾏构造与析构了,所以常⽤来做 随机插⼊和删除操作容器
双向链表,list每次插⼊新节点都会进⾏内存申请
deque
⽀持快速随机访问,由于deque需要处理内部跳转,因此速度上没有vector快。
deque是⼀个双端开⼝的连续线性空间,其内部为分段连续的空间组成,随时可以增加⼀段新的空间并链接
deque采⽤⼀块map作为主控,其中的每个元素都是指针,指向另⼀⽚连续线性空间,称之为缓存区,这个区才是 ⽤来储存数据的。
deque除了维护⼀个map指针以外,还维护了start与finish迭代器分别指向第⼀缓冲区的第⼀个元素,和最后⼀个 缓冲区的最后⼀个元素的下⼀个元素,同时它还必须记住当前map的⼤⼩。
stack && queue
栈与队列被称之为duque的配接器,其底层是以deque为底部架构。通过deque执⾏具体操作
heap(堆)
建⽴在完全⼆叉树上,分为两种,⼤根堆,⼩根堆
map && set
map内部实现了⼀个红⿊树(红⿊树是⾮严格平衡的⼆叉搜索树,⽽AVL是严格平衡⼆叉搜索树),红⿊树有⾃动 排序的功能,因此map内部所有元素都是有序的,红⿊树的每⼀个节点都代表着map的⼀个元素。因此,对于map 进⾏的查找、删除、添加等⼀系列的操作都相当于是对红⿊树进⾏的操作。map中的元素是按照⼆叉树(⼜名⼆叉 查找树、⼆叉排序树)存储的,特点就是左⼦树上所有节点的键值都⼩于根节点的键值,右⼦树所有节点的键值都 ⼤于根节点的键值。使⽤中序遍历可将键值按照从⼩到⼤遍历出来。
都是C++的关联容器,只是通过它提供的接⼝对⾥⾯的元素进⾏访问,底层都是采⽤红⿊树实现。
set:⽤来判断某⼀个元素是不是在⼀个组⾥⾯。
map:映射,相当于字典,把⼀个值映射成另⼀个值,可以创建字典。
map && unordered_map
map底层是基于红⿊树实现的,因此map内部元素排列是有序的。
unordered_map底层则是基于哈希表实现的,因此其元素的排列顺序是杂乱⽆序的。
时间复杂度:map是O(log n); unordered_map是O(1)
push_back && emplace_back
push_back ⽤于在容器的尾部添加⼀个元素。
emplace_back ⽤于在容器的尾部直接构造⼀个元素。
模板
模板分为类模板与函数模板,特化分为特例化(全特化)和部分特例化(偏特化)。 对模板特例化是因为对特定类型,可以利⽤某些特定知识来提⾼效率,⽽不是使⽤通⽤模板。
函数模板,只有全特化,偏特化的功能可以通过函数的重载完成。
对主版本模板类、全特化类、偏特化类的调⽤优先级从⾼到低进⾏排序是:全特化类>偏特化类>主版本模板类。
模板允许我们编写与数据类型无关的代码,减少了重复代码,提高代码可重用性。
因为模板会在编译时生成具体类型的代码,可能会导致编译时间显著增加。
函数模板
用于创建可以接受不同类型参数的函数,我们定义一个一次性模板,然后生成多个函数版本。
template <typename T>
T max(T a, T b) {return (a > b) ? a : b;
}
编译器在实际调用函数时,根据传入参数的类型自动生成具体类型的函数。
类模板
用于创建可以接受不同类型参数的类,通过创建一次模板类,我们可以生成多个不同类型的类实例
template <typename T>
class Stack {
private:std::vector<T> elements;
public:void push(T const& elem) { elements.push_back(elem); }void pop() { elements.pop_back(); }T top() const { return elements.back(); }
};
在使用类模板时,我们需要显式地声明模板类型
Stack<int> intStack;
Stack<double> doubleStack;