本篇blog还不算很完善,等我慢慢补充
章节一. 前置知识点
STL中的容器类型:
-
顺序容器 (Sequence containers):根据元素在容器中的位置来存储和访问元素。vector(动态数组)、list(双向链表)和deque(双端队列)
-
容器适配器 (Container adaptors):将一种已有的容器类型转换为另一种行为类似但提供不同接口或功能的容器。比如,stack、queue,都是基于deque/list实现的
-
关联容器(Associative containers):支持通过键(Key)来高效地查找和读取元素的容器,比如map和set
-
无序容器 (Unordered containers):基于哈希实现,提供了快速的元素插入、删除和查找操作。无序容器中的元素不按照任何特定的顺序排列,unordered_set、unordered_map
键->值 是什么:
键和值是键值对中的两个基本概念,下面我将通过生活中的例子和编程中的例子来解释。
键(Key):
就像字典中的“词语”或“索引”,用于查找对应的解释。
在一个键值对集合中,键是唯一的标识符,每个键只能出现一次。
例如,在电话簿中,联系人的名字就是键。
值(Value):
是与键相关联的数据,就像字典中词语的“解释”。
值可以通过键来获取,值可以是任意类型的数据,并且值可以重复。
例如,在电话簿中,电话号码就是值。
索引访问特性总结
容器 是否支持索引访问 索引类型 时间复杂度 备注 vector ✅ 支持 整数下标 O(1) 连续内存,快速随机访问 array ✅ 支持 整数下标 O(1) 固定大小数组 string ✅ 支持 整数下标 O(1) 字符序列 deque ✅ 支持 整数下标 O(1) 双端队列 map ✅ 支持键访问 键类型 O(log n) 通过键访问,不是整数索引 unordered_map ✅ 支持键访问 键类型 O(1)平均 通过键访问 set ❌ 不支持 - - 只有键没有值 list ❌ 不支持 - - 链表结构 forward_list ❌ 不支持 - - 单向链表 queue ❌ 不支持 - - 队列适配器 stack ❌ 不支持 - - 栈适配器 priority_queue ❌ 不支持 - - 优先队列适配器
章节二. map基本概念
std::map是C++标准模板库(STL)中的一个关联容器,它提供了一种键值对(key-value) 的存储方式,并且会根据键(key)自动排序,适用于需要按键排序和快速查找的场景。理解其特性和正确使用方式对编写高效的C++程序至关重要。主要特性:
有序容器:元素按key的升序排列(默认)
唯一键:每个key在map中只能出现一次
底层实现:通常为红黑树(平衡二叉搜索树)
访问效率:O(log n)的查找、插入和删除
关联性:通过键来访问元素,而不是通过位置。
动态增长:map的大小可以动态改变。
头文件: #include <map>
时间复杂度
查找:O(log n)
插入:O(log n)
删除:O(log n)
遍历:O(n)
使用建议
需要有序访问时使用map
键需要唯一时使用map
频繁查找时效率高
不适合频繁插入删除中间元素
考虑内存开销(每个节点额外指针)
章节三. 基本用法
map的函数接口(方法)可以分为以下几类:
1. 构造、析构和赋值操作
2. 迭代器
3. 容量
4. 元素访问
5. 修改操作
6. 观察器(比较函数、分配器等)
7. 非成员函数(全局函数)
构造函数和析构函数
构造函数
// 1. 默认构造函数(空map) map<Key, T> m1; // 2. 范围构造函数 map<Key, T> m2(first, last); // first/last为迭代器 // 3. 拷贝构造函数 map<Key, T> m3(otherMap); // 4. 移动构造函数(C++11) map<Key, T> m4(std::move(otherMap)); // 5. 初始化列表构造函数(C++11) map<Key, T> m5 = {{1, "one"}, {2, "two"}, {3, "three"}}; // 6. 带比较函数的构造函数 map<Key, T, Compare> m6(comp);析构函数
自动调用,释放所有元素内存。
迭代器相关函数
map提供了双向迭代器。
begin() 返回指向第一个元素的迭代器
end() 返回指向尾后(最后一个元素之后)的迭代器
rbegin() 返回指向最后一个元素的逆向迭代器
rend() 返回指向第一个元素之前的逆向迭代器cbegin(), cend(), crbegin(), crend() 返回const迭代器。
map<int, string> m = {{1, "one"}, {2, "two"}, {3, "three"}}; // 正向迭代器 for (auto it = m.begin(); it != m.end(); ++it) for (auto it = m.cbegin(); it != m.cend(); ++it) // const迭代器 // 反向迭代器 for (auto rit = m.rbegin(); rit != m.rend(); ++rit) for (auto rit = m.crbegin(); rit != m.crend(); ++rit) // 基于范围的for循环(C++11) for (const auto& kv : m) { cout << kv.first << ": " << kv.second << endl; }
容量相关函数
empty() 检查map是否为空
size() 返回元素个数
max_size() 返回可以容纳的最大元素数(通常是一个很大的数)// 1. empty() - 检查是否为空 bool isEmpty = m.empty(); // 2. size() - 返回元素数量 size_t count = m.size(); // 3. max_size() - 返回最大可能元素数 size_t maxCount = m.max_size();
元素访问函数
operator[]: 如果键存在,返回对应值的引用;如果键不存在,则插入一个具有该键的元素,并返回其值的引用(值初始化)。 at(key): 返回键为key的值的引用,如果键不存在,抛出out_of_range异常。// 1. operator[] - 访问或插入元素 map<int, string> m; m[1] = "one"; // 插入 string val = m[1]; // 访问 m[2]; // 如果不存在,值初始化(string为"") // 2. at() - 带边界检查的访问(C++11) string val = m.at(1); // 如果键不存在,抛出std::out_of_range // 3. 迭代器访问 for (auto it = m.begin(); it != m.end(); ++it) { cout << it->first << ": " << it->second << endl; }
修改器函数
有两种插入元素的方法:
何时使用 emplace
-
构造参数已知:可以直接传递构造参数时
-
对象构造成本高:避免不必要的拷贝/移动
-
不可拷贝的对象:需要在容器中存储时
-
性能敏感代码:需要优化插入性能时
何时使用 insert
-
已有对象:对象已经构造好时
-
代码清晰性:当
insert({key, value})更清晰时 -
C++11 之前:需要向后兼容时
-
简单类型:对于基本类型,性能差异不大
insert: 插入元素
语法结构:std::pair<iterator, bool> insert(const value_type& val);
(1) m.insert(pair<const Key, T>(key, value)); 或 m.insert(make_pair(key, value));
(2) m.insert(iterator hint, pair<const Key, T>(key, value)); 使用提示位置插入
(3) m.insert(InputIterator first, InputIterator last); 插入一个范围
(4) m.insert(initializer_list<pair<const Key, T>> il); 插入初始化列表返回值是一个
pair对象,包含两个部分:1.
first成员:iterator
如果插入成功 → 指向新插入的元素
如果插入失败(键已存在) → 指向已存在的元素
2.
second成员:bool
true→ 插入成功(键之前不存在)
false→ 插入失败(键已存在) // insert() - 插入元素 map<int, string> m; // 插入单个元素(pair) m.insert(pair<int, string>(1, "one")); m.insert(make_pair(2, "two")); m.insert({3, "three"}); // C++11 // 带提示的插入 auto hint1 = m.begin(); m.insert(hint, {4, "four"}); // 提示插入位置 // 插入范围 map<int, string> m2; m2.insert(m.begin(), m.end());
emplace: 构造并插入元素,参数是构造键值对所需的参数。
它通过完美转发直接在容器内部构造元素,避免了不必要的拷贝和移动操作,提高了性能,特别是对于构造成本高的对象
语法结构:std::pair<iterator, bool> emplace(Args&&... args);
m.emplace(args...); // args用于构造一个pairemplace_hint: 使用提示位置构造并插入元素。
template <class... Args>
函数签名:iterator emplace_hint(const_iterator hint, Args&&... args);emplace_hint是 emplace 的一个变体,它允许你提供一个提示(hint)位置,用于指示新元素可能会插入的位置。这个提示是一个迭代器,对于有序容器(如 map),如果提示位置正确(即新元素应该紧挨着 hint 指向的元素之前插入),那么插入操作可以在常数时间 O(1) 内完成,而不是对数时间 O(log n)。如果提示不正确,则插入操作会退化为普通的插入,即 O(log n)。但是,请注意,对于无序容器(如 unordered_map),这个提示通常会被忽略,因为无序容器的元素位置由哈希值决定。
std::map<int, std::string> m = {{0, "x"},{1, "a"}, {5, "e"}}; // emplace() - 原位构造(C++11) m.emplace(5, "five"); // 避免临时对象构造 // 带提示的插入 auto hint = m.find(5); // 使用 find找到键为5的元素 auto result = m.emplace_hint(hint, 3, "c"); // 从hint前一个元素开始尝试插入键3
erase: 删除元素
(1) m.erase(iterator position); 删除指定位置的元素
(2) m.erase(const key_type& key); 删除键为key的元素,返回删除的元素个数(对于map,为0或1)
(3) m.erase(iterator first, iterator last); 删除一个范围
clear(): 清空map
swap(map& other): 交换两个map的内容
std::map<int, std::string> m = {{0, "x"},{1, "a"}, {5, "e"}}; //erase() - 删除元素 m.erase(1); // 按键删除 m.erase(m.begin()); // 按迭代器删除 m.erase(m.begin(), m.end()); // 按范围删除 // clear() - 清空所有元素 m.clear(); // swap() - 交换两个map内容 map<int, string> m3; m.swap(m3);
查找操作函数
函数 签名 返回类型 是否修改容器 异常保证 find()iterator find(const key_type&)iterator否 强异常保证 count()size_type count(const key_type&) constsize_type否 无异常 lower_bound()iterator lower_bound(const key_type&)iterator否 强异常保证 upper_bound()iterator upper_bound(const key_type&)iterator否 强异常保证 equal_range()pair<iterator,iterator> equal_range(const key_type&)pair否 强异常保证 contains()bool contains(const key_type&) constbool否 强异常保证 map<int, string> m = {{1, "one"}, {2, "two"}, {3, "three"}}; // 1. find() - 查找指定键 auto it = m.find(2); if (it != m.end()) { cout << "Found: " << it->second << endl; } // 2. count() - 统计键出现次数(对于map总是0或1) size_t cnt = m.count(2); // 返回1 // 3. lower_bound() - 返回第一个不小于key的元素迭代器 auto lb = m.lower_bound(2); // 指向键为2的元素 // 4. upper_bound() - 返回第一个大于key的元素迭代器 auto ub = m.upper_bound(2); // 指向键为3的元素 // 5. equal_range() - 返回匹配键的范围(对于map是单个元素) auto range = m.equal_range(2); for (auto it = range.first; it != range.second; ++it) { // 处理元素 }
遍历map
std::map<int, std::string> m = {{1, "Red"}, {2, "Green"}, {3, "Blue"}}; // 方法1:使用迭代器 for (auto it = m.begin(); it != m.end(); ++it) { std::cout << "Key: " << it->first << ", Value: " << it->second << std::endl; } // 方法2:范围for循环(C++11) for (const auto& pair : m) { std::cout << "Key: " << pair.first << ", Value: " << pair.second << std::endl; } // 方法3:使用结构化绑定(C++17) for (const auto& [key, value] : m) { std::cout << "Key: " << key << ", Value: " << value << std::endl; }
观察器函数
key_comp(): 返回键的比较函数对象(默认是less<Key>) value_comp(): 返回值的比较函数对象,实际上是一个用于比较pair的函数对象,先比较键,再比较值。map<int, string, greater<int>> m; // 降序排列 // 1. key_comp() - 返回键比较函数 auto key_compare = m.key_comp(); bool result = key_compare(1, 2); // 对于greater<int>返回false // 2. value_comp() - 返回值比较函数 auto value_compare = m.value_comp(); bool result = value_compare( make_pair(1, "one"), make_pair(2, "two") );
C++17 新增特性
// 1. extract() - 节点句柄(可转移元素而不复制) map<int, string> m1 = {{1, "one"}, {2, "two"}}; map<int, string> m2; auto nh = m1.extract(1); // 提取节点 if (!nh.empty()) { m2.insert(std::move(nh)); // 插入到m2 } // 2. merge() - 合并两个map m1.merge(m2); // m2中元素移到m1,冲突元素保留在m2 // 3. insert_or_assign() - 插入或赋值 m1.insert_or_assign(3, "three"); // 插入 m1.insert_or_assign(3, "THREE"); // 更新
C++20 新增特性
// 1. contains() - 检查是否包含键(比find()更直观) if (m.contains(1)) { // 键存在 } // 2. 三路比较运算符支持 // map现在支持<=>运算符
自定义排序规则
// 按key降序排列 struct CompareDesc { bool operator()(int a, int b) const { return a > b; // 降序 } }; std::map<int, std::string, CompareDesc> m1; m1[3] = "Three"; m1[1] = "One"; m1[2] = "Two"; // 遍历时输出:3->Three, 2->Two, 1->One // 对于自定义类型作为key struct Person { std::string name; int age; // 需要定义小于运算符 bool operator<(const Person& other) const { if (name == other.name) return age < other.age; return name < other.name; } }; std::map<Person, int> personMap;
实际应用示例
#include <iostream> #include <map> #include <string> // 单词计数器 void wordCounter() { std::map<std::string, int> wordCount; std::string text = "apple banana apple orange banana apple"; size_t start = 0, end = 0; while ((end = text.find(' ', start)) != std::string::npos) { std::string word = text.substr(start, end - start); wordCount[word]++; start = end + 1; } // 最后一个单词 std::string lastWord = text.substr(start); wordCount[lastWord]++; // 输出结果 for (const auto& [word, count] : wordCount) { std::cout << word << ": " << count << std::endl; } } // 学生成绩管理系统 class StudentGrades { private: std::map<int, double> grades; // 学号 -> 成绩 public: void addGrade(int studentId, double grade) { grades[studentId] = grade; } void updateGrade(int studentId, double newGrade) { if (grades.find(studentId) != grades.end()) { grades[studentId] = newGrade; } } double getGrade(int studentId) const { auto it = grades.find(studentId); if (it != grades.end()) { return it->second; } return -1.0; // 表示未找到 } void printAll() const { for (const auto& [id, grade] : grades) { std::cout << "Student " << id << ": " << grade << std::endl; } } };
章节四. unordered_map
unordered_map基本特性
unordered_map是C++ STL中的关联容器,提供键值对存储,基于哈希表实现。它提供了平均常数时间复杂度的操作,但牺牲了元素顺序。在选择使用map还是unordered_map时,需要根据具体需求权衡有序性和性能。如果不需要有序数据,且有一个好的哈希函数,unordered_map通常是更好的选择。如果需要有序遍历或内存使用更可预测,则选择map。
键唯一性:每个键只能在unordered_map中出现一次
无序性:元素不以任何特定顺序存储,而是根据哈希值组织
平均常数时间复杂度:查找、插入、删除操作平均O(1),最坏情况O(n)
单向迭代器:只支持正向迭代器
内存不连续:元素散列存储
头文件: #include <unordered_map>
使用建议
需要顺序访问或范围查询 → 选择 map
需要最高性能查找,数据量大 → 选择 unordered_map
键类型简单,哈希质量好 → 优先考虑 unordered_map
内存受限,需要可预测内存 → 选择 map
需要反向迭代器 → 只能选择 map
键类型复杂,无良好哈希 → 选择 map
频繁插入删除,不关心顺序 → 选择 unordered_map
| 特性 | map | unordered_map |
|---|---|---|
| 底层实现 | 红黑树(平衡二叉搜索树) | 哈希表 |
| 元素顺序 | 按键排序(默认升序) | 无序 |
| 查找复杂度 | O(log n) | 平均 O(1),最坏 O(n) |
| 内存布局 | 树结构,节点分散 | 桶数组 + 链表/红黑树 |
| 迭代器类型 | 双向迭代器 | 单向迭代器 |
| 所需操作 | 需要 < 比较运算符 | 需要 == 和哈希函数 |
| 内存开销 | 每个节点3个指针 | 桶数组 + 冲突处理开销 |
| 缓存友好性 | 较差(节点分散) | 较好(桶内元素连续) |
| 操作 | map | unordered_map | 说明 |
|---|---|---|---|
| 插入单个元素 | O(log n) | 平均 O(1),最坏 O(n) | unordered_map最坏情况:所有键哈希冲突 |
| 查找元素 | O(log n) | 平均 O(1),最坏 O(n) | |
| 删除元素 | O(log n) | 平均 O(1),最坏 O(n) | |
| 遍历所有元素 | O(n) | O(n) | |
| 范围查询 | O(log n + k) | 不支持高效范围查询 | map支持[lower_bound, upper_bound) |
| 获取最小键 | O(log n) | O(n) | unordered_map需要遍历 |
| 获取最大键 | O(log n) | O(n) | unordered_map需要遍历 |
| operator[]访问 | O(log n) | 平均 O(1),最坏 O(n) |
| 内存方面 | map | unordered_map |
|---|---|---|
| 每个节点内存 | 约 40-48字节(64位) (3指针+颜色+键值) | 约 24-32字节 (1指针+键值+可能缓存哈希) |
| 额外开销 | 树结构管理开销 | 桶数组 + 冲突处理开销 |
| 内存连续性 | 差(节点分散) | 较好(桶数组连续,桶内链表可能连续) |
| 缓存友好性 | 低 | 较高 |
| 内存可预测性 | 较高(固定结构) | 较低(依赖哈希分布) |
| rehash开销 | 无 | 可能较大(重建哈希表) |
| 使用场景 | 推荐容器 | 原因 |
|---|---|---|
| 需要有序遍历 | map | 自动排序,支持范围查询 |
| 频繁查找,不关心顺序 | unordered_map | 平均O(1)查找 |
| 内存有限 | map | 内存使用更可预测 |
| 需要反向迭代 | map | 支持rbegin()/rend() |
| 键类型无良好哈希 | map | 不需要哈希函数 |
| 需要稳定迭代器 | map | 迭代器不因插入失效 |
| 大数据量插入删除 | unordered_map | 平均O(1)操作 |
| 缓存友好性重要 | unordered_map | 连续内存访问 |
| 简单键类型,性能关键 | unordered_map | 哈希表通常更快 |
| 复杂键类型 | map | 避免哈希函数设计 |
章节五:基本函数接口
构造函数和析构函数
构造函数
// 1. 默认构造函数 unordered_map<Key, T> m1; // 2. 指定桶数量的构造函数 unordered_map<Key, T> m2(bucket_count); // 3. 范围构造函数 unordered_map<Key, T> m3(first, last); // 4. 拷贝构造函数 unordered_map<Key, T> m4(otherMap); // 5. 移动构造函数(C++11) unordered_map<Key, T> m5(std::move(otherMap)); // 6. 初始化列表构造函数(C++11) unordered_map<Key, T> m6 = {{1, "one"}, {2, "two"}}; // 7. 带桶数量和哈希函数、键相等谓词的构造函数 unordered_map<Key, T, Hash, KeyEqual> m7(bucket_count, hasher, key_equal);
构造函数类型 map unordered_map 默认构造函数 map<Key,T>()unordered_map<Key,T>()范围构造函数 map(first, last)unordered_map(first, last)拷贝构造函数 map(other)unordered_map(other)移动构造函数 map(std::move(other))unordered_map(std::move(other))初始化列表 map({ {k1,v1}, {k2,v2} })unordered_map({ {k1,v1}, {k2,v2} })带比较函数构造 map(Compare comp)不支持 带桶数构造 不支持 unordered_map(size_type n)带哈希函数构造 不支持 unordered_map(size_type n, Hash hasher)完整参数构造 不支持 unordered_map(size_type n, Hash hasher, KeyEqual eq)析构函数
自动调用,释放所有元素内存。
容量相关函数
// 1. empty() - 检查是否为空 bool isEmpty = m.empty(); // 2. size() - 返回元素数量 size_t count = m.size(); // 3. max_size() - 返回最大可能元素数 size_t maxCount = m.max_size();
函数 map unordered_map 说明 empty() ✓ ✓ 检查是否为空 size() ✓ ✓ 返回元素数量 max_size() ✓ ✓ 返回最大可能元素数 bucket_count() ✗ ✓ 返回桶的数量 max_bucket_count() ✗ ✓ 返回最大桶数 bucket_size(n) ✗ ✓ 返回第n个桶的元素数 bucket(key) ✗ ✓ 返回键所在的桶索引 load_factor() ✗ ✓ 当前负载因子 max_load_factor() ✗ ✓ 获取/设置最大负载因子 rehash(n) ✗ ✓ 设置桶数至少为n reserve(n) ✗ ✓ 预留空间容纳n个元素
元素访问函数
// 1. operator[] - 访问或插入元素 unordered_map<int, string> m; m[1] = "one"; // 插入 string val = m[1]; // 访问 m[2]; // 如果不存在,值初始化 // 2. at() - 带边界检查的访问(C++11) string val = m.at(1); // 如果键不存在,抛出std::out_of_range
函数 map unordered_map 说明 operator[] ✓ ✓ 访问或插入元素 at() ✓ ✓ 带边界检查,键不存在时抛异常 find() ✓ ✓ 查找元素,返回迭代器 count() ✓ ✓ 返回1或0 lower_bound() ✓ ✗ 第一个不小于key的元素 upper_bound() ✓ ✗ 第一个大于key的元素 equal_range() ✓ ✓ 返回匹配范围(对map为单个) contains() C++20 ✓ C++20 ✓ 检查是否包含键
修改器函数
multimap::insert总是返回iterator(没有 bool),因为总是插入成功// 1. insert() - 插入元素 unordered_map<int, string> m; m.insert({1, "one"}); m.insert(make_pair(2, "two")); m.insert(pair<int, string>(3, "three")); // 2. emplace() - 原位构造(C++11) m.emplace(4, "four"); // 3. emplace_hint() - 带提示的原位构造(提示可能被忽略) m.emplace_hint(m.begin(), 5, "five"); // 4. erase() - 删除元素 m.erase(1); // 按键删除 m.erase(m.begin()); // 按迭代器删除 m.erase(m.begin(), m.end()); // 按范围删除 // 5. clear() - 清空所有元素 m.clear(); // 6. swap() - 交换两个unordered_map内容 unordered_map<int, string> m2; m.swap(m2);
函数 map unordered_map 说明 insert() ✓ ✓ 插入元素 insert_or_assign() C++17 ✓ C++17 ✓ 插入或更新 emplace() ✓ ✓ 原位构造 emplace_hint() ✓ ✓ 带提示原位构造 try_emplace() C++17 ✓ C++17 ✓ 键不存在时构造 erase() ✓ ✓ 删除元素 clear() ✓ ✓ 清空容器 swap() ✓ ✓ 交换内容 extract() C++17 ✓ C++17 ✓ 提取节点 merge() C++17 ✓ C++17 ✓ 合并容器
查找操作函数
unordered_map<int, string> m = {{1, "one"}, {2, "two"}}; // 1. find() - 查找指定键 auto it = m.find(2); if (it != m.end()) { cout << "Found: " << it->second << endl; } // 2. count() - 统计键出现次数(对于unordered_map总是0或1) size_t cnt = m.count(2); // 返回1 // 3. equal_range() - 返回匹配键的范围(对于unordered_map是单个元素) auto range = m.equal_range(2); for (auto it = range.first; it != range.second; ++it) { // 处理元素 }
桶接口函数(map没有这些)
// 1. bucket_count() - 返回桶的数量 size_t bc = m.bucket_count(); // 2. max_bucket_count() - 返回桶的最大可能数量 size_t mbc = m.max_bucket_count(); // 3. bucket_size(n) - 返回第n个桶中的元素数量 size_t bs = m.bucket_size(0); // 4. bucket(key) - 返回键key所在的桶编号 size_t b = m.bucket(1); // 5. begin(n), end(n) - 返回第n个桶的迭代器 for (size_t i = 0; i < m.bucket_count(); ++i) { for (auto it = m.begin(i); it != m.end(i); ++it) { // 处理第i个桶中的元素 } }
哈希策略函数(map没有这些)
// 1. load_factor() - 返回负载因子(元素数/桶数) float lf = m.load_factor(); // 2. max_load_factor() - 返回或设置最大负载因子 float mlf = m.max_load_factor(); // 获取 m.max_load_factor(0.7f); // 设置 // 3. rehash(n) - 设置桶数量至少为n,并重新哈希 m.rehash(20); // 4. reserve(n) - 预留空间,使容器可以容纳至少n个元素而不触发rehash m.reserve(100);
观察器函数
// 1. hash_function() - 返回哈希函数 auto hasher = m.hash_function(); size_t hash_value = hasher(1); // 计算键1的哈希值 // 2. key_eq() - 返回键相等性比较函数 auto key_equal = m.key_eq(); bool equal = key_equal(1, 1); // 比较两个键是否相等
迭代器相关函数
unordered_map<int, string> m = {{1, "one"}, {2, "two"}}; // 正向迭代器 for (auto it = m.begin(); it != m.end(); ++it) for (auto it = m.cbegin(); it != m.cend(); ++it) // const迭代器 // 注意:unordered_map没有反向迭代器(rbegin, rend)
迭代器特性 map unordered_map 迭代器类型 双向迭代器 前向迭代器 反向迭代器 ✓ ✗ const迭代器 ✓ ✓ 迭代器稳定性 高(除删除元素) 低(rehash时失效) 迭代顺序 按键升序排列 无特定顺序 基于范围for ✓ ✓ begin()/end() ✓ ✓ rbegin()/rend() ✓ ✗ cbegin()/cend() ✓ ✓ crbegin()/crend() ✓ ✗
C++17 新增特性
// 1. extract() - 节点句柄 unordered_map<int, string> m1 = {{1, "one"}}; unordered_map<int, string> m2; auto nh = m1.extract(1); // 提取节点 if (!nh.empty()) { m2.insert(std::move(nh)); } // 2. merge() - 合并两个unordered_map m1.merge(m2); // 3. insert_or_assign() - 插入或赋值 m1.insert_or_assign(3, "three");
C++20 新增特性
// 1. contains() - 检查是否包含键 if (m.contains(1)) { // 键存在 }
自定义哈希函数和相等比较函数示例
// 自定义键类型 struct MyKey { int id; string name; }; // 自定义哈希函数 struct MyKeyHash { size_t operator()(const MyKey& k) const { return hash<int>()(k.id) ^ (hash<string>()(k.name) << 1); } }; // 自定义相等比较函数 struct MyKeyEqual { bool operator()(const MyKey& lhs, const MyKey& rhs) const { return lhs.id == rhs.id && lhs.name == rhs.name; } }; // 使用自定义哈希和相等比较的unordered_map unordered_map<MyKey, string, MyKeyHash, MyKeyEqual> m;
要求 map unordered_map 键类型要求 需要 <运算符或自定义Compare需要 ==运算符和哈希函数自定义比较器 可指定Compare类型 不需要(但可指定KeyEqual) 自定义哈希 不需要 必须为键类型提供哈希 透明比较器 C++14支持 C++20支持
完整示例
#include <iostream> #include <unordered_map> #include <string> int main() { // 创建unordered_map std::unordered_map<int, std::string> um; // 插入元素 um.insert({1, "one"}); um.emplace(2, "two"); um[3] = "three"; // 遍历(无序) std::cout << "Elements in unordered_map:" << std::endl; for (const auto& [key, value] : um) { std::cout << key << ": " << value << std::endl; } // 查找 if (auto it = um.find(2); it != um.end()) { std::cout << "Found: " << it->second << std::endl; } // 桶信息 std::cout << "Bucket count: " << um.bucket_count() << std::endl; std::cout << "Load factor: " << um.load_factor() << std::endl; // 删除 um.erase(1); // 检查存在 if (um.contains(3)) { std::cout << "3 exists" << std::endl; } return 0; }
章节六:multimap
multimap是C++ STL中的关联容器,类似于map,但允许重复键。也就是说,多个元素可以具有相同的键。multimap中的元素也是键值对,并且按照键进行排序(默认升序)。multimap同样基于红黑树实现。
在处理一个键对应多个值的场景时(如电话簿、数据库索引等),multimap是一个很好的选择。使用multimap时,要注意其与map的区别,特别是访问和查找元素的方式。
允许重复键:与map不同,multimap可以存储多个具有相同键的元素。
自动排序:元素按照键的顺序进行排序(默认使用
<运算符,可以自定义比较函数)。双向迭代器:支持正向和反向遍历。
查找效率:基于红黑树,查找、插入、删除操作的时间复杂度为O(log n)。
头文件:#include <map>
multimap与map的主要区别
特性 map multimap 键唯一性 键唯一 允许重复键 插入操作 插入重复键时会失败(insert返回的bool为false) 总是可以插入,即使键重复 访问元素 可以使用 operator[]和at()通过键直接访问值没有 operator[]和at(),因为一个键可能对应多个值查找元素 使用 find()返回一个迭代器(如果键存在)使用 find()返回第一个匹配键的迭代器,可能需要遍历所有相同键的元素
章节七:基本函数接口
构造函数
multimap的构造函数与map类似:
// 1. 默认构造函数 multimap<Key, T> mm1; // 2. 范围构造函数 multimap<Key, T> mm2(first, last); // 3. 拷贝构造函数 multimap<Key, T> mm3(otherMultimap); // 4. 移动构造函数(C++11) multimap<Key, T> mm4(std::move(otherMultimap)); // 5. 初始化列表构造函数(C++11) multimap<Key, T> mm5 = {{1, "one"}, {2, "two"}, {2, "second two"}}; // 6. 带比较函数的构造函数 multimap<Key, T, Compare> mm6(comp);
插入操作
由于multimap允许重复键,插入操作总是成功。
multimap<int, string> mm; // 1. 使用insert插入pair mm.insert(pair<const int, string>(1, "one")); mm.insert(make_pair(2, "two")); // 2. 使用insert插入多个相同键的元素 mm.insert({3, "three"}); mm.insert({3, "3"}); // 3. 使用emplace(C++11)直接构造元素 mm.emplace(4, "four"); mm.emplace(4, "4"); // 4. 使用emplace_hint带提示插入 auto hint = mm.begin(); mm.emplace_hint(hint, 5, "five");注意:multimap没有
operator[],因为一个键可能对应多个值,无法确定返回哪一个。
访问元素
由于一个键可能对应多个值,multimap不提供
operator[]和at()。访问元素通常通过迭代器。multimap<int, string> mm = {{1, "a"}, {2, "b"}, {2, "c"}}; // 遍历所有元素 for (auto it = mm.begin(); it != mm.end(); ++it) { cout << it->first << " -> " << it->second << endl; } // 基于范围的for循环 for (const auto& kv : mm) { cout << kv.first << " -> " << kv.second << endl; }
查找操作
multimap提供了多个查找函数,但由于键可以重复,所以这些函数的行为与map有所不同。
multimap<int, string> mm = {{1, "a"}, {2, "b"}, {2, "c"}, {2, "d"}, {3, "e"}}; // 1. find():返回第一个匹配键的迭代器 auto it = mm.find(2); if (it != mm.end()) { cout << "找到键2的第一个值: " << it->second << endl; // b } // 2. count():返回匹配键的元素数量 size_t cnt = mm.count(2); cout << "键2出现了 " << cnt << " 次" << endl; // 3 // 3. lower_bound():返回第一个不小于key的元素的迭代器 auto lb = mm.lower_bound(2); cout << "lower_bound(2): " << lb->first << " -> " << lb->second << endl; // 2 -> b // 4. upper_bound():返回第一个大于key的元素的迭代器 auto ub = mm.upper_bound(2); cout << "upper_bound(2): " << ub->first << " -> " << ub->second << endl; // 3 -> e // 5. equal_range():返回一个pair,表示匹配键的范围 auto range = mm.equal_range(2); for (auto it = range.first; it != range.second; ++it) { cout << it->first << " -> " << it->second << endl; // 打印所有键为2的元素 }
删除操作
multimap<int, string> mm = {{1, "a"}, {2, "b"}, {2, "c"}, {3, "d"}}; // 1. 按键删除:删除所有匹配键的元素 size_t erased = mm.erase(2); // 返回删除的元素数量,此处为2 cout << "删除了 " << erased << " 个元素" << endl; // 2. 按迭代器删除 auto it = mm.find(1); if (it != mm.end()) { mm.erase(it); // 删除键为1的元素 } // 3. 按范围删除 auto first = mm.lower_bound(3); auto last = mm.upper_bound(3); mm.erase(first, last); // 删除键为3的元素(这里只有一个)
其他操作
容量相关:
bool isEmpty = mm.empty(); size_t size = mm.size(); size_t maxSize = mm.max_size();交换:
multimap<int, string> mm1, mm2; mm1.swap(mm2);比较函数:
auto comp = mm.key_comp(); auto valueComp = mm.value_comp();
示例:使用multimap存储多个值
#include <iostream> #include <map> #include <string> using namespace std; int main() { multimap<string, string> phonebook; // 插入多个相同键(人名)的电话号码 phonebook.insert({"Alice", "123-456-7890"}); phonebook.insert({"Alice", "234-567-8901"}); phonebook.insert({"Bob", "345-678-9012"}); phonebook.insert({"Alice", "456-789-0123"}); // 查找Alice的所有电话号码 auto range = phonebook.equal_range("Alice"); cout << "Alice的电话号码:" << endl; for (auto it = range.first; it != range.second; ++it) { cout << " " << it->second << endl; } // 统计Alice的电话号码数量 cout << "Alice有 " << phonebook.count("Alice") << " 个电话号码" << endl; return 0; }
multimap与unordered_multimap
multimap是有序的,而unordered_multimap是无序的(基于哈希表)。选择哪一个取决于是否需要排序以及性能需求。
| 特性 | multimap | unordered_multimap |
|---|---|---|
| 排序 | 有序 | 无序 |
| 查找复杂度 | O(log n) | 平均O(1),最坏O(n) |
| 内存开销 | 较大(树结构) | 较大(哈希表) |
| 迭代器稳定性 | 稳定(除删除元素) | 可能失效(rehash时) |
章节八:unordered_multimap
这个比较冷门,而且根据上面的规律你应该自己可以推理,暂时我不写了
1286

被折叠的 条评论
为什么被折叠?



