material/program/c_cpp/STL/containers.md

241 lines
8.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# STL 容器
## index
- [概念](#)
- [通性](#)
- [序列容器](#)
- [vector](#)
- [deque](#)
- [list](#)
- [关联容器](#)
- [set](#)
- [multiset](#)
- [map](#)
- [multimap](#)
- [无序容器](#)
- [unordered-set](#unordered-set)
- [unordered-multiset](#unordered-multiset)
- [unordered-map](#unordered-map)
- [unordered-multimap](#unordered-multimap)
- [预定义函数符](#)
## 概念
**函数符**概念
其实就是重载了()的伪函数
- 生成器:无参数
- 一元函数:一个参数
- 二元函数:两个参数
- 谓词:返回bool的一元函数
- 二元谓词:返回bool的二元函数
## 通性
+ `begin()``end()` 用于获取容器的迭代器,指向容器的第一个元素和最后一个元素的“后继”
+ `rbegin()``rend()` 用于获取容器的反向迭代器,指向容器的最后一个元素和第一个元素的“前驱”位置
+ `size()` 返回容器中元素的数量
+ `empty()` 如果容器为空返回true否则返回false
+ `max_size()` 返回容器可能存储的最大元素数量
+ `clear()` 清空容器中的所有元素,但不释放内存
+ `swap()` 交换两个容器的内容
+ `data()` **仅适用于序列容器** 返回指向容器底层存储的指针
+ 提供对容器底层数据的直接访问
+ 适用于需要与C语言风格的API交互的场景
+ `front()``back()` **仅适用于序列容器** 返回容器的第一个元素和最后一个元素的引用
+ `operator[]``at()` **仅适用于序列容器** 访问容器中的特定元素
+ at() 进行边界检查,如果索引超出范围,会抛出`std::out_of_range`异常
## 序列容器
存储元素的顺序与插入顺序一致
### vector
1. 定义和初始化
`std::vector<int> vec1; // 定义一个空的 vector`
`std::vector<int> vec2 = {1, 2, 3, 4, 5}; // 使用初始化列表初始化`
`std::vector<int> vec3(10, 0); // 10个元素初始值为0`
`std::vector<int> vec4(vec2); // 拷贝构造`
`std::vector<int> vec5(std::move(vec2)); // 移动构造`
2. 元素访问
支持序列容器的大部分访问方法
3. 修改容器
`push_back()` 在容器末尾添加一个元素
`pop_back()` 删除容器末尾的元素
`insert()` 在指定位置插入一个或多个元素
`erase()` 删除指定位置的元素
4. 容量和大小
`capacity()` 返回容器当前分配的内存容量(以元素数量计)
`reserve()` 预分配内存,减少动态扩展的次数
`shrink_to_fit()` 收缩内存,释放多余空间
__使用技巧__
1. 预分配内存
如果知道容器将存储大量元素,可以使用 reserve() 预分配内存,以减少动态扩展的次数
2. 使用 `std::move()`
`vec2.push_back(std::move(vec1[0])); // 将vec1的第一个元素移动到vec2`
3. 使用vector的bool值特性
`std::vector<bool>` 是一个特化版本,它使用位存储来优化空间占用,但可能会牺牲某些操作的性能
4. 使用 `std::vector` 的指针特性
如果需要存储动态分配的对象,可以使用 `std::vector<std::unique_ptr<T>>``std::vector<std::shared_ptr<T>>`
__vector 的 capacity 和 size 属性区别__
size 是当前 vector 容器真实占用的大小,也就是容器当前拥有多少个容器。
capacity 是指在发生 realloc 前能允许的最大元素数,即预分配的内存空间。
使用 resize() 容器内的对象内存空间是真正存在的。
使用 reserve() 仅仅只是修改了 capacity 的值,容器内的对象并没有真实的内存空间(空间是"野"的)。
### deque
1. 插入和删除
+ `push_back()``pop_back()`:尾部操作
+ `push_front()``pop_front()`:头部操作
+ `insert()``erase()`:在任意位置插入或删除
2. 大小操作
+ `resize()`:调整容器大小
__使用技巧__
> 高效头部操作:`deque` 是处理头部和尾部频繁插入删除的理想选择
> 随机访问优化:虽然 `deque` 的内存不连续,但通过下标访问效率较高
> 排序:由于支持随机访问,可以使用 `std::sort` 对 `deque` 进行排序
> 迭代器失效:在添加或删除元素后,迭代器可能失效,需要重新获取
__注意事项__
> 内存不连续:`deque` 的元素可能分散在多个内存块中,但通过下标或迭代器访问不会受到影响
> 迭代器失效:在容器大小发生变化时(如插入或删除元素),迭代器可能会失效
> 性能权衡:虽然 `deque` 在头部和尾部操作高效,但在中间插入或删除元素的性能不如 `list`
__相比vector的优势__
> 头部操作高效deque 在头部插入和删除操作的时间复杂度为 O(1),而 vector 为 O(n)。
> 动态变化灵活deque 的*分块内存*分配使其更灵活,性能损耗更小,且内存不足时不用搬运所有元素
> 随机访问性能接近:虽然 deque 的随机访问效率略低于 vector但差距不大
### list
1. 特点
+ 双向链表结构:每个元素包含指向前后元素的指针,支持双向遍历
+ 高效插入和删除:在任意位置插入或删除元素的时间复杂度为 O(1),前提是已知位置
+ 不支持随机访问:无法通过索引直接访问元素,只能通过迭代器遍历
+ 稳定的迭代器:插入和删除操作不会使迭代器失效,除非删除了迭代器所指向的元素
1. 插入和删除
+ `push_back()``pop_back()`:尾部操作
+ `push_front()``pop_front()`:头部操作
+ `insert()``erase()`:在任意位置插入或删除
2. 其他
+ resize():调整容器大小
+ swap():交换两个 list 的内容
+ splice():将一个 list 的元素插入到另一个 list 的指定位置
+ sort()、merge()、reverse():排序、合并和反转
__注意事项__
> 不支持随机访问:无法通过索引访问元素,只能通过迭代器
> 内存开销:每个元素存储额外的指针,内存开销较大
> 缓存局部性差:元素不连续存储,遍历性能不如 std::vector
> 适用场景有限:只有在需要频繁中间插入和删除时才适合使用
## 关联容器
存储元素时会自动排序,通常基于键值对
### set
基于红黑树实现 适用于需要去重、排序和*高效查找*的场景
1. 特点
+ 元素唯一性:容器中不允许存储重复元素
+ 自动排序:元素会根据指定的比较规则自动排序,默认为升序
+ 高效查找:插入、删除和查找操作的时间复杂度均为 O(log n)
2. 构造与初始化
+ 默认构造
+ 初始化列表
+ 拷贝构造
+ 自定义比较规则
```
struct Compare {
bool operator()(int a, int b) const {
return a > b; // 降序排序
}
};
```
3. 常用操作
+ 插入元素 `s1.insert(5);`
+ 删除元素 `s1.erase(3); // 删除值为 3 的元素`
+ 查找元素 `s1.find(4); // 返回指向元素的迭代器,未找到时返回 s1.end()`
+ 统计元素 `int count = s1.count(4);`
+ 范围查询
```
auto lower = s1.lower_bound(3); // 返回第一个 >= 3 的元素的迭代器
auto upper = s1.upper_bound(5); // 返回第一个 > 5 的元素的迭代器
```
4. 高级用法
+ 自定义排序规则:使用仿函数或 `std::greater` 可以改变排序规则
+ 存储复杂数据类型:可以存储 `std::pair` 或自定义结构体,通过自定义比较函数排序
+ 范围删除 `s1.erase(s1.lower_bound(10), s1.upper_bound(50)); // 删除值在 [10, 50) 范围内的所有元素`
__注意事项__
> 修改元素:无法直接修改容器中的元素值,需要先删除再插入
> 性能开销:由于自动排序的特性,插入和删除操作比无序容器慢
> 迭代器失效:插入操作不会使迭代器失效,但删除操作会使指向被删除元素的迭代器失效
### map
用于存储键值对key-value pairs并根据键自动排序
1. 特点
+ 键的唯一性:每个键在 map 中是唯一的,不允许重复
+ 自动排序:容器会根据键的默认比较规则(通常是 < 运算符自动排序
+ 高效操作插入删除和查找操作的时间复杂度均为 O(log n)其中 n 是容器中元素的数量
2. 常用操作
+ 插入元素:`map1.insert({4, "four"});``map1[5] = "five"; // 如果键不存在,则插入新键值对;如果键存在,则更新值`
+ 查找元素:`map1.find(3);` 或者使用 `operator[]` 直接访问键对应的值
+ 删除元素:`map1.erase(3); // 删除键为 3 的元素。`
## 无序容器
存储元素时不排序基于哈希表实现
## 预定义函数符
+ `+` plus
+ `-` minus
+ `*` multiplies
+ `/` divides
+ `%` modulus
+ `==` `equal_to`
+ `!=` `not_equal_to`
+ `>` `greater`
+ `<` `less`
+ `>=` `greater_equal`
+ `<=` `less_equal`
+ `&&` `logical_and`
+ `||` `logical_or`
+ `!` `logical_not`