一、map
基础概念
1. 什么是 map
?
map
是 C++ STL 中的关联容器,它提供基于键(key)的快速数据检索能力,具有以下特性:
- 键值对(key-value)存储
- 按键自动排序(默认升序)
- 键唯一(不允许重复)
- 基于红黑树实现(平衡二叉搜索树)
2. map
基本操作复杂度
操作 | 时间复杂度 | 说明 |
---|---|---|
插入 | O(log n) | 需要维持树平衡 |
删除 | O(log n) | 需要维持树平衡 |
查找 | O(log n) | 二分查找 |
遍历 | O(n) | 需要访问所有节点 |
修改值 | O(log n) | 需要先查找 |
二、map
的基本使用
1. 头文件与声明
#include <map>
#include <string>
// 基本声明
std::map<int, std::string> studentMap;
// 使用初始化列表
std::map<std::string, double> priceMap {
{"apple", 1.99},
{"banana", 0.99},
{"orange", 2.49}
};
2. 常用操作详解
(1) 插入元素
// 使用insert成员函数
auto ret = studentMap.insert({101, "Alice"});
// ret是pair<iterator, bool>,second表示是否插入成功
// 使用operator[] (如果键不存在会自动创建)
studentMap[102] = "Bob"; // 插入或修改
// 使用emplace (C++11更高效的方式)
studentMap.emplace(103, "Charlie");
(2) 访问元素
// 使用operator[] (键不存在时会创建)
std::string name = studentMap[101];
// 使用at (键不存在时抛出out_of_range异常)
try {
std::string name = studentMap.at(101);
} catch (const std::out_of_range& e) {
std::cerr << "Key not found: " << e.what() << std::endl;
}
// 使用find (安全访问方式)
auto it = studentMap.find(104);
if (it != studentMap.end()) {
std::cout << "Found: " << it->second << std::endl;
} else {
std::cout << "Key 104 not found" << std::endl;
}
(3) 删除元素
// 通过键删除
size_t numRemoved = studentMap.erase(101);
// 通过迭代器删除
auto it = studentMap.find(102);
if (it != studentMap.end()) {
studentMap.erase(it);
}
// 删除范围 (前闭后开区间)
auto first = studentMap.find(100);
auto last = studentMap.find(200);
if (first != studentMap.end() && last != studentMap.end()) {
studentMap.erase(first, last);
}
// 清空map
studentMap.clear();
(4) 遍历元素
// 使用迭代器
for (auto it = studentMap.begin(); it != studentMap.end(); ++it) {
std::cout << "ID: " << it->first << ", Name: " << it->second << std::endl;
}
// 使用范围for循环 (C++11)
for (const auto& pair : studentMap) {
std::cout << "ID: " << pair.first << ", Name: " << pair.second << std::endl;
}
// 使用结构化绑定 (C++17)
for (const auto& [id, name] : studentMap) {
std::cout << "ID: " << id << ", Name: " << name << std::endl;
}
三、map
的高级特性
1. 自定义比较函数
// 自定义键比较函数
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 tolower(c1) < tolower(c2);
});
}
};
std::map<std::string, int, CaseInsensitiveCompare> wordCountMap;
// 使用函数指针
bool compareStrings(const std::string& a, const std::string& b) {
return a.length() < b.length();
}
std::map<std::string, int, decltype(&compareStrings)> lengthMap(&compareStrings);
2. 特殊查找操作
std::map<int, std::string> m {
{10, "ten"},
{20, "twenty"},
{30, "thirty"},
{40, "forty"}
};
// lower_bound - 第一个不小于key的元素
auto lb = m.lower_bound(25); // 指向30
// upper_bound - 第一个大于key的元素
auto ub = m.upper_bound(25); // 指向30
// equal_range - 返回匹配的范围
auto range = m.equal_range(20);
// range.first指向20,range.second指向30
3. 性能优化技巧
// 1. 使用emplace_hint提高插入效率
auto hint = m.begin(); // 可以是任何合理的迭代器位置
m.emplace_hint(hint, 15, "fifteen");
// 2. 避免不必要的拷贝
std::map<int, std::string> bigMap;
// 不好:会创建临时string
bigMap[1] = "very long string...";
// 更好:使用try_emplace (C++17)
bigMap.try_emplace(1, "very long string...");
// 3. 节点操作 (C++17)
std::map<int, std::string> source, target;
auto node = source.extract(10); // 从source移除但不销毁
if (!node.empty()) {
target.insert(std::move(node)); // 转移到target
}
四、map
的常见易错点
1. operator[]
的陷阱
std::map<std::string, int> wordCount;
// 以下操作会插入键"unknown"并值初始化为0
int count = wordCount["unknown"];
// 正确做法:先检查是否存在
auto it = wordCount.find("unknown");
if (it != wordCount.end()) {
count = it->second;
}
2. 迭代器失效问题
std::map<int, int> m {{1, 10}, {2, 20}};
// 安全:删除当前迭代器指向的元素
for (auto it = m.begin(); it != m.end(); ) {
if (it->second == 10) {
it = m.erase(it); // C++11后erase返回下一个有效迭代器
} else {
++it;
}
}
// 危险:在遍历时插入元素可能导致树重新平衡,使迭代器失效
3. 自定义比较函数的严格要求
// 错误的比较函数:不满足严格弱序
struct BadCompare {
bool operator()(int a, int b) const {
return a <= b; // 错误!应该使用 <
}
};
// 使用这种比较函数会导致未定义行为
std::map<int, int, BadCompare> badMap;
五、map
与其他容器的比较
1. map
vs unordered_map
特性 | map | unordered_map |
---|---|---|
实现方式 | 红黑树 | 哈希表 |
元素顺序 | 按键排序 | 无序 |
查找复杂度 | O(log n) | 平均O(1),最差O(n) |
内存使用 | 通常较少 | 通常较多(有负载因子) |
适用场景 | 需要有序遍历 | 只需快速查找 |
2. map
vs multimap
特性 | map | multimap |
---|---|---|
键唯一性 | 键唯一 | 允许重复键 |
插入操作 | operator[]可用 | 无operator[] |
查找结果 | 返回单个元素 | 可能返回多个元素 |
六、实际应用案例
1. 单词统计
std::map<std::string, int> wordCount;
std::string word;
while (std::cin >> word) {
// 使用operator[]会自动初始化新键为0
++wordCount[word];
}
// 输出统计结果(已按字典序排序)
for (const auto& [word, count] : wordCount) {
std::cout << word << ": " << count << std::endl;
}
2. 学生成绩管理系统
class StudentGradeSystem {
private:
std::map<int, std::map<std::string, double>> studentGrades;
// 外层key: 学生ID
// 内层map: 课程名 -> 成绩
public:
void addGrade(int studentId, const std::string& course, double grade) {
studentGrades[studentId][course] = grade;
}
double getGrade(int studentId, const std::string& course) const {
auto studentIt = studentGrades.find(studentId);
if (studentIt == studentGrades.end()) {
throw std::runtime_error("Student not found");
}
auto courseIt = studentIt->second.find(course);
if (courseIt == studentIt->second.end()) {
throw std::runtime_error("Course not found for student");
}
return courseIt->second;
}
void printAllGrades() const {
for (const auto& [id, courses] : studentGrades) {
std::cout << "Student ID: " << id << "\n";
for (const auto& [course, grade] : courses) {
std::cout << " " << course << ": " << grade << "\n";
}
}
}
};
3. 区间映射(处理重叠区间)
class IntervalMap {
private:
std::map<int, char> intervals; // key: 区间起点, value: 区间值
public:
void addInterval(int start, int end, char value) {
if (start >= end) return;
// 删除或截断重叠区间
auto it = intervals.upper_bound(start);
if (it != intervals.begin()) {
--it;
if (it->second == value) {
start = std::min(start, it->first);
} else if (it->first + 1 > start) {
// 截断现有区间
intervals[start] = it->second;
intervals[it->first] = it->second;
}
++it;
}
// 处理结束部分
while (it != intervals.end() && it->first < end) {
if (it->second == value) {
end = std::max(end, it->first + 1);
} else {
if (it->first + 1 > end) {
intervals[end] = it->second;
}
}
it = intervals.erase(it);
}
intervals[start] = value;
}
char getValue(int point) const {
auto it = intervals.upper_bound(point);
if (it == intervals.begin()) {
return '\0'; // 默认值
}
--it;
return it->second;
}
};
七、C++17/20 新特性
1. try_emplace
和 insert_or_assign
std::map<int, std::string> m;
// try_emplace: 键不存在时才构造值
m.try_emplace(1, "one"); // 构造"one"
m.try_emplace(1, "uno"); // 无效果
// insert_or_assign: 插入或更新
m.insert_or_assign(1, "uno"); // 更新为"uno"
2. 节点操作
std::map<int, std::string> source, target;
// 从source提取节点(不分配/释放内存)
auto node = source.extract(1);
if (!node.empty()) {
// 修改键
node.key() = 2;
// 插入到target
target.insert(std::move(node));
}
3. contains
方法 (C++20)
std::map<int, std::string> m {{1, "one"}};
if (m.contains(1)) {
std::cout << "Key 1 exists\n";
}
八、最佳实践总结
-
选择正确的容器:
- 需要有序遍历 →
map
- 只需快速查找 →
unordered_map
- 允许重复键 →
multimap
- 需要有序遍历 →
-
安全访问:
- 优先使用
find
+ 检查end()
- 慎用
operator[]
(可能意外插入) - 使用
at()
进行带边界检查的访问
- 优先使用
-
性能考虑:
- 批量插入时使用
emplace_hint
- C++17+ 使用节点操作避免拷贝
- 自定义比较函数要满足严格弱序
- 批量插入时使用
-
现代C++特性:
- 使用
try_emplace
避免不必要的临时对象 - 使用结构化绑定简化遍历代码
- C++20 使用
contains
替代find
+end
检查
- 使用
-
线程安全:
map
本身不是线程安全的- 多线程环境需要外部同步
- 考虑使用
shared_mutex
实现读写锁
map
是 C++ 中最有用的容器之一,理解其内部实现和特性可以帮助你编写出更高效、更安全的代码。根据具体需求选择合适的变体(如 unordered_map
或 multimap
),并充分利用现代 C++ 提供的各种优化手段。