48 C++ STL模板库17-容器9-关联容器-映射(map)多重映射(multimap)
STL模板库-容器9-关联容器-映射(map)多重映射(multimap)
文章目录
- STL模板库-容器9-关联容器-映射(map)多重映射(multimap)
- 一、映射(map)多重集映射(multimap)的特点
- 二、共有函数(map 和 multimap 均支持)
- 1. 构造函数与赋值操作
- 1. 函数说明
- 2. 用法示例
- 3. 详细说明
- 2. 迭代器操作
- 1. 函数说明
- 2. 用法示例
- 3. 详细说明
- 3. 容量查询
- 1. 函数说明
- 2. 用法示例
- 3. 详细说明
- 4. 修改器(基础操作)
- 1. 函数说明
- 2. 用法示例
- 3. 详细说明
- 5. 查找操作
- 1. 函数说明
- 2. 用法示例
- 3. 详细说明
- 6. 观察器
- 1. 函数说明
- 2. 用法示例
- 3. 详细说明
- 7. 非成员函数
- 1. 函数说明
- 2. 用法示例
- 3. 详细说明
- 三、map 和 multimap 功能差异对比表
- 1. 函数说明
- 2. 用法示例
- 3. 详细说明
- 四、map 独有函数
- 1. 函数说明
- 2. 用法示例
- 3. 详细说明
- 五、map 和 multimap 关键差异说明
- 六、完整用法示例
映射(map)多重集映射(multimap) 是STL模板库里面的一种关联容器。存储键值对,键唯一,值可重复。
映射包含了多重映射的全部操作,元素以键值对有序排列(默认升序),映射的元素键不能重复,多重映射的元素键可以重复,两者使用方法相同,所有操作定义在头文件
<map>
中。
一、映射(map)多重集映射(multimap)的特点
-
有序关联容器
- 存储键值对(pair<const Key, T>),键自动排序
- 底层实现:基于红黑树(平衡二叉搜索树),保证元素始终按键排序(默认升序,可自定义比较函数)。
- 时间复杂度:插入、删除、查找操作均为 O(log n),适合动态数据管理。
-
键值对存储
- 元素类型为
pair<const Key, T>
,里面每个元素都是成对的,前面的Key
称之为“键”不可修改,不能重复。后面的(T
)称之为“值”可修改(map
允许通过迭代器直接修改值)。
- 元素类型为
二、共有函数(map 和 multimap 均支持)
1. 构造函数与赋值操作
1. 函数说明
函数签名 | 功能说明 |
---|---|
map()/multimap() | 默认构造函数,创建空容器 |
map(InputIt first, InputIt last) | 通过迭代器范围构造 |
map(std::initializer_list<value_type>) | 初始化列表构造 (C++11) |
operator=(const map&) | 拷贝赋值 |
operator=(map&&) | 移动赋值 (C++11) |
operator=(std::initializer_list<value_type>) | 初始化列表赋值 (C++11) |
~map()/~multimap() | 析构函数 |
2. 用法示例
-
默认构造(空容器)
std::map<std::string, int> map1; // 空map std::multimap<int, double> multimap1; // 空multimap // ✅ 特点:初始size=0,适合动态填充数据
-
迭代器范围构造
std::vector<std::pair<int, std::string>> vec = {{101, "Alice"}, {102, "Bob"} }; std::map<int, std::string> map2(vec.begin(), vec.end()); // ✅ 输出:{101:"Alice", 102:"Bob"}
-
初始化列表构造(C++11)
std::map<std::string, int> map3{{"Apple", 3}, {"Banana", 5}, {"Cherry", 2} }; // ✅ 输出:{"Apple":3, "Banana":5, "Cherry":2}std::multimap<char, float> multimap2{{'A', 1.1f}, {'A', 1.2f}, {'B', 2.0f} }; // ✅ 输出:{'A':1.1, 'A':1.2, 'B':2.0} (multimap允许重复键)
-
拷贝赋值
std::map<int, std::string> map4 = map2; // 深拷贝 // ✅ map4内容:{101:"Alice", 102:"Bob"} (独立副本)
-
移动赋值(C++11)
std::map<int, std::string> map5 = std::move(map4); // ✅ 结果: // map5: {101:"Alice", 102:"Bob"} // map4变为空容器 (资源所有权转移)
-
初始化列表赋值(C++11)
map5 = {{201, "Tom"}, {202, "Jerry"}}; // 覆盖原内容 // ✅ 新内容:{201:"Tom", 202:"Jerry"}
-
隐式析构
{std::map<int, int> tempMap{{1,10}, {2,20}}; } // 离开作用域时自动调用 ~map() 释放内存 // ✅ 无需手动调用,RAII机制自动管理
3. 详细说明
-
排序特性
std::map<int, int> {{3,30}, {1,10}, {2,20}}; // 自动排序为:{1:10, 2:20, 3:30}
-
重复键处理
std::map<std::string, int> {{"A",1}, {"A",2}}; // 仅保留最后一个值 {"A":2}
-
multimap特性
std::multimap<int, std::string> mm{{1, "A"}, {1, "B"}, {2, "C"} // ✅ 允许重复键 };
查找时需用
equal_range()
获取重复键范围 -
自定义比较器
struct CaseInsensitiveCompare {bool operator()(const std::string& a, const std::string& b) const {return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end(),[](char c1, char c2) { return std::tolower(c1) < std::tolower(c2); });} }; std::map<std::string, int, CaseInsensitiveCompare> caseInsensitiveMap;
-
自定义比较器
auto cmp = [](const std::string& a, const std::string& b) {return a.length() < b.length(); // 按长度排序 }; std::map<std::string, int, decltype(cmp)> customMap(cmp);
-
性能警告
避免在循环中反复构造/赋值大容器,优先使用移动语义:// 推荐做法 ✅ std::map<int, Data> process(MapType&& input) {return std::move(input); // 零拷贝返回 }
2. 迭代器操作
1. 函数说明
函数签名 | 功能说明 |
---|---|
begin()/cbegin() | 返回首元素迭代器 |
end()/cend() | 返回尾后迭代器 |
rbegin()/crbegin() | 返回反向迭代器 |
rend()/crend() | 返回反向尾后迭代器 |
2. 用法示例
-
正向遍历
std::map<std::string, int> m{{"A",1}, {"B",2}, {"C",3}};// 普通迭代器(可修改元素) auto it = m.begin(); // 获取首元素迭代器 std::cout << it->first << ":" << it->second <<std::endl; // 输出 A:1 it->second = 10; // ✅ 允许修改值 //输出 A:10 B:2 C:3 print_container(m);// 常量迭代器(C++11起) auto cit = m.cbegin(); // 常量迭代器 //
-
遍历整个容器
// 传统循环(C++03风格) for (auto it = m.begin(); it != m.end(); ++it) {std::cout << it->first << "→" << it->second << " "; } // 输出:A→10 B→2 C→3 // 反向遍历(C++11起) for (auto rit = m.rbegin(); rit != m.rend(); ++rit) {std::cout << rit->first << " "; // 输出:C B A }
-
反向迭代器特殊用法
// 获取最后一个有效元素 auto last_it = m.rbegin(); std::cout << last_it->first; // 输出 C // 反向迭代器转正向迭代器 auto normal_it = last_it.base(); // 指向结束位置 std::cout << (--normal_it)->first; // 输出 C
-
常量容器场景
const std::map<int, std::string> cm{{1,"一"}, {2,"二"}}; // auto mod_it = cm.begin(); ❌ 普通迭代器被禁用 auto const_it = cm.cbegin(); // ✅ 必须用常量迭代器 std::cout << const_it->second; // 输出 一
3. 详细说明
迭代器关键特性总结
-
迭代器失效规则
- 插入/删除元素可能使当前迭代器失效
- 修改
key
会导致未定义行为(需先删除再插入)
-
性能特性
m.begin(); // O(1) 时间复杂度 m.rbegin(); // O(1) (底层实现为 end()-- 操作)
-
通用性扩展
// 适用于所有STL有序容器 std::multimap<int, int> mm; auto mm_it = mm.crbegin(); // ✅ 反向常量迭代器
-
结构化绑定迭代
for (const auto& [key, value] : countryCodes) {std::cout << key << " : " << value << "\n"; }
3. 容量查询
1. 函数说明
函数签名 | 功能说明 |
---|---|
empty() const | 检查容器是否为空 |
size() const | 返回元素数量 |
max_size() const | 返回最大可能元素数 |
2. 用法示例
-
empty() const
- 空容器检测std::map<std::string, int> inventory;// 检查容器是否为空 if (inventory.empty()) {std::cout << "仓库为空,请及时补货!\n";// ✅ 输出:仓库为空,请及时补货! }inventory.insert({"手机", 50}); if (!inventory.empty()) {std::cout << "仓库有存货,可正常销售\n";// ✅ 输出:仓库有存货,可正常销售 }
关键应用场景:
- 数据加载前的预检查
- 循环遍历前的安全防护
- 资源释放前的状态确认
-
size() const
- 元素数量统计std::multimap<int, std::string> employees{ {101, "刘备"}, {102, "关羽"}, {102, "张飞"} };// 获取当前元素数量 size_t count = employees.size(); std::cout << "公司员工总数:" << count << "\n"; // ✅ 输出:公司员工总数:3 // 与empty()结合使用 while (employees.size() > 0) {auto it = employees.begin();std::cout << "解雇员工:" << it->second << "\n";employees.erase(it); } /* ✅ 输出: 解雇员工:刘备 解雇员工:关羽 解雇员工:张飞 */
关键特性:
容器类型 size()
行为时间复杂度 std::map
返回唯一键值对数量 O(1) std::multimap
返回所有元素数量(含重复键) O(1) -
max_size() const
- 容量极限检测std::map<int, double> dataMap;// 获取理论最大容量 size_t maxCap = dataMap.max_size(); std::cout << "最大可存储元素数:" << maxCap << "\n"; /* ✅ 输出示例(64位系统): 最大可存储元素数:384307168202282325 */// 实际应用:内存预判 size_t required = 1'000'000; if (required < dataMap.max_size()) {} else {std::cerr << "超出系统支持的最大容量!"; }
3. 详细说明
使用建议
-
优先选择
empty()
而非size()==0
// ✅ 推荐做法 if (container.empty()) { ... }// ⚠️ 不推荐(某些容器size()非O(1)) if (container.size() == 0) { ... }
-
max_size()
的实用价值// 典型应用:内存敏感系统 if (inputData.size() > container.max_size() / 2) {throw std::bad_alloc("内存需求超警戒线"); }
4. 修改器(基础操作)
1. 函数说明
函数签名 | 功能说明 |
---|---|
insert(const value_type&) | 插入元素(键存在时行为不同,见差异表) |
emplace(Args&&...) | 原位构造元素 (C++11) |
emplace_hint(const_iterator, Args&&...) | 带位置提示的原位构造 |
erase(iterator pos) | 删除指定位置元素 |
erase(const key_type& key) | 删除所有匹配键的元素 |
clear() | 清空所有元素 |
swap(map& other) | 交换容器内容 |
extract(const_iterator) | 移除节点并返回节点句柄 (C++17) |
extract(const key_type&) | 按键移除节点 (C++17) |
merge(map& source) | 合并两个容器 (C++17) |
2. 用法示例
-
insert(const value_type&)
std::map<int, std::string> m; // ✅ 单元素插入 auto [it1, success1] = m.insert({101, "Alice"}); // success1=true, it1->second="Alice" std::cout <<it1->first<<"->" <<it1->second <<' '<< std::boolalpha<<success1<<std::endl; // 🔑 键已存在时不覆盖(multimap允许重复) auto [it2, success2] = m.insert({101, "Bob"}); // success2=false, 值仍为"Alice" std::cout <<it2->first<<"->" <<it2->second <<' '<< std::boolalpha<<success2<<std::endl;
std::map
容器本身没有first/second
成员,该语法属于std::pair
元素
-
emplace(Args&&...)
(C++11)// ✅ 免拷贝构造(效率更高) auto [it, success] = m.emplace(102, "Charlie"); // 等效于 m.insert(std::make_pair(102, "Charlie"))
-
emplace_hint(const_iterator, Args&&...)
// ✅ 提示插入位置(优化红黑树平衡) auto hint = m.find(102); m.emplace_hint(hint, 103, "David"); // 在102后快速插入
-
erase(iterator)
/erase(key)
// 删除特定位置 auto it = m.find(101); if (it != m.end()) m.erase(it); // ✅ 删除101// 删除所有匹配键(multimap删除全部重复键) std::multimap<int, std::string> mm{{1,"A"}, {1,"B"}}; mm.erase(1); // ✅ 两个元素均被删除
-
clear()
std::map<int, int> data{{1,10}, {2,20}}; data.clear(); // ✅ 清空容器 std::cout << data.size(); // 输出:0
-
swap(map& other)
std::map<int, std::string> mapA{{1,"X"}}; std::map<int, std::string> mapB{{2,"Y"}};mapA.swap(mapB); // ✅ 高效交换(O(1)复杂度) // mapA: {2:"Y"}, mapB: {1:"X"}
-
extract()
(C++17)std::map<int, std::string> src{{3,"Apple"}, {4,"Banana"}}; std::map<int, std::string> dst;// 提取节点(不销毁元素) auto node = src.extract(3); // ✅ 从src移除 node.key() = 5; // 修改键(提取后允许修改key) dst.insert(std::move(node)); // 插入新容器 // dst: {5:"Apple"}, src: {4:"Banana"}
-
merge(map& source)
(C++17)std::map<int, std::string> main{{1,"A"}}; std::map<int, std::string> sub{{2,"B"}, {3,"C"}};main.merge(sub); // ✅ 合并sub到main // main: {1:"A", 2:"B", 3:"C"}, sub变为空
3. 详细说明
-
⚡ 操作特性对比表
操作 时间复杂度 键冲突处理 容器兼容性 insert
O(log n) map:拒绝插入;multimap:允许 map/multimap emplace
O(log n) 同insert map/multimap emplace_hint
O(1)~O(log n) 同insert map/multimap erase(iterator)
O(1) 精确删除单个元素 map/multimap extract
O(log n) 允许修改提取后的键 map/multimap(C++17) merge
O(n log n) 保留原容器所有元素 同类型容器(C++17) -
在频繁修改键的场景优先使用
extract() + insert()
(避免拷贝开销); -
批量操作使用
merge()
比循环插入快 50% 以上。
5. 查找操作
1. 函数说明
函数签名 | 功能说明 |
---|---|
find(const key_type&) | 返回匹配键的迭代器(未找到返回 end() ) |
count(const key_type&) const | 返回匹配键的元素数量 |
equal_range(const key_type&) | 返回匹配键的范围 pair<iter, iter> |
lower_bound(const key_type&) | 返回首个 ≥ key 的迭代器 |
upper_bound(const key_type&) | 返回首个 > key 的迭代器 |
2. 用法示例
-
find(const key_type&)
std::map<int, std::string> m{{101,"A"}, {103,"C"}, {105,"E"}};auto it = m.find(103); // ✅ 查找键103 if (it != m.end()) {std::cout << "找到: " << it->second<< std::endl; // 输出:C } else {std::cout << "未找到"<< std::endl; } // 查找不存在的键: auto it2 = m.find(102); // it2 == m.end() if (it2 == m.end()) std::cout << "未找到"<< std::endl;
-
count(const key_type&)
std::map<int, char> cnt{{1,'a'}, {2,'b'}, {2,'c'}}; // map自动去重 std::cout << "键2的数量: " << cnt.count(2)<< std::endl; // ✅ 输出:1 std::cout << "键3的数量: " << cnt.count(3)<< std::endl; // 输出:0// ⚠️ 对比multimap std::multimap<int, char> mm{{1,'x'}, {1,'y'}}; std::cout << mm.count(1); // 输出:2(计数重复键)
-
equal_range(const key_type&)
std::multimap<int, std::string> mm{{10, "A"}, {20, "B"}, {20, "C"}, {30, "D"} };auto [first, last] = mm.equal_range(20); // ✅ C++17结构化绑定 for (auto it = first; it != last; ++it) {std::cout << it->second << " "; // 输出:B C }
-
lower_bound(const key_type&)
std::map<int, char> data{{10,'J'}, {20,'K'}, {30,'L'}, {40,'M'} };auto lb = data.lower_bound(25); // ✅ 首个 ≥25的键 if (lb != data.end()) {std::cout << lb->first << ":" << lb->second; // 输出:30:L }// 边界测试 auto lb0 = data.lower_bound(5); // → 指向10:J auto lbMax = data.lower_bound(50); // → 返回end()
-
upper_bound(const key_type&)
std::map<char, int> letters{ {'a',1}, {'c',3}, {'e',5}, {'g',7} };auto ub = letters.upper_bound('d'); // ✅ 首个 >'d'的键 std::cout << ub->first<< std::endl; // 输出:e// 与lower_bound配合实现范围查询 auto lb = letters.lower_bound('b'); // → 指向'c' auto ub2 = letters.upper_bound('f'); // → 指向'g' for (; lb != ub2; ++lb) {std::cout << lb->first<<' '; // 输出:c e }
3. 详细说明
-
查找函数特性对比表
函数 返回类型 典型应用场景 时间复杂度 find(key)
迭代器 精确查找单个元素 O(log n) count(key)
size_t
检查键是否存在 O(log n) equal_range(key)
pair<iter, iter>
获取重复键的范围(multimap) O(log n) lower_bound(key)
迭代器 范围查询起点/键不存在时插入点 O(log n) upper_bound(key)
迭代器 范围查询终点 O(log n) -
应用场景示例:成绩分段统计
std::map<int, int> scoreRanges{{60, 1}, {70, 1}, {80, 2}, {90, 2}, {100, 1} };// 统计[75, 85)区间的成绩数量 auto low = scoreRanges.lower_bound(75); // 指向80 auto high = scoreRanges.upper_bound(85); // 指向90(不含)int count = 0; for (auto it = low; it != high; ++it) {count += it->second; } std::cout << "75~85分人数: " << count;
注意事项
-
迭代器有效性
find()
未找到时返回end()
,不可解引用lower_bound(100)
在最大键为90时返回end()
-
multimap 专用操作
// 遍历重复键的两种方式 // 方式1:equal_range auto [start, end] = mm.equal_range(key);// 方式2:lower_bound + upper_bound auto lb = mm.lower_bound(key); auto ub = mm.upper_bound(key);
-
性能优化
- 多次查找时优先缓存
end()
迭代器
auto endIt = m.end(); for (auto key : keysToFind) {if (m.find(key) != endIt) { ... } }
- 多次查找时优先缓存
-
-
在需要同时检查存在性和获取值的场景,优先使用
find()
而非count()
+operator[]
,避免两次查找开销。 -
范围查询时,
lower_bound
/upper_bound
组合比循环遍历快 10 倍以上。
6. 观察器
1. 函数说明
函数签名 | 功能说明 |
---|---|
key_comp() const | 返回键比较函数(如 less<Key> ) |
value_comp() const | 返回值比较函数 |
get_allocator() const | 返回关联的分配器 |
2. 用法示例
-
key_comp()
:获取键比较器#include <map>int main() {std::map<int, std::string> m;auto key_compare = m.key_comp(); // 获取键比较器(默认 less<int>)// 验证比较器逻辑 bool cmp1 = key_compare(10, 20); // true(10 < 20)bool cmp2 = key_compare(30, 15); // false(30 ≮ 15)std::cout <<cmp1<< ' ' <<cmp2<< std::endl;//1 0return 0; }
-
value_comp()
:获取值比较器#include <map>int main() {std::map<int, char> m{{1,'A'}, {3,'C'}, {5,'E'}};auto value_compare = m.value_comp(); // 比较器基于键(first元素)// 比较两个pair对象 auto p1 = std::pair<const int, char>(2, 'B');auto p2 = std::pair<const int, char>(4, 'D');bool cmp = value_compare(p1, p2); // true(2 < 4)std::cout <<cmp<< std::endl;//1return 0; }
-
get_allocator()
:获取内存分配器#include <map> #include <memory> #include <iostream> // 新增输出 int main() {std::map<int, double> m;auto alloc = m.get_allocator(); // ========== 验证1:分配器基本信息 ==========std::cout << "1. 分配器验证\n";std::cout << "分配器类型: " << typeid(decltype(alloc)).name() << "\n";std::cout << "支持的最大内存: " << alloc.max_size() << " bytes\n\n";// ========== 内存分配阶段 ==========using value_type = std::pair<const int, double>;std::cout << "2. 分配内存前指针状态: " << std::hex << (void*)nullptr << std::dec << std::endl;auto ptr = std::allocator_traits<decltype(alloc)>::allocate(alloc, 1);std::cout << "3. 分配内存后地址: " << std::hex << ptr << std::dec << " | 分配单元数: 1\n\n";// ========== 对象构造阶段 ==========std::cout << "4. 构造对象前内存内容: " << "key=" << ptr->first << ", value=" << ptr->second << " (随机值)\n";std::allocator_traits<decltype(alloc)>::construct(alloc, ptr, 101, 3.14);std::cout << "5. 构造对象后验证: key=" << ptr->first << ", value=" << ptr->second << "\n\n";// // ========== 销毁释放阶段 ==========std::cout << "6. 销毁对象前访问测试: " << ptr->second << " (正常访问)\n";std::allocator_traits<decltype(alloc)>::destroy(alloc, ptr);std::cout << "7. 对象已销毁 | 内存地址保留: " << std::hex << ptr << std::dec << "\n";// 安全防护:阻止悬空指针访问(演示用异常处理)try {std::cout << "8. 尝试访问已销毁对象: ";std::cout << ptr->second << std::endl; // 实际会崩溃 } catch(...) {std::cout << "捕获到异常(预期行为)\n";}std::allocator_traits<decltype(alloc)>::deallocate(alloc, ptr, 1);std::cout << "9. 内存已释放 | 原地址状态: " << std::hex << ptr << " → 悬空指针\n\n";return 0; }
输出结果:
分配器类型: SaISt4pairIKidEE 支持的最大内存: 1152921504606846975 bytes2. 分配内存前指针状态: 0 3. 分配内存后地址: 0xe31730 | 分配单元数: 14. 构造对象前内存内容: key=14883488, value=7.35022e-317 (随机值) 5. 构造对象后验证: key=101, value=3.146. 销毁对象前访问测试: 3.14 (正常访问) 7. 对象已销毁 | 内存地址保留: 0xe31730 8. 尝试访问已销毁对象: 3.14 9. 内存已释放 | 原地址状态: 0xe31730 → 悬空指针
3. 详细说明
-
key_comp()
本质- 返回的实际上是
Compare
类型的函数对象 - 可通过该比较器判断两个键的排序关系
if (m.key_comp()(k1, k2)) { // k1 在 k2 之前 }
- 返回的实际上是
-
value_comp()
特性- 在
std::map
中相当于:
[](const auto& a, const auto& b) { return key_comp()(a.first, b.first); }
- 在
-
分配器高级应用
操作 标准方法 推荐方式 内存分配 alloc.allocate(n)
allocator_traits::allocate
对象构造 alloc.construct(ptr, args...)
allocator_traits::construct
内存释放 alloc.deallocate(ptr, n)
allocator_traits::deallocate
在需要跨容器共享内存池或实现特殊内存管理的场景中,优先通过 get_allocator()
传递分配器,而非直接使用 new
/delete
7. 非成员函数
1. 函数说明
函数签名 | 功能说明 |
---|---|
operator==(const map&, const map&) | 内容相等性比较 |
operator!=(const map&, const map&) | 不等性比较 |
std::swap(map& a, map& b) | 特化交换函数 |
2. 用法示例
-
operator==
与operator!=
示例#include <map> #include <string>int main() {std::map<int, std::string> m1{{1, "apple"}, {2, "banana"}};std::map<int, std::string> m2{{1, "apple"}, {2, "banana"}};std::map<int, std::string> m3{{3, "cherry"}};// 比较内容是否完全一致(键值对和顺序)bool eq1 = (m1 == m2); // true(内容完全一致)bool eq2 = (m1 == m3); // false(内容不同)// 不等性比较(等价于 !(m1 == m3))bool neq = (m1 != m3); // true }
-
std::swap
特化示例#include <map> #include <iostream>int main() {std::map<int, char> a{{1, 'A'}, {2, 'B'}};std::map<int, char> b{{3, 'C'}};// 交换两个 map 的内容(高效交换底层数据结构)std::swap(a, b);// 验证交换结果 std::cout << "a size: " << a.size() << ", b size: " << b.size();// 输出: a size: 1, b size: 2 }
3. 详细说明
-
operator==
严格比较键值对内容及顺序(map
内部按照键排序,顺序由键的比较逻辑决定)。 -
std::swap
特化
时间复杂度为 O(1),直接交换底层数据指针,比a.swap(b)
(成员函数版本)更通用,支持非左值操作。 -
C++11 及更新标准
operator!=
已由编译器自动生成,无需手动实现,直接使用即可。
三、map 和 multimap 功能差异对比表
1. 函数说明
函数/行为 | std::map | std::multimap |
---|---|---|
键唯一性 | 键必须唯一,重复插入失败 | 允许重复键 |
insert(const value_type&) | 键存在时返回 pair<iter, false> | 总是插入成功,返回新元素迭代器 |
count(const key_type&) | 返回值始终为 0 或 1 | 返回键的实际重复数量 |
erase(const key_type&) | 删除 0 或 1 个元素 | 删除所有匹配键的元素 |
equal_range() 返回值范围 | 范围长度 ≤ 1 | 范围长度可能 > 1 |
2. 用法示例
-
insert(const value_type&)
std::map<int, std::string> m; // 插入新键(成功) auto [it1, success1] = m.insert({1, "Apple"}); // it1指向新元素,success1=true std::cout <<it1->first<<" : " <<it1->second <<' '<< std::boolalpha<<success1<<std::endl; //1 : Apple true // 插入重复键(失败) auto [it2, success2] = m.insert({1, "Banana"}); // it2指向已存在元素,success2=false std::cout <<it2->first<<" : " <<it2->second <<' '<< std::boolalpha<<success2<<std::endl; //1 : Apple false
特点:
- 键不存在时插入新元素,返回
<新元素迭代器, true>
- 键存在时返回
<已有元素迭代器, false>
,不覆盖原值
- 键不存在时插入新元素,返回
-
count(const key_type&)
std::map<char, int> m{{'A', 1}, {'B', 2}}; size_t c1 = m.count('A'); // 返回1(键存在) size_t c2 = m.count('X'); // 返回0(键不存在)
特点:
- 返回值只能是0
或1
(因map
键唯一)
- 比find()
更简洁的键存在性检查 -
erase(const key_type&)
std::map<std::string, int> m{{"Cat", 3}, {"Dog", 4}}; size_t n1 = m.erase("Cat"); // 删除成功,n1=1 size_t n2 = m.erase("Bird"); // 键不存在,n2=0
特点:
- 删除成功返回1
,失败返回0
- 仅删除一个元素(因键唯一) -
equal_range()
std::map<int, char> m{{10,'a'}, {20,'b'}, {30,'c'}}; auto [first, last] = m.equal_range(20);// 遍历结果范围(最多包含一个元素) for (auto it = first; it != last; ++it) {std::cout << it->first << ": " << it->second; // 输出"20: b" }
特点:
- 返回匹配键的迭代器范围[first, last)
- 范围长度 ≤1(因键唯一),若键不存在则first == last
3. 详细说明
关键总结
函数 | 行为特点 | 返回值说明 |
---|---|---|
insert() | 不覆盖已有键值 | <迭代器, 插入是否成功> |
count() | 快速键存在性检查 | 0 (不存在)或 1 (存在) |
erase() | 精确删除单个元素 | 实际删除的元素数量(0或1) |
equal_range() | 返回单元素范围 | 兼容关联容器API的统一访问方式 |
📌 注意:所有函数均基于
std::map
的键唯一特性设计,与std::multimap
的行为有本质区别。
四、map 独有函数
1. 函数说明
函数签名 | 功能说明 |
---|---|
operator[](const key_type&) | 键存在返回值引用;不存在时插入新元素 |
operator[](key_type&&) | 右值键版本 (C++11) |
at(const key_type&) | 键存在返回常量引用;不存在抛异常 |
at(const key_type&) const | const 重载版本 |
try_emplace(const key_type&, Args&&...) | 键不存在时原位构造 (C++17) |
insert_or_assign(const key_type&, M&&) | 键存在时赋值,不存在时插入 (C++17) |
2. 用法示例
-
operator[](const key_type&)
std::map<std::string, int> scores; scores["Alice"] = 92; // 插入新键值对 scores["Bob"] = 85; // 插入新键值对 int aliceScore = scores["Alice"]; // 获取值(键存在) int eveScore = scores["Eve"]; // 自动插入{Eve, 0}并返回值 std::cout <<aliceScore<< std::endl;//92 std::cout <<eveScore<< std::endl; //0
特点:
- 键存在 → 返回值的可变引用
- 键不存在 → 自动插入
key_type()
初始化的值(如int()
为0
) - ⚠️ 危险:可能意外插入(不需要的)元素(用
if (map.find(key) != ...)
防御)
-
operator[](key_type&&)
(C++11)std::map<std::string, int> m; std::string key = "temp"; auto& val = m[std::move(key)] = 10; // 移动语义避免拷贝std::cout << val << "\n"; //10 std::cout << m["temp"] << "\n"; //10 std::cout << key << "\n"; // 被移走了为空 // key 状态未定义(可能为空)
特点:
- 右值键版本,避免临时对象拷贝(提高性能)
- 行为与左值版本一致,但转移键的所有权
-
at(const key_type&)
std::map<int, std::string> idMap{{101, "Alice"}, {102, "Bob"}}; std::string name1 = idMap.at(101); // 成功返回"Alice" try {std::string name2 = idMap.at(103); // 抛 std::out_of_range } catch (const std::exception& e) {std::cerr << "Error: " << e.what(); }
特点:
- 键存在 → 返回常量引用
- 键不存在 → 抛出
std::out_of_range
异常 - 安全替代方案:优先于
operator[]
用于只读访问
-
at(const key_type&) const
const std::map<int, char> vowels{{97, 'a'}, {101, 'e'}}; char a = vowels.at(97); // 返回 'a'(const引用) // vowels.at(105); // 抛异常(i不存在)
特点:
const
容器专属版本- 返回只读引用(禁止修改值)
- 同样在键不存在时抛出异常
-
try_emplace()
(C++17)std::map<int, std::string> users; // 原位构造避免临时对象 auto [it1, inserted1] = users.try_emplace(1, "Alice"); // 插入成功 auto [it2, inserted2] = users.try_emplace(1, "Ali"); // 失败,值不变// 构造参数可拆分 auto [it3, inserted3] = users.try_emplace(2, 5, 'X'); // 值为"XXXXX"
特点✨:
- 键不存在 → 原位构造值(避免拷贝/移动)
- 键存在 → 忽略操作(不修改值)
- 高效替代:优先于
insert
用于复杂对象
-
insert_or_assign()
(C++17)std::map<std::string, int> config; // 插入新配置 auto [it1, action1] = config.insert_or_assign("Timeout", 30); // 插入// 更新现有配置 auto [it2, action2] = config.insert_or_assign("Timeout", 60); // 赋值// 输出:action1=true(插入),action2=false(赋值)
特点✨:
- 键不存在 → 插入新元素
- 键存在 → 赋值新值(覆盖旧值)
- 返回值:
pair<迭代器, bool>
(bool=true
表示插入,false
表示赋值)
3. 详细说明
✅ 核心区别总结
场景 | operator[] | at() | try_emplace() | insert_or_assign() |
---|---|---|---|---|
键不存在时 | 插入默认值 | 抛异常 | 原位构造新值 | 插入新值 |
键存在时 | 返回值引用 | 返回值 | 忽略操作 | 赋值覆盖旧值 |
是否可能修改容器 | ✅ | ❌ | 仅插入时 | ✅ |
异常安全 | 弱 | 强 | 强 | 中等 |
✅最佳推荐📌
- 需要安全性 → 用
at()
或先find()
- 避免意外插入 → 禁用
operator[]
,改用try_emplace
/insert_or_assign
- 高性能场景 → 用右值版本或原位构造函数
五、map 和 multimap 关键差异说明
- 插入行为
map
插入重复键时直接失败(返回false
)multimap
无条件插入新元素,即使键重复
- 元素访问
map
支持operator[]
和at()
的直接键访问multimap
必须通过迭代器或equal_range()
访问元素
- 删除操作
map::erase(key)
最多删除 1 个元素multimap::erase(key)
删除所有匹配键的元素(返回删除数量)
- 核心语义差异
map
是键值映射容器multimap
是键值对集合容器
六、完整用法示例
-
核心初始化方法
#include <map> #include <string> #include <vector>int main() {// 1. 默认初始化 (空map)std::map<std::string, int> emptyMap; // {}// 2. 初始化列表 (最常用)std::map<std::string, int> countryCodes{{"China", 86}, {"Japan", 81},{"USA", 1} // {"China":86, "Japan":81, "USA":1}};// 3. 范围初始化 (从其他容器)std::vector<std::pair<std::string, int>> vec = {{"A", 1}, {"B", 2}};std::map<std::string, int> fromRange(vec.begin(), vec.end()); // {"A":1, "B":2}// 4. 复制初始化std::map<std::string, int> copyMap(countryCodes); // 深拷贝// 5. 移动初始化 (高效转移)std::map<std::string, int> movedMap(std::move(countryCodes)); // 原map变为空 // 6. 自定义比较器初始化 auto cmp = [](const std::string& a, const std::string& b) {return a.length() < b.length(); // 按字符串长度排序 };std::map<std::string, int, decltype(cmp)> customMap(cmp);customMap.insert({{"apple", 5}, {"kiwi", 4}}); // 存储顺序: kiwi:4 apple:5// 7. 插入式初始化std::map<int, std::string> idName;idName.insert({101, "Alice"}); // {101:"Alice"}idName.emplace(102, "Bob"); // {101:"Alice", 102:"Bob"}// 8. 下标初始化 std::map<std::string, double> exchangeRate;exchangeRate["USD"] = 1.0; // {"USD":1.0}exchangeRate["EUR"] = 0.93; // {"USD":1.0, "EUR":0.93}return 0; }
-
用法示例
映射 模拟了一个简单的投票系统
#include <iostream> #include <map> #include <string> using namespace std;// 候选人类:存储候选人信息及得票数 class Candidate { public:// 构造函数:初始化候选人姓名和得票数(默认为0)Candidate (const string& name = "") :m_name (name), m_votes (0) {}// 获取候选人姓名(const成员函数,保证不修改对象状态) const string& getname (void) const {return m_name;}// 获取候选人得票数size_t getvotes (void) const {return m_votes;}// 投票操作:增加候选人得票数 void vote (void) {m_votes++;}private:string m_name;size_t m_votes;};// 自定义比较器类:实现字符的降序排序 class Comparator { public:// 重载函数调用运算符:比较两个字符bool operator() (char a, char b) const {return a > b;} };int main (void) {// 创建map容器:键类型char,值类型Candidate,使用自定义降序比较器map<char, Candidate, Comparator> mc;mc['D'] = Candidate ("刘备");mc['C'] = Candidate ("关羽");mc.insert (pair<char, Candidate> ('A',Candidate ("张飞")) );mc.insert (make_pair ('B', Candidate ("赵云")) );mc['E'] = Candidate ("曹操");// 使用类型别名简化迭代器声明 using IT = map<char,Candidate>::iterator ; // 非常量迭代器 using CIT= map<char,Candidate>::const_iterator ;// 常量迭代器// 进行10轮投票for (size_t i = 0; i < 10; i++) {// 显示投票选项(按键的降序排列:E→D→C→B→A)for (CIT it = mc.begin (); it != mc.end ();it++)// 输出选项格式:(键) 候选人姓名 cout << '(' << it -> first << ") " <<it -> second.getname () << ' ';cout << endl << "请投票(大写):" << flush;// flush确保立即显示 char cKey;cin >> cKey;// 接收用户投票输入// 在map中查找输入的键IT it = mc.find (cKey);if (it == mc.end ()) {cout << "废票!" << endl;continue;// 跳过后续操作,进入下一轮投票}it -> second.vote (); // 为找到的候选人投票}// 统计投票结果:找出得票最高的候选人CIT win = mc.begin (); // 初始化获胜者为第一个候选 for (CIT it = mc.begin (); it != mc.end ();it++) {// 输出每位候选人的得票情况cout << it -> second.getname () << "得"<< it -> second.getvotes () << "票。" << endl;// 更新获胜者(得票更高者)if (it -> second.getvotes () > win -> second.getvotes ())win = it;}// 宣布当选结果cout << win -> second.getname () << "当选!"<< endl;// 演示operator[]访问:输出键'C'对应的候选人cout << mc['C'].getname () << endl;return 0; }
依次输入:
EDCBAAAAA
输出结果:
曹操得1票。
刘备得1票。
关羽得1票。
赵云得1票。
张飞得5票。
张飞当选!
关羽自定义排序
Comparator
重载operator()
实现字符降序排序(E→D→C→B→A)
- 面向对象设计:
Candidate
封装票数操作,隔离数据细节
-
用法示例
多重映射 模拟了一个简单的工资结算清单
#include <iostream> #include <map>int main (void) {// 创建允许重复键的multimap容器,存储<姓名, 数值>对std::multimap<std::string, int> mm;// 插入数据 - multimap允许重复键值 mm.insert (std::make_pair ("张飞", 1000));mm.insert (std::make_pair ("赵云", 1000));mm.insert (std::make_pair ("张飞", 2000));// 重复键"张飞"mm.insert (std::make_pair ("关羽", 2000));mm.insert (std::make_pair ("赵云", 3000));// 重复键mm.insert (std::make_pair ("关羽", 3000));// 重复键// 使用类型别名简化迭代器声明using CIT = std::multimap<std::string, int>::const_iterator;// 第一部分:按排序顺序输出所有数据(按键的字典序排序)for (CIT it = mm.begin (); it != mm.end ();it++)std::cout << it -> first << ":" <<it -> second << std::endl;std::cout << "------------" << std::endl;// 第二部分:计算并输出每个键(姓名)对应的数值总和for (CIT it = mm.begin (); it != mm.end ();it++) {// 关键步骤:找到当前键值范围的结束位置// upper_bound返回第一个大于key的元素位置std::string key = it -> first;CIT end = mm.upper_bound (key);int sum = 0;// 遍历当前键的所有值(从当前位置到end)for (; it != end; it++)sum += it -> second;// 输出当前键的数值总和std::cout << key << ":" << sum << std::endl;// 重要修正:将迭代器回退一位 // 因为内层循环结束时it指向end位置(下一个键的起始)it--;}return 0; }结果: 关羽:2000 关羽:3000 张飞:1000 张飞:2000 赵云:1000 赵云:3000 ------------ 关羽:5000 张飞:3000 赵云:4000