【Example】C++ STL 常用容器概述
前排提醒:
由于 Microsoft Docs 全是机翻。所以本文表格是我人脑补翻+审校。
如果有纰漏、模糊及时反馈。
了解每一种容器的特性、知道什么情况下用什么容器就可以。
序列式容器
序列容器是指在逻辑上以线性排列方式存储给定类型元素的容器。
这些容器和数组非常类似,都是在逻辑上连续的(但内存不一定是连续的),与数组不同的是,容器可以非常方便的动态管理,而不是固定元素大小。
std::vector
当你需要容器时,就找vector!
-- Bjarne Stroustrup
std::vector 差不多是C++当中最常用的容器,它是一个模版类。你可以将它视作传统数组的动态功能增强版本,因此它的泛用性非常高。
当你以局部变量形式创建并初始化 vector 时,对象本身是存储于栈内存当中,但是它所存储的元素却是在堆内存当中连续的一块空间,因此 std::vector 对于随机访问效率会非常高。
vector 的存储是自动管理的,按需扩张收缩。 vector 通常占用多于静态数组的空间,因为要分配更多内存以管理将来的增长。 vector 所用的方式不在每次插入元素时,而只在额外内存耗尽时重分配。分配的内存总量可用 capacity() 函数查询。额外内存可通过对 shrink_to_fit() 的调用返回给系统。 (C++11 起)
重分配通常是性能上有开销的操作。若元素数量已知,则 reserve() 函数可用于消除重分配。
-- 《C++ Reference》
头文件:
#include <vector>
构造语法:
// 空初始化 std::vector<Type> name; // 带有默认集合 std::vector<Type> name{ value, value, ... }; // 预分配长度 std::vector<Type> name(num); // 预分配长度与默认值 std::vector<Type> name(num, value);
成员函数:
名称 | 说明 |
---|---|
assign | 清除当前vector并将指定的元素复制到该空vector。 |
at | 返回对vector中指定位置的元素的引用。 |
back | 返回对vector中最后一个元素的引用。 |
begin | 返回该vector中起始位置的迭代器。 |
capacity | 返回在不分配更多的内存的情况下vector可以包含的元素数。(当前内存空间) |
cbegin | 返回指向vector中起始位置的常量迭代器。(const修饰) |
cend | 返回末尾位置常量迭代器。(非末尾元素)(const修饰) |
crbegin | 返回一个指向vector中起始位置的常量反向迭代器。(const修饰) |
crend | 返回一个指向vector中末尾位置的常量反向迭代器。(const修饰) |
clear | 清除vector的所有元素。(但没有回收内存) |
data | 返回指向vector中首个元素的指针。 |
emplace | 将元素原位插入到指定位置之前。 |
emplace_back | 将元素原位插入到指定位置之后。 |
empty | 检查vector是否为空。 |
end | 返回指向vector末尾的迭代器。(非末尾元素) |
erase | 从指定位置删除vector中的一个元素或一系列元素。 |
front | 返回回vector中第一个元素的引用。 |
get_allocator | 将对象返回到vector使用的 allocator 类。 |
insert | 将一个元素或多个元素插入到vector指定位置。 |
max_size | 返回vector的最大长度。 |
pop_back | 删除vector末尾处的元素。 |
push_back | 在vector末尾处追加一个元素。 |
rbegin | 返回起始位置的反向迭代器。 |
rend | 返回末尾位置的反向迭代器。 |
reserve | 重新分配vector的最小存储长度。 |
resize | 为vector指定新的大小。 |
shrink_to_fit | 释放冗余容量(内存)。 |
size | 返回vector中的元素数量。 |
swap | 交换两个vector的元素。 |
运算符:
名称 | 说明 |
---|---|
operator[] | 返回对指定位置的vector元素的引用。 |
operator= | 用另一个vector的副本替换该向量中的元素。 |
最简单示例:
int main() { std::vector<int> vec = { 0, 1, 2, 3, 4 }; for (auto &i : vec) { std::cout << i << std::endl; } vec.reserve(10); vec.push_back(5); vec.push_back(6); vec.push_back(7); vec.push_back(8); vec.push_back(9); vec.erase(vec.begin() + 2, vec.end() - 3); for (auto& i : vec) { std::cout << i << std::endl; } vec.clear(); std::vector<int>().swap(vec); return EXIT_SUCCESS; }
扩展阅读:
【Example】C++ Vector 内存预分配的良好习惯
std::list 与 std::forward_list
std::list 是一个模板类,即链表。它的特点是每个元素在逻辑上都以线性连续方式来存储。
它的每个元素内部都有指向前元素及后元素的指针,每次插入与删除都只需更改前后“邻居”的指针,可以做到任何位置高效的插入与删除。
但是,虽然在逻辑上是连续的,然而每个元素在内存当中并不是连续存储的,因此 std::list 无法做到像 std::vector 那样随机读写。(它直接没有 at 函数及 [] 重载)
此外 std::list 对异常的控制是,要么操作成功,出现异常则不进行任何更改。
头文件:
#include <list>
构造语法:
// 默认 std::list<Type> name; // 预分配长度 std::list<Type> name(num); // 预分配长度及默认值 std::list<Type> name(num, value); // 从 initlist 创建 std::list<Type> name(initlist); // 迭代器区间创建 std::list<Type> name(obj.begin(), obj.end());
成员函数:
名称 | 说明 |
---|---|
assign | 清空当前list并将指定的元素复制到当前空list。 |
back | 返回对list中最后一个元素的引用。 |
begin | 返回list中指向起始位置的迭代器。 |
cbegin | 返回list中起始的位置的常量迭代器。(const修饰) |
cend | 返回list中末尾的位置的常量迭代器。(const修饰) |
clear | 清空list。 |
crbegin | 返回list中起始的常量反向迭代器。(const修饰) |
crend | 返回list中末尾的常量反向迭代器。(const修饰) |
emplace | 将元素原位插入到指定位置。 |
emplace_back | 将元素原位插入到末尾位置。 |
emplace_front | 将元素原位插入到起始位置。 |
empty | 判断list是否为空。 |
end | 返回list中指向末尾的迭代器。 |
erase | 从指定位置删除list中的一个元素或一系列元素。 |
front | 返回对list中第一个元素的引用。 |
get_allocator | 返回用于构造list的 allocator 对象的一个副本。 |
insert | 将一个、几个或一系列元素插入list中的指定位置。 |
max_size | 返回list的最大长度。 |
merge | 合并两个已排序list,合并前必须升序或其他指定顺序排序。 |
pop_back | 删除最后元素。 |
pop_front | 删除首个元素。 |
push_back | 从末尾追加元素。 |
push_front | 从起始追加元素。 |
rbegin | 返回起始位置的反向迭代器。 |
remove | 移除满足条件的元素。 |
remove_if | 移除满足谓词条件的元素。 |
rend | 返回list中末尾的反向迭代器。 |
resize | 重新分配长度。 |
reverse | 反转list中元素的顺序。 |
size | 返回list中元素的数目。 |
sort | 按升序或指定其他顺序排列list中的元素。 |
splice | 从另一个list中移动元素。 |
swap | 交换两个list的元素。 |
unique | 删除连续的重复元素。 |
运算符:
名称 | 说明 |
---|---|
operator= | 用另一个list的副本替换当前list中的元素。 |
最简单示例:
int main() { std::list<int> list(10, 0); list.emplace_front(1); list.emplace_front(2); list.emplace_back(6); list.emplace_back(7); for (auto& i : list) { std::cout << i << std::endl; } std::cout << "------" << std::endl; list.sort(); std::list<int> list_m{1, 2, 3, 4, 5}; list.merge(list_m); for (auto& i : list) { std::cout << i << std::endl; }return EXIT_SUCCESS; }
扩展阅读:
std::forward_list 是单项链表,它的头文件是:
#include <forward_list>
它的操作方式和 std::list 基本相同,但是,由于它是单向链表,所以它没有反向迭代器。
也就意味着没有 size() 函数,没有 push_back()、pop_back()、emplace_back() 这些涉及反向操作的函数。
它的优势是空间利用率比 std::list 更高,酌情使用。
它相对于 std::list 多了以下操作函数:
名称 | 说明 |
before_begin | 返回指向第一个元素之前的迭代器 |
cbefore_begin | 返回指向第一个元素之前的常量迭代器 |
insert_after | 在某个元素后插入新元素 |
emplace_after | 在元素后原位构造元素 |
erase_after | 擦除元素后的元素 |
std::deque
双端队列,是具有下标与逻辑相邻顺序的容器。它是 std::vector 与 std::list 相结合的方案,既可随机访问、也可高效双端插入删除。
std::vector 之所以随机访问效率高,是因为它在内存当中是连续的空间并且具有下标。
std::list 之所以插入删除效率高,是因为它所进行插入与删除操作时只需更改前后邻居的链接节点指针。
先来看 std::vector 的内存逻辑:【Example】C++ Vector 内存预分配的良好习惯
std::vector 是始终保持每个元素在连续的一块内存上,当 pushback 了新的元素后,如果冗余内存空间不足,需要重新申请一块新内存再将原有数据拷贝入新的内存块并释放旧内存块。
而 std::deque 在面临 pushback 了新元素且已有内存空间面临不足情况时,则新申请一块内存直接存入新数据,再对新旧内存块进行链接。
因此,std::deque 本质是由多个连续内存块组成,在一定程度上兼具了 std::vector 与 std::list 的优点,但却无法单独超越其中一者。
区块,这很 Minecraft! /滑稽
-- ZhouFZ
除此之外,std::deque 还具有以下特点:
1,双端都可以进行数据的增删。
2,不支持内存预分配或其他控制手段,也不支持对容量进行手动修改。
3,deque 会释放冗余的内存区块,时机取决于编译器实现。
4,它的迭代器需要在不同内存区块之间迭代,所以性能不如 std::vector 但优于 std::list 。
需要注意的问题:
迭代器非法化:指的是在 std::deque 逻辑上连续元素的头尾与中间进行插入或删除新的元素而导致的迭代器失效。
特别补充:迭代器失效情况也取决于编译器实现,如果实际操作中存在任何可能原因而导致失效,请采取措施避免。
引发失效的情况:
名称 | 情况 |
在头尾插入 | 可能导致迭代器失效(全部或部分),但指针与引用仍然有效 |
在头尾删除 | 其他元素的迭代器不失效 |
中间插入或删除操作 | 全部失效 |
具体原因:
std::deque 是一个同时管理着索引区块与对应数据区块的结构,它通过一个类似于 MAP 的 Key:Value 形式来记录所拥有的内存区块。
当出现头尾插或者中间插操作时,如果当前所管理的数据内存区块容量不足,而去开辟了新的数据内存区块,自然索引就会增加。
如果存储索引本身的区块内存空间不足,就又要去开辟新的内存去存储更改后的索引区块,已有的迭代器自然就失效了,因为迭代器找不到自己所处数据区块的原有索引在哪了。
(听不懂没事,多琢磨几天。)
《C++ Reference》对迭代器非法化的补充
操作 被非法化 所有只读操作 决不 swap 、 std::swap 尾后迭代器可能被非法化(实现定义) shrink_to_fit 、 clear 、 insert 、 emplace 、
push_front 、 push_back 、 emplace_front 、 emplace_back始终 erase 若在起始擦除——仅被擦除元素
若在末尾擦除——仅被擦除元素和尾后迭代器
否则——非法化所有迭代器(包含尾后迭代器)。resize 若新大小小于旧者:仅被擦除元素和尾后迭代器
若新大小大于旧者:非法化所有迭代器
否则——不非法化任何迭代器。pop_front 仅有指向被擦除元素者 pop_back 仅有指向被擦除元素者和尾后迭代器 此节有仍少量不准确处,更多细节请查看涉及单独成员函数的页面
非法化注意
- 从 deque 任一端插入时, insert 和 emplace 不会非法化引用。
- push_front 、 push_back 、 emplace_front 和 emplace_back 不会非法化任何到 deque 元素的引用。
- 从 deque 任一端擦除时, erase 、 pop_front 和 pop_back 不会非法化到未擦除元素的引用。
- 以较小的大小调用 resize 不会非法化任何到未擦除元素的引用。
- 以较大的大小调用 resize 不会非法化任何到 deque 元素的引用。
回归正题
头文件:
#include <deque>
构造语法:
// 默认空 std::deque<Type> name; // 拷贝构造 std::deque<Type> name(dequeobj); // 默认分配长度及默认值 std::deque<Type> name(num, value); // 迭代器区间 std::deque<Type> name(obj.begin(), obj.end());
成员函数:
名称 | 说明 |
---|---|
assign | 清空当前deque并将指定的元素复制到当前空deque。 |
at | 返回对deque中指定位置的元素的引用。 |
back | 返回对deque中最后一个元素的引用。 |
begin | 返回指向起始的迭代器。 |
cbegin | 返回指向起始的常量迭代器。(const修饰) |
cend | 返回指向末尾的常量迭代器。(const修饰) |
clear | 清空 deque。 |
crbegin | 返回指向起始的逆向常量迭代器。(const修饰) |
crend | 返回指向末尾的逆向常量迭代器。(const修饰) |
emplace | 将元素原位插入到指定位置。 |
emplace_back | 将元素原位插入到末尾位置。 |
emplace_front | 将元素原位插入到起始位置。 |
empty | 检查 deque 是否为空。 |
end | 返回指向末尾的迭代器。 |
erase | 从指定位置删除一个或一系列元素。 |
front | 返回第一个元素的引用。 |
get_allocator | 返回用于构造 allocator 的 deque 对象的副本。 |
insert | 将一个、多个或一系列元素插入到指定位置。 |
max_size | 返回可容纳的最大元素数。 |
pop_back | 删除末尾处的元素。 |
pop_front | 删除起始处的元素。 |
push_back | 插入元素到末尾位置。 |
push_front | 插入元素到起始位置。 |
rbegin | 返回指向起始的逆向迭代器。 |
rend | 返回指向末尾的逆向迭代器。 |
resize | 手动改变大小。 |
shrink_to_fit | 释放未使用的内存。 |
size | 返回当前长度。(元素数量) |
swap | 交换两个deque。 |
运算符:
名称 | 说明 |
---|---|
operator[] | 返回对指定位置的 deque 元素的引用。 |
operator= | 将 deque 的元素替换为另一个 deque 的副本。 |
最简单示例:
(注意看对迭代器的操作)
int main() { std::deque<int> deque_d(10, 0); std::deque<int>::iterator it = deque_d.begin(); it = deque_d.insert(it, 1); it = deque_d.insert(it, 2); it = deque_d.insert(it, 3); it = deque_d.insert(it, 4); std::deque<int>::iterator itf = std::find(deque_d.begin(), deque_d.end(), 3); itf = deque_d.emplace(itf, 5); itf = deque_d.emplace(itf, 6); itf = deque_d.emplace(itf, 7); for (auto &i : deque_d) { std::cout << i << std::endl; } return EXIT_SUCCESS; }
std::array
标准库数组,本质一个模板类,是一个固定长度的容器,不可扩容。在现代C++中,主张使用 std::array 替代传统样式的数组。
std::array 提供的功能也比 std::vector、std::list 更简单。因为,它从设计上的目的,就是对传统数组进行现代化改造。
具体体现在:
1,它拥有和传统数组一样的性能、可访问性。
2,它具有传统数组所没有的容器优点:可获取大小、随机访问迭代器、支持赋值等。
所以,当你需要固定大小的数组时,应首先考虑 std::array。
头文件:
#include <array>
构造语法:
// 默认空 std::array<Type, SizeNum> name; // 默认值情况下 std::array<Type, SizeNum> name{value, value...}; std::array<Type, SizeNum> name = {value, value...};
成员函数:
名称 | 说明 |
---|---|
array | 构造一个数组对象。 |
at | 访问指定位置处的元素。 |
back | 访问最后一个元素。 |
begin | 指定受控序列的开头。 |
cbegin | 返回一个随机访问常量迭代器,它指向数组中的第一个元素。 |
cend | 返回一个随机访问常量迭代器,它指向刚超过数组末尾的位置。 |
crbegin | 返回一个指向反向数据中第一个元素的常量迭代器。 |
crend | 返回一个指向反向数组末尾的常量迭代器。 |
data | 获取第一个元素的地址。 |
empty | 测试元素是否存在。 |
end | 指定受控序列的末尾。 |
fill | 将所有元素替换为指定值。 |
front | 访问第一个元素。 |
max_size | 对元素数进行计数。 |
rbegin | 指定反向受控序列的开头。 |
rend | 指定反向受控序列的末尾。 |
size | 对元素数进行计数。 |
swap | 交换两个容器的内容。 |
运算符:
运算符 | 说明 |
---|---|
array::operator= | 赋值替换数组。 |
array::operator[] | 访问指定位置处的元素。 |
最简单示例:
int main() { std::array<int, 5> arry = {5, 4, 3, 2, 1}; std::sort(arry.begin(), arry.end()); for (auto &i : arry) { std::cout << i << std::endl; } std::cout << "-------------" << std::endl; arry[2] = 10; for (auto& i : arry) { std::cout << i << std::endl; } std::cout << "-------------" << std::endl; arry.fill(0); for (auto& i : arry) { std::cout << i << std::endl; } return EXIT_SUCCESS; }
关联式容器
与序列式容器不同的是,关联式容器是采用键值对的方式即 Key : Value 对应的方式来存储数据。
STL 所内置的关联式容器主要使用红黑树来实现,容器内会自动根据 Key 来自动升序排序。
此外还有基于哈希值的无序关联式容器,请照猫画虎使用即可。
名称 | 头文件 | 实现 | 键值对应 | 允许键重复 | 键排序 |
std::set | set | 红黑树 | Key = Value | No | 升序 |
std::multiset | set | 红黑树 | Key = Value | Yes | 升序 |
std::unordered_set | unordered_set | 哈希表 | Key = Value | No | 无 |
std::unordered_multiset | unordered_set | 哈希表 | Key = Value | Yes | 无 |
std::map | map | 红黑树 | Key : Value | No | 升序 |
std::multimap | map | 红黑树 | Key : Value | Yes | 升序 |
std::unordered_map | unordered_map | 哈希表 | Key : Value | No | 无 |
std::unordered_multimap | unordered_map | 哈希表 | Key : Value | Yes | 无 |
红黑树与哈希表不同实现的关联式容器区别:红黑树实现的关联式容器遍历性能更好,哈希表实现的关联式容器基于键的随机访问性能更好。
请记下表格当中的头文件,下文当中不再赘述。
Set
std::set 与 std::multiset 最显著的特点就是键就是值,所以在 Set 当中的值不能直接修改,需要删除旧值再重新建立新值 (即新建立键值,只是对于 set 来说值就是键而已)。
std::set 与 std::multiset 的区别是,std::set 不允许有重复值,std::multiset 则允许。两者同样都会根据键值大小进行升序排序。
构造语法:
// 默认 std::set<Type> name; std::multiset<Type> sm1; // 迭代器区间 std::set<Type> name(obj.begin(), obj.end()); std::multiset<Type> name(obj.begin(), obj.end()); // initlist std::set<int> name{value, value, ...}; std::multiset<int> name{value, value, ...}; // 拷贝构造和移动构造略 // 自定义比较器(C++14) struct Point { double x, y; }; struct PointCmp { bool operator()(const Point& lhs, const Point& rhs) const { return std::hypot(lhs.x, lhs.y) < std::hypot(rhs.x, rhs.y); } }; std::set<Point, PointCmp> z = { {2, 5}, {3, 4}, {1, 1} };
成员函数:
名称 | 说明 |
---|---|
begin | 返回指向起始的迭代器。 |
cbegin | 返回指向起始的常量迭代器。(const修饰) |
cend | 返回指向末尾的常量迭代器。(const修饰) |
clear | 清除所有元素。 |
contains(c++20) | 检查是否存在指定键。仅限C++20。 |
count | 返回匹配特定键的元素数量。 |
crbegin | 返回指向起始的常量逆向迭代器。(const修饰) |
crend | 返回指向末尾的常量逆向迭代器。(const修饰) |
emplace | 原位插入元素。 |
emplace_hint | 原位插入元素,且尽可能于 hint(迭代器) 前面位置。 |
empty | 检查是否为空。 |
end | 返回指向末尾的迭代器。 |
equal_range | 返回一对表示范围区间的迭代器,为匹配特定键的元素范围。 |
erase | 从指定位置移除一个元素或元素范围,或者移除与指定键匹配的元素。 |
find | 寻找带有特定键的元素,并返回它所处位置的迭代器。 |
get_allocator | 返回用于构造 allocator 的 set 对象的副本。 |
insert | 将一个元素或元素范围插入到set。 |
key_comp | 返回set内用于比较排序对象(比较器)的副本。 |
lower_bound | 返回指向首个不小于给定键的元素的迭代器。 |
max_size | 返回set的最大长度。 |
rbegin | 返回指向起始的逆向迭代器。 |
rend | 返回指向末尾的逆向迭代器。 |
size | 返回set中的元素数量。 |
swap | 交换两个set。 |
upper_bound | 返回指向首个大于给定键的元素的迭代器。 |
value_comp | 返回用于在value_type类型的对象中比较键的函数。 |
运算符:
名称 | 说明 |
---|---|
operator= | 将一个集中的元素替换为另一个集的副本。 |
最简单示例:
int main() { std::set<int> s3{4, 3, 2, 1, 0}; s3.emplace(5); s3.emplace(6); s3.emplace(7); s3.emplace(8); for (auto &i : s3) { std::cout << i << std::endl; } std::cout << "-------------" << std::endl; s3.erase(s3.find(5)); s3.emplace(10); auto it = s3.equal_range(10); if (it.first == s3.end() || it.second == s3.end()) { std::cout << "There are 10 in the set" << std::endl; } for (auto& i : s3) { std::cout << i << std::endl; } return EXIT_SUCCESS; }
扩展阅读:
std::unordered_set 与 std::unordered_multiset
基于哈希表实现的无序 set 容器,拥有比红黑树所实现的版本更好的随机访问性能,但是遍历性能弱于红黑树实现。
序列由哈希函数弱排序,哈希函数将此序列分区到称为存储桶的有序序列集中。 在每个存储桶中,比较函数确定任何一对元素是否具有等效的排序。 每个元素同时用作排序键和值。 序列以允许查找、插入和移除任意元素的方式表示,并包含与序列中的元素数量无关的多个操作(常量时间),至少在所有存储桶长度大致相等时如此。 在最坏情况下,当所有元素位于一个存储桶中时,操作数量与序列中的元素数量成比例(线性时间)。 插入元素不会使任何 iterator 无效,删除元素只会使指向已删除元素的 iterator 失效。
-- Microsoft Docs
两者相较于 std::set,新增了以下桶操作、哈希策略函数:
名称 | 说明 |
bucket_count | 返回桶数量 |
max_bucket_count | 返回桶最大数量 |
bucket_size | 返回桶的大小 |
bucket | 返回带有特定键的桶 |
load_factor | 返回每个桶的平均元素数量 |
max_load_factor | 获取或设置每个桶的最大元素数。 |
rehash | 重新生成哈希表,并且为指定数量的桶预留空间。 |
reserve | 重新分配预留元素个数。 |
hash_function | 返回用于存储元素的哈希函数对象。 |
key_eq | 返回用于比较键相等性的函数对象。 |
Map
与 set 不同的是,map 系列是键值与值对应的形式,即 Key : Value 成对出现。基于红黑树的 map 会根据键的大小自动升序排序,基于哈希表的则无序。
map 可以根据键的映射直接修改元素值。但是,键却是常量无法修改,只能删除已有的键值对再添加新的。
标准库当中 map 系列分为 std::map 和 std::multimap,前者不允许键重复,后者则允许键重复。
构造语法:
// 默认空 std::map<KeyType, ValueType> name; // initlist std::map<KeyType, ValueType> name{ {Key, Value}, ...}; // 迭代器范围 std::map<KeyType, ValueType> name(map.begin(), map.end()); // 拷贝构造和移动构造略 // 自定义比较struct struct Comp { int x, y; Comp(int v, int n) : x(v), y(n) {}; bool operator()(const int& l, const int& r) const { return std::hypot(l, r) < std::hypot(x, y); }; }; std::map<int, string, Comp> m4(Comp(11, 22)); // 自定义比较lambda int xx = 11; int yy = 22; auto compLambda = [&](const int &x, const int &y) { return std::hypot(x, y) < std::hypot(xx, yy); }; std::map<int, string, decltype(compLambda)> m5(compLambda);
成员函数:
名称 | 说明 |
---|---|
at | 查找具有指定键值的元素。(在std::multimap中不提供) |
begin | 返回一个迭代器,此迭代器指向Map起始位置。 |
cbegin | 返回一个常量迭代器,此常量迭代器指向Map起始位置。(const修饰) |
cend | 返回一个常量迭代器,此常量迭代器指向Map末尾位置。(const修饰) |
clear | 清除所有元素。 |
contains(C++20) | 检查Map中是否有具有指定键的元素。(仅限C++20) |
count | 返回Map中其键与参数中指定的键匹配的元素数量。 |
crbegin | 返回一个常量反向迭代器,此常量反向迭代器指向Map起始位置。(const修饰) |
crend | 返回一个常量反向迭代器,此常量反向迭代器指向Map末尾位置。(const修饰) |
emplace | 将原位构造的元素插入到Map中。 |
emplace_hint | 将原位构造的元素插入到Map中,且尽可能于 hint(迭代器) 前面位置。 |
empty | 判断是否为空。 |
end | 返回一个迭代器,此迭代器指向Map末尾位置。 |
equal_range |
返回一对迭代器。 第一个迭代器指向Map中其键大于指定键的第一个元素。 第二个迭代器指向Map中其键等于或大于指定键的第一个元素。 |
erase | 从指定位置移除Map中的元素或元素范围。 |
find | 寻找带有特定键的元素,并返回它所处位置的迭代器。 |
get_allocator | 返回用于构造 allocator 的 map 对象的副本。 |
insert | 将一个或一系列元素插入到Map中的指定位置。 |
key_comp | 返回Map内用于比较排序对象(比较器)的副本。 |
lower_bound | 返回指向首个不小于给定键的元素的迭代器。 |
max_size | 返回可容纳的最大元素数。 |
rbegin | 返回一个反向迭代器,此反向迭代器指向Map起始位置。 |
rend | 返回一个反向迭代器,此反向迭代器指向Map末尾位置。 |
size | 返回当前Map中的元素数量。 |
swap | 交换两个Map。 |
upper_bound | 返回指向首个大于给定键的元素的迭代器。 |
value_comp | 返回用于在value_type类型的对象中比较键的函数。 |
运算符:
名称 | 说明 |
---|---|
operator[] | 将元素插入到具有指定键值的映射。(在std::multimap中不提供) |
operator= | 将一个映射中的元素替换为另一映射副本。 |
最简单示例:
int main() { std::map<int, string> mapObj{ {0, "ABC"}, {1, "DEF"}, {2, "GHI"}}; for (auto &it : mapObj) { std::cout << "Key:" << it.first << " Value:" << it.second << std::endl; } std::cout << "-------------" << std::endl; std::vector<std::pair<int, string>> vec{ std::make_pair(3, "OOO"), std::make_pair(4, "PPP"), std::make_pair(5, "QQQ") }; mapObj.insert(vec.begin(), vec.end()); for (auto& it : mapObj) { std::cout << "Key:" << it.first << " Value:" << it.second << std::endl; } std::cout << "-------------" << std::endl; auto it = mapObj.find(4); if (it != mapObj.end()) { it->second = "***"; } for (auto& it : mapObj) { std::cout << "Key:" << it.first << " Value:" << it.second << std::endl; } return EXIT_SUCCESS; }
扩展阅读:
std::unordered_map 与 std::unordered_multimap
两个基于哈希表实现的 Map,顾名思义一个不允许键重复,另一个则允许。
哈希函数将此序列分区到称为存储桶的有序序列集中。 在每个存储桶中,比较函数将确定任一元素对是否具有等效顺序。 每个元素存储两个对象,包括一个排序键和一个值。 序列以允许查找、插入和移除任意元素的方式表示,并包含与序列中的元素数量无关的多个操作(常量时间),至少在所有存储桶长度大致相等时如此。 在最坏情况下,当所有元素位于一个存储桶中时,操作数量与序列中的元素数量成比例(线性时间)。 此外,插入元素不会使迭代器失效,移除元素仅会使指向已移除元素的迭代器失效。
-- Microsoft Docs
两者相较于 std::map,新增了以下桶操作、哈希策略函数:
名称 | 说明 |
bucket_count | 返回桶数量 |
max_bucket_count | 返回桶最大数量 |
bucket_size | 返回桶的大小 |
bucket | 返回带有特定键的桶 |
load_factor | 返回每个桶的平均元素数量 |
max_load_factor | 获取或设置每个桶的最大元素数。 |
rehash | 重新生成哈希表,并且为指定数量的桶预留空间。 |
reserve | 重新分配预留元素个数。 |
hash_function | 返回用于存储元素的哈希函数对象。 |
key_eq | 返回用于比较键相等性的函数对象。 |
std::pair 与 std::tuple
可以同时存储不同数据类型的容器,它们两个都有各自的优势与最佳用途。
std::pair
std:pair 是一个类模板,提供了一个单元存储两个不同类型数据的功能,但也仅限于存储两个数据。
但也正是它的优势:拿它可以轻松高效的初始化 std::map。
注意:声明 std::pair 时<>内的类型声明必须和初始化时()内排列的数据类型相对应。
头文件:
#include <utility>
构造语法:
// 默认空 std::pair<Type1, Type2> name; // 默认值 std::pair<Type1, Type2> name(Value1, Value2); // make_pair auto name3(std::make_pair<Type1, Type2>(Value1, Value2));
成员对象:
名称 | 说明 |
first | Value1 |
second | Value2 |
成员函数:
名称 | 说明 |
operator= | 赋值 |
swap | 交换 |
辅助类:
名称 | 说明 |
std::tuple_size<std::pair> | 获得大小 |
std::tuple_element<std::pair> | 获得元素类型 |
最简单示例:
int main() { std::map<int, string> mapObj{ {0, "ABC"}, {1, "DEF"}, {2, "GHI"}}; std::vector<std::pair<int, string>> vec{ std::make_pair(3, "OOO"), std::make_pair(4, "PPP"), std::make_pair(5, "QQQ") }; mapObj.insert(vec.begin(), vec.end()); mapObj.insert(std::make_pair(6, "GGG")); mapObj.insert(std::make_pair(7, "EEE")); for (auto& it : mapObj) { std::cout << "Key:" << it.first << " Value:" << it.second << std::endl; } return EXIT_SUCCESS; }
std::tuple
元组,它是 std::pair 的扩展,是一个类模板。可以将多个不同类型的值汇集在一起,但它的长度只能是固定的。
此外,它还需要配合其头文件内的几个类外部函数来使用。
注意:
1,声明 std::tuple 时 <> 内的类型声明必须和初始化时()内相排列的数据类型对应。
2,std::tuple 长度是固定的,长度由声明时 <> 内的类型数量而定,此后便不可更改。
头文件:
#include <tuple>
构造语法:
// 默认空 std::tuple<Type1, Type2, Type3, ...> name; // 默认值 std::tuple<Type1, Type2, Type3, ...> name(Value1, Value2, Value3, ...); // 使用 std::pair 构造 std::tuple<Type1, Type2> name(std::make_pair<Type1, Type2>(Value1, Value2)); // 使用 std::make_tuple 构造 std::tuple<Type1, Type2, Type3, ...> name(std::make_tuple(Value1, Value2, Value3, ...));
成员函数:
名称 | 说明 |
operator= | 赋值 |
swap | 交换两个tuple |
非成员辅助函数:
名称 | 说明 |
make_tuple | 创建一个tuple对象,其类型根据各实参类型定义 |
tie | 创建左值引用的tuple,或将 tuple 解包为独立对象 |
forward_as_tuple | 创建转发引用的 tuple |
tuple_cat | 通过连接任意数量的元组来创建一个tuple |
std::get(std::tuple) | 元组式访问指定的元素 |
辅助类:
名称 | 说明 |
tuple_size | 获得大小 |
tuple_element | 获得元素类型 |
ignore | 用 tie 解包 tuple 时用来跳过元素的占位符 |
std::uses_allocator<std::tuple> | 特化 std::uses_allocator 类型特征 |
最简单示例:
int main() { std::tuple<int, string, float> t4(std::make_tuple(0, "str", 3.14)); std::cout << std::get<0>(t4) << std::endl; std::cout << std::get<1>(t4) << std::endl; std::cout << std::get<2>(t4) << std::endl; // 批量拆包 int i; string s; float f; std::tie(i, s, f) = t4; std::cout << i << std::endl; std::cout << s << std::endl; std::cout << f << std::endl; return EXIT_SUCCESS; }
容器配接器
C + + 标准库定义了三种类型的容器适配器: stack 、 queue 和 priority_queue 。 每种适配器都限制了一些基础容器类的功能,以便对标准数据结构提供精确控制的接口。
- stack 类支持) 数据结构的后进先出 (后进先出。 可以在脑海中将其类比为一摞盘子。 元素(盘子)只能从堆栈顶部(基容器末尾的最后一个元素)插入、检查或删除。 仅访问顶部元素的限制是使用 stack 类的原因。
- queue 类支持先进先出 (FIFO) 数据结构。 可以在脑海中将其类比为排队等候银行柜员的人。 元素(人)可从行的后部添加,并且可以从行的前部删除。 行的前部和后部都可以插入。 仅以这种方式访问前端和后端元素的限制是使用 queue 类的原因。
- priority_queue类对其元素进行排序,以便最大的元素始终位于顶部位置。 它支持元素的插入以及顶部元素的检查和删除。 要记住的一个好方法是,人们将其按 age、身高或其他一些标准进行排列。
-- Microsoft Docs
C++ 标准库当中提供了三种容器配接器,分别是 std::stack、std::queue、std::priority_queue。它们并不是容器,而是给予容器功能,比如栈与队列。
首先先讲两个逻辑:
栈:先进后出
队列:先进先出
配接器目的就是给予容器这些抽象的功能。
扩展阅读自行百度:百度百科_栈(Stack) | 百度百科_队列(queue)
std::stack
std::stack 类是容器适配器,它给予程序员栈的功能——特别是 FILO (先进后出)数据结构。
该类模板表现为底层容器的包装器——只提供特定函数集合。栈从被称作栈顶的容器尾部推弹元素。
-- 《C++ Reference》
在不指定的情况下,std::stcak 默认基于 std::deque 实现。当然也可手动指定基于 std::vector 或者 std::list 实现。
由于栈本身属于线性概念,所有它不能用于关联式容器。
头文件:
#include <stack>
构造语法:
// 默认 std::stack<Type> name; // 指定底层容器 std::stack<Type, std::vector<Type>> name; std::stack<Type, std::list<Type>> name; std::stack<Type, std::deque<Type>> name; // 默认值统一初始化 std::stack<Type> name({1, 2, 3}); // 根据其他容器 std::vector<Type> name({1, 2, 3}); std::stack<int, std::vector<Type>> name(vec); std::list<Type> list({ 1, 2, 3 }); std::stack<Type, std::list<Type>> name(list); std::deque<Type> deque({ 1, 2, 3 }); std::stack<Type, std::deque<Type>> name(deque);
成员函数:
名称 | 说明 |
top | 访问栈顶元素 |
empty | 判断是否为空 |
size | 返回当前元素个数 |
push | 向栈顶推入元素 |
pop | 推出(移除)栈顶元素 |
emplace | 原位向栈顶推入元素 |
swap | 交换两个类型相同的栈 |
运算符:
名称 | 说明 |
operator= | 赋值 |
最简单示例:
int main() { std::stack<int, std::vector<int>> st({ 1, 2, 3 }); st.push(4); st.push(4); st.push(4); st.top() = 5; while (!st.empty()) { std::cout << st.top() << std::endl; st.pop(); } return EXIT_SUCCESS; }
std::queue
队列可以给予容器先进先出的功能,就像核酸检测排队一样先到的先做。
和 std::stack 有一个共同点,就是 std::queue 也是默认使用 std::deque 作为默认容器,也可基于 std::vector 和 std::list。
PS:std::queue 支持 std:set 构造,但不支持 std::map。
头文件:
#include <queue>
构造语法:
// 默认 std::queue<Type> name; // 指定底层容器 std::queue<Type, std::vector<Type>> name; std::queue<Type, std::list<Type>> name; std::queue<Type, std::deque<Type>> name; // 默认值统一初始化 std::queue<Type> name({1, 2, 3}); // 根据其他容器 std::vector<Type> name({1, 2, 3}); std::queue<int, std::vector<Type>> name(vec); std::list<Type> list({ 1, 2, 3 }); std::queue<Type, std::list<Type>> name(list); std::deque<Type> deque({ 1, 2, 3 }); std::queue<Type, std::deque<Type>> name(deque);
成员函数:
名称 | 说明 |
front | 访问首个元素 |
back | 访问末尾 |
empty | 判断是否为空 |
size | 返回当前元素个数 |
push | 向尾部推入元素 |
pop | 移除首个元素 |
emplace | 原位向尾部推入元素 |
swap | 交换两个类型相同的队列 |
运算符:
名称 | 说明 |
operator= | 赋值 |
最简单示例:
int main() { std::queue<int> qu({ 1, 2, 3 }); qu.push(9); qu.pop(); // del 1 std::cout << "Front " << qu.front() << std::endl; // 2 std::cout << "Back " << qu.back() << std::endl; // 9 return EXIT_SUCCESS; }
std::priority_queue
std::priority_queue 与 std::queue 完全不同,它是优先级队列,优先级高的元素先出队列,而并非先进入的元素。
默认情况下,std::priority_queue 会选择值最大的元素作为最高优先级。当然,也可以自定义值最小元素作为最高优先级。
PS:和 std::queue 一样,std::priority_queue 支持 std:set 构造,但不支持 std::map。
头文件:
#include <queue>
构造语法:
// 默认 std::priority_queue<Type> name; // 指定底层容器 std::priority_queue<Type, std::vector<Type>> name; std::priority_queue<Type, std::list<Type>> name; std::priority_queue<Type, std::deque<Type>> name; std::priority_queue<Type, std::set<Type>> name; // 自定义比较器 auto comp = [](const Type& v1, const Type& v2){ return v1 > v2; }; std::priority_queue<Type, std::vector<Type>, decltype(comp)> name(comp);
成员函数:
名称 | 说明 |
top | 访问最大优先级元素 |
empty | 判断是否为空 |
size | 返回当前所容纳的元素数量 |
push | 推入元素并排序底层容器 |
emplace | 原位推入元素并排序底层容器 |
pop | 移除优先级最大的元素 |
swap | 交换两个同类型priority_queue |
运算符:
名称 | 说明 |
operator= | 赋值 |
最简单示例:
int main() { auto comp = [](const int& v1, const int& v2){ return v1 > v2; }; std::priority_queue<int, std::vector<int>, decltype(comp)> pri_qu(comp); pri_qu.push(3); pri_qu.push(8); pri_qu.push(6); pri_qu.push(1); pri_qu.push(7); pri_qu.push(5); pri_qu.push(9); pri_qu.push(2); pri_qu.push(4); while (!pri_qu.empty()) { std::cout << pri_qu.top() << std::endl; pri_qu.pop(); } return EXIT_SUCCESS; }
AirChip
2022-04-08 02:22