C++:map容器
前言:在 C++ STL 中,map
是一种关联容器,用于存储键值对(key-value) 数据,并且会根据键(key)自动排序。它的核心特点是键唯一且按键有序,非常适合需要通过键快速查找、插入和删除值的场景。
1. map 的核心特性
- 键值对存储:每个元素是一个
pair<const Key, Value>
结构,包含一个键(key)和一个值(value)。- 键的唯一性:同一个键只能出现一次,插入重复键会被忽略(或覆盖,取决于用法)。
- 自动排序:元素会按照键的大小自动排序(默认升序),排序规则可自定义。
- 底层实现:通常基于红黑树(自平衡二叉搜索树),因此插入、删除、查找操作的时间复杂度均为 O(log n)。
- 迭代器支持:提供双向迭代器,可遍历元素,但不能随机访问(如
it + 2
不允许)。
2. 头文件与定义
使用
map
需包含头文件<map>
,并使用std
命名空间:#include <map> using namespace std;// 定义一个默认map(键为int,值为string,按key升序) map<int, string> m;// 定义一个按key降序排列的map map<int, string, greater<int>> m_desc;
map
的模板参数说明:
- 第一个参数:键(key)的类型(如
int
)- 第二个参数:值(value)的类型(如
string
)- 第三个参数(可选):比较器类型(默认
less<Key>
,即升序)
3. 常用操作
(1)插入元素
有多种插入方式,最常用的是
insert()
和直接赋值:map<int, string> m;// 方式1:insert()插入pair对象 m.insert(pair<int, string>(1, "apple"));// 方式2:insert()插入make_pair(更简洁) m.insert(make_pair(2, "banana"));// 方式3:直接赋值(若key已存在,会覆盖原有value) m[3] = "pear";// 方式4:C++11及以上,插入初始化列表 m.insert({{4, "orange"}, {5, "grape"}});// 插入重复key(会被忽略) auto result = m.insert(make_pair(1, "cherry")); if (!result.second) {cout << "键1已存在,值为:" << result.first->second << endl; }
insert()
返回值是pair<iterator, bool>
:
- 第一个元素:指向插入位置的迭代器
- 第二个元素:
true
表示插入成功,false
表示 key 已存在(2)访问元素
- 通过
[key]
访问:若 key 不存在,会自动插入一个默认值(如空字符串、0 等)。- 通过
at(key)
访问:若 key 不存在,会抛出out_of_range
异常(更安全)。- 通过迭代器访问:遍历或定位元素时使用。
map<int, string> m = {{1, "apple"}, {2, "banana"}, {3, "pear"}};// 方式1:[key]访问(可能自动插入新元素) cout << m[2] << endl; // 输出:banana cout << m[4] << endl; // 键4不存在,插入默认值(空字符串)// 方式2:at(key)访问(安全检查) try {cout << m.at(3) << endl; // 输出:pearcout << m.at(5) << endl; // 抛出异常 } catch (const out_of_range& e) {cout << "访问失败:" << e.what() << endl; }// 方式3:迭代器访问 auto it = m.find(1); if (it != m.end()) {cout << "键" << it->first << "的值是:" << it->second << endl; }
(3)删除元素
erase(key)
:删除键为key
的元素,返回删除的元素个数(0 或 1)。erase(pos)
:删除迭代器pos
指向的元素,无返回值。erase(begin, end)
:删除迭代器范围[begin, end)
内的元素,无返回值。clear()
:清空所有元素。map<int, string> m = {{1, "apple"}, {2, "banana"}, {3, "pear"}};// 删除键为2的元素 m.erase(2); // 删除第一个元素 auto it = m.begin(); m.erase(it); // 清空map m.clear();
(4)查找元素
find(key)
:查找键为key
的元素,返回指向该元素的迭代器;若不存在,返回end()
。count(key)
:返回键为key
的元素个数(由于键唯一,结果只能是 0 或 1)。lower_bound(key)
:返回第一个键大于等于key
的元素迭代器。upper_bound(key)
:返回第一个键大于key
的元素迭代器。map<int, string> m = {{1, "apple"}, {3, "banana"}, {5, "pear"}};// 查找键3 auto it = m.find(3); if (it != m.end()) {cout << "找到:" << it->first << " → " << it->second << endl; }// 检查键2是否存在 if (m.count(2) == 0) {cout << "键2不存在" << endl; }// 查找第一个键>=4的元素(指向5) auto it_low = m.lower_bound(4);// 查找第一个键>3的元素(指向5) auto it_high = m.upper_bound(3);
(5)遍历元素
通过迭代器遍历,由于元素按键排序,遍历结果会按键的顺序输出:
map<int, string> m = {{3, "pear"}, {1, "apple"}, {2, "banana"}};// 正向遍历(按key升序) for (auto it = m.begin(); it != m.end(); ++it) {// first是键,second是值cout << it->first << " → " << it->second << endl; } // 输出:1→apple 2→banana 3→pear// C++11及以上:范围for循环 for (const auto& pair : m) {cout << pair.first << " → " << pair.second << endl; }// 反向遍历(按key降序) for (auto it = m.rbegin(); it != m.rend(); ++it) {cout << it->first << " → " << it->second << endl; } // 输出:3→pear 2→banana 1→apple
(6)其他常用方法
size()
:返回键值对的数量。empty()
:判断 map 是否为空(空则返回true
)。swap(other_map)
:与另一个 map 交换内容。map<int, string> m = {{1, "a"}, {2, "b"}}; cout << "元素数量:" << m.size() << endl; // 输出:2if (!m.empty()) {cout << "map不为空" << endl; }map<int, string> m2; m.swap(m2); // m变空,m2包含原m的元素
4. 自定义键类型与排序规则
当
map
的键是自定义类型时,需要指定排序规则(通过比较器)。示例:用
map
存储学生信息,按学号降序排列#include <map> #include <string> #include <iostream> using namespace std;// 自定义键类型:学生学号 class StudentID { public:int grade; // 年级int num; // 学号StudentID(int g, int n) : grade(g), num(n) {} };// 自定义比较器:按年级降序,年级相同则按学号降序 class CompareID { public:bool operator()(const StudentID& a, const StudentID& b) const {if (a.grade != b.grade) {return a.grade > b.grade; // 年级高的在前}return a.num > b.num; // 年级相同,学号大的在前} };int main() {// 键为StudentID,值为学生姓名,比较器为CompareIDmap<StudentID, string, CompareID> students;students.insert({ StudentID(3, 101), "Alice" });students.insert({ StudentID(2, 202), "Bob" });students.insert({ StudentID(3, 103), "Charlie" });// 遍历输出(按年级降序:3年级 → 2年级)for (const auto& pair : students) {cout << "年级:" << pair.first.grade<< ",学号:" << pair.first.num<< ",姓名:" << pair.second << endl;}return 0; }
输出结果:
年级:3,学号:103,姓名:Charlie 年级:3,学号:101,姓名:Alice 年级:2,学号:202,姓名:Bob
5. 与 unordered_map 的区别
特性 map
unordered_map
底层实现 红黑树 哈希表 排序 按键自动排序 无序 查找效率 O(log n) 平均 O (1),最坏 O (n) 内存占用 较低(红黑树结构紧凑) 较高(哈希表需预留空间) 适用场景 需要有序遍历或范围查询 追求极致查找效率
6. 注意事项
map
的键是常量(const Key
),不能直接修改(会破坏排序),若需修改键,需先删除旧键值对,再插入新的。- 使用
[key]
访问时,若键不存在会自动插入默认值,可能导致意外行为(如需避免,可用find()
或at()
)。- 迭代器在插入和删除操作后,只有被删除元素的迭代器失效,其他迭代器仍然有效。
总结:map
是 STL 中处理键值对数据的重要容器,其有序性和高效的查找能力使其在需要通过键快速定位值的场景中(如字典、索引表等)非常实用。掌握map
的用法,能帮助你更高效地处理关联数据。