自动排序:元素始终按照键的顺序存储
唯一键值:每个键在 map 中只能出现一次
高效操作:查找、插入、删除时间复杂度均为 O(log n)
双向迭代:支持正向和反向迭代器遍历
前言
1. map 容器概述
std::map
是 C++ 标准模板库(STL)中的一个关联容器,它以键值对(key-value)的形式存储元素,并且会根据键(key)自动进行排序。map 内部通常实现为红黑树(一种自平衡二叉查找树),这保证了元素操作的高效性。
2. 基础用法详解
2.1 头文件与基本声明
#include <iostream>
#include <map>
#include <string>
int main() {
// 声明一个键为string,值为int的map
std::map<std::string, int> studentScores;
// 声明并初始化map
std::map<std::string, int> productPrices = {
{"apple", 5},
{"banana", 3},
{"orange", 4}
};
return 0;
}
-
必须包含
<map>
头文件 模板参数:std::map<KeyType, ValueType>
-
可以使用初始化列表在声明时直接初始化
2.2 元素插入操作
// 继续上面的代码
// 方法1:使用insert成员函数
studentScores.insert(std::make_pair("Alice", 90));
studentScores.insert({"Bob", 85}); // C++11风格
// 方法2:使用operator[]
studentScores["Charlie"] = 88; // 如果键不存在会自动创建
studentScores["David"]; // 会创建一个值为0的元素(int的默认值)
// 方法3:使用emplace(C++11)
studentScores.emplace("Eve", 92); // 直接在map中构造元素,避免临时对象
emplace是原位构造比insert要省空间
-
insert
方法不会覆盖已存在的键值 -
operator[]
如果键不存在会创建新元素(值类型默认构造)
2.3 元素访问与查找
// 访问元素
std::cout << "Alice's score: " << studentScores["Alice"] << std::endl;
// 安全访问(不自动插入)
if (studentScores.find("Frank") != studentScores.end()) {
std::cout << "Frank's score: " << studentScores["Frank"] << std::endl;
} else {
std::cout << "Frank not found" << std::endl;
}
// 使用at方法(会检查边界)
try {
std::cout << "Bob's score: " << studentScores.at("Bob") << std::endl;
} catch (const std::out_of_range& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
// 使用count检查存在性(返回0或1)
if (studentScores.count("Eve")) {
std::cout << "Eve exists in the map" << std::endl;
}
-
如果"Alice"存在,返回对应的值 如果"Alice"不存在,会自动插入一个默认构造的值(对于int是0)注意:这种访问方式会修改map(当key不存在时)
-
find
方法不会自动插入不存在的key 如果找到,返回指向该元素的迭代器 如果没找到,返回end()
迭代器 这是一种安全的检查方式,不会意外修改map -
如果"Bob"存在,返回对应的值 如果"Bob"不存在,抛出
std::out_of_range
异常 这是一种更安全的访问方式,适合需要异常处理的场景 -
count
对于map返回0或1(因为key唯一) 不会修改map 比find
更简洁,但如果你需要访问值,find
更合适(因为它返回迭代器)
3. 元素删除与修改
3.1 删除操作
// 通过键删除
size_t erased = studentScores.erase("David"); // 返回删除的元素数量(0或1)
// 通过迭代器删除
auto it = studentScores.find("Charlie");
if (it != studentScores.end()) {
studentScores.erase(it); // 更高效,因为不需要再次查找
}
// 删除一定范围内的元素
auto begin = studentScores.begin();
auto end = studentScores.find("Eve"); // 不包括Eve
studentScores.erase(begin, end);
// 清空整个map
studentScores.clear();
-
如果"David"存在,删除该键值对并返回1 时间复杂度:O(log n)(因为需要查找)注意:这种方式需要先查找key,再删除
-
先通过
find
获取迭代器 如果找到(it != end()),使用迭代器删除 删除后迭代器会失效
-
删除从begin到end(不包括end)之间的所有元素
3.2 修改元素值
// 修改现有元素的值
studentScores["Alice"] = 95; // 直接通过键修改
// 通过迭代器修改
auto it = studentScores.find("Bob");
if (it != studentScores.end()) {
it->second = 90; // 可以修改value
// it->first = "Bobby"; // 错误!不能修改key
}
// 插入或修改(C++17)
studentScores.insert_or_assign("Eve", 93); // 如果存在则修改,不存在则插入
4. 遍历与迭代
4.1 多种遍历方式
// 1. 使用迭代器(传统方式)
std::cout << "\nAll scores (iterator):" << std::endl;
for (auto it = studentScores.begin(); it != studentScores.end(); ++it) {
std::cout << it->first << ": " << it->second << std::endl;
}
// 2. 基于范围的for循环(C++11)
std::cout << "\nAll scores (range-based for):" << std::endl;
for (const auto& pair : studentScores) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
// 3. 结构化绑定(C++17)
std::cout << "\nAll scores (structured binding):" << std::endl;
for (const auto& [name, score] : studentScores) {
std::cout << name << ": " << score << std::endl;
}
// 4. 反向迭代
std::cout << "\nAll scores (reverse order):" << std::endl;
for (auto rit = studentScores.rbegin(); rit != studentScores.rend(); ++rit) {
std::cout << rit->first << ": " << rit->second << std::endl;
}
-
通过
it->first
访问键(key),it->second
访问值(value) -
pair
是一个std::pair<const Key, Value>
,包含first
(键)和second
(值 -
直接解构
std::pair
,[name, score]
分别绑定键和值
5. 容量与状态查询
// 检查是否为空
if (studentScores.empty()) {
std::cout << "The map is empty" << std::endl;
} else {
std::cout << "The map is not empty" << std::endl;
}
// 获取元素数量
std::cout << "Number of students: " << studentScores.size() << std::endl;
// 获取最大可能大小
std::cout << "Max possible size: " << studentScores.max_size() << std::endl;
6. 高级特性与应用
6.1 自定义排序规则
// 自定义比较函数:按字符串长度排序
struct LengthCompare {
bool operator()(const std::string& a, const std::string& b) const {
if (a.length() == b.length()) {
return a < b; // 长度相同则按字典序
}
return a.length() < b.length();
}
};
int main() {
std::map<std::string, int, LengthCompare> lenMap = {
{"apple", 1},
{"banana", 2},
{"cherry", 3},
{"kiwi", 4}
};
std::cout << "\nMap ordered by string length:" << std::endl;
for (const auto& [fruit, num] : lenMap) {
std::cout << fruit << ": " << num << std::endl;
}
// 输出顺序:kiwi, apple, banana, cherry
return 0;
}
-
比较函数必须定义严格的弱序 如果两个键"等价"(即比较函数返回false),map会视为相同键 自定义比较函数可以实现各种复杂排序需求
6.2 合并两个map(C++17)
std::map<std::string, int> map1 = {{"A", 1}, {"B", 2}};
std::map<std::string, int> map2 = {{"B", 20}, {"C", 3}};
// 合并map2到map1
map1.merge(map2);
// 此时:
// map1: {"A":1, "B":2, "C":3} (B的值保留map1的)
// map2: {"B":20} (冲突的键留在原map中)
merge
会尝试移动元素到目标map 对于冲突的键保留目标map的值 源map中未移动的元素会保留
7. 性能分析与优化
7.1 时间复杂度对比
操作 | 时间复杂度 | 说明 |
---|---|---|
插入 | O(log n) | 需要保持树平衡 |
删除 | O(log n) | 需要保持树平衡 |
查找 | O(log n) | 二分查找 |
遍历 | O(n) | 需要访问每个节点 |
[]访问 | O(log n) | 可能触发插入操作 |
8. 常见问题解答
map和unordered_map如何选择?
std::unordered_map
是 C++ 标准库中的一种关联容器,它提供基于哈希表的键值对存储。与 std::map
不同,unordered_map
中的元素不是排序的。
-
需要元素有序 → map 需要最高查找性能 → unordered_map
-
需要内存效率 → 通常unordered_map更好 需要稳定性能 → map(没有哈希冲突问题)
map的迭代器失效情况?
插入操作:不会使任何迭代器失效 删除操作:只会使被删除元素的迭代器失效
修改值:不会使迭代器失效(但不能修改键)
总结
std::map 是C++中功能强大的关联容器,通过红黑树实现保证了操作的稳定效率。本文详细介绍了map的各类操作,从基础插入访问到高级特性如自定义排序、节点操作等,并提供了大量示例代码和分析。正确使用map可以显著提高程序的效率和可读性,是每个C++开发者都应该掌握的容器之一。