C++笔记——STL map
map 是 C++ 标准模板库(STL)中一个非常重要的关联式容器,它提供了一种“键-值”对的存储和访问机制。
1. 核心概念与特性
-
键值对:
map存储的元素都是std::pair<const Key, T>类型的对象。简单理解,就是一个唯一的Key(关键字)映射到一个Value(值)。 -
有序性:
map中的元素会根据 键(Key) 自动进行排序。默认情况下,它使用std::less<Key>(即<操作符)对键进行升序排序。你也可以通过自定义比较函数来指定排序规则。 -
唯一性:
map容器保证所有元素的键都是唯一的。如果你尝试插入一个已经存在的键,插入操作会失败。 -
底层实现:
map通常使用红黑树 实现。红黑树是一种自平衡的二叉搜索树,这使得map在插入、删除和查找操作上的时间复杂度都能保持在 O(log n),效率非常稳定。 -
不可修改的键:一旦一个元素被插入到
map中,它的键就是常量,不能被修改。这是为了不破坏红黑树的结构。但你可以修改该键对应的值。
2. 基本用法
头文件与声明
cpp
#include <iostream>
#include <map> // 必须包含的头文件
#include <string>int main() {// 声明一个 map,键为 string,值为 intstd::map<std::string, int> studentScores;// 也可以声明时指定自定义比较规则(例如降序)// std::map<std::string, int, std::greater<std::string>> studentScoresDesc;return 0;
}
常用操作
插入元素
有几种常见的方法:
-
使用
insert成员函数:cpp
std::map<std::string, int> studentScores;// 方法1:使用 make_pair studentScores.insert(std::make_pair("Alice", 95)); // 方法2:使用花括号 {} (C++11) studentScores.insert({"Bob", 88}); // 方法3:使用 pair 的构造函数 studentScores.insert(std::pair<const std::string, int>("Charlie", 70));// insert 会返回一个 pair<iterator, bool> auto ret = studentScores.insert({"Alice", 100}); // 键 "Alice" 已存在,插入失败 if (!ret.second) {std::cout << "Insertion failed. Key 'Alice' already exists.\n"; } -
使用
operator[](最常用、最直观):cpp
std::map<std::string, int> studentScores;studentScores["Alice"] = 95; // 如果 "Alice" 不存在,会先创建一个并赋值 studentScores["Bob"] = 88; studentScores["Alice"] = 100; // 因为 "Alice" 已存在,这会修改其对应的值std::cout << studentScores["Alice"]; // 输出 100
注意:
operator[]如果找不到键,会自动插入一个具有该键的新元素,并用值类型的默认构造函数初始化其值(对于int是 0)。这有时会导致非预期的插入。
访问元素
-
使用
operator[]:cpp
int score = studentScores["Alice"]; // 如果 "Alice" 不存在,会插入一个新元素!
-
使用
at成员函数 (C++11):cpp
int score = studentScores.at("Alice"); // 如果 "Alice" 不存在,会抛出 std::out_of_range 异常推荐在确定键存在时使用
at,因为它更安全,不会意外插入新元素。 -
使用迭代器:
cpp
auto it = studentScores.find("Alice"); if (it != studentScores.end()) {// it->first 是键 ("Alice")// it->second 是值 (100)std::cout << it->first << ": " << it->second << std::endl; } else {std::cout << "Key not found.\n"; }
查找元素
使用 find 成员函数,它返回一个迭代器。
cpp
std::map<std::string, int>::iterator it = studentScores.find("Bob");
// 或者用 auto
auto it = studentScores.find("Bob");if (it != studentScores.end()) {std::cout << "Found: " << it->first << " -> " << it->second << std::endl;
} else {std::cout << "Key 'Bob' not found." << std::endl;
}
注意:不要使用 std::find 算法来查找 map 的元素,因为它是线性搜索,效率远低于 map 自己的 find 方法(O(n) vs O(log n))。
删除元素
使用 erase 成员函数。
cpp
std::map<std::string, int> studentScores = {{"Alice", 95}, {"Bob", 88}, {"Charlie", 70}};// 方法1:通过迭代器删除
auto it = studentScores.find("Bob");
if (it != studentScores.end()) {studentScores.erase(it);
}// 方法2:通过键删除
size_t numRemoved = studentScores.erase("Charlie"); // 返回被删除元素的个数(对于map是0或1)// 方法3:删除一个范围
studentScores.erase(studentScores.begin(), studentScores.find("Alice")); // 删除 "Alice" 之前的所有元素
遍历元素
由于 map 是有序的,遍历时会按照键的顺序进行。
cpp
std::map<std::string, int> studentScores = {{"Alice", 95}, {"Bob", 88}, {"Charlie", 70}};// 方法1:使用迭代器
for (std::map<std::string, int>::iterator it = studentScores.begin(); it != studentScores.end(); ++it) {std::cout << it->first << ": " << it->second << std::endl;
}// 方法2:使用基于范围的for循环 (C++11) - 推荐!
for (const auto& pair : studentScores) {std::cout << pair.first << ": " << pair.second << std::endl;
}// 输出:
// Alice: 95
// Bob: 88
// Charlie: 70
3. 复杂度分析
-
插入:
O(log n) -
查找:
O(log n) -
删除:
O(log n) -
遍历:
O(n)
4. 与其他容器的比较
| 特性 | std::map | std::unordered_map | std::multimap |
|---|---|---|---|
| 有序性 | 有序(按键排序) | 无序 | 有序(按键排序) |
| 底层结构 | 红黑树(平衡BST) | 哈希表 | 红黑树(平衡BST) |
| 查找复杂度 | O(log n) | 平均 O(1),最坏 O(n) | O(log n) |
| 键的唯一性 | 键唯一 | 键唯一 | 键可重复 |
| 需要哈希函数 | 否 | 是 | 否 |
| 需要比较函数 | 是 | 否 | 是 |
| 使用场景 | 需要元素有序 | 需要极速查找,不关心顺序 | 需要有序且键可重复 |
5. 高级用法与技巧
自定义比较函数
当键是自定义类型或需要特殊排序规则时使用。
cpp
struct Student {std::string name;int id;
};// 自定义比较函数对象
struct CompareByID {bool operator()(const Student& a, const Student& b) const {return a.id < b.id; // 按 ID 升序排列}
};int main() {std::map<Student, int, CompareByID> studentScores;studentScores[{"Alice", 2}] = 90;studentScores[{"Bob", 1}] = 85;studentScores[{"Charlie", 3}] = 95;// 遍历时,会按 ID 1(Bob), 2(Alice), 3(Charlie) 的顺序输出for (const auto& pair : studentScores) {std::cout << "ID: " << pair.first.id << ", Name: " << pair.first.name << ", Score: " << pair.second << std::endl;}return 0;
}
emplace 高效构造 (C++11)
emplace 可以直接在容器内部构造元素,避免了临时对象的创建和拷贝/移动。
cpp
std::map<std::string, int> studentScores;
studentScores.emplace("David", 92); // 直接在 map 中构造 pair("David", 92)
总结
std::map 是一个强大且灵活的有序关联容器,它通过键值对的形式存储数据,并保证键的唯一性和有序性。其基于红黑树的实现提供了稳定的对数时间复杂度操作。在选择使用 map 时,需要考虑:
-
你是否需要元素有序?
-
你的键是否是唯一的?
-
你的应用场景是否能接受 O(log n) 的查找时间?
如果不需要有序,且追求极致的查找速度,可以考虑 std::unordered_map。如果需要键可以重复,则考虑 std::multimap。
