在C++标准库中,std::map是一个基于红黑树实现的有序关联容器,它提供了一种键值对的存储方式。每个元素都是一个pair对象,包含一个唯一的key和对应的value。map会根据key自动进行排序,默认使用std::less进行升序排列。
与unordered_map不同,map保证了元素的顺序性,这使得它在需要有序遍历的场景下特别有用。map的查找、插入和删除操作的时间复杂度都是O(log n),这个特性来自于其底层红黑树的数据结构实现。
注意:map中的key必须是可比较的,这意味着key类型必须支持<操作符或者你可以提供一个自定义的比较函数。
创建一个map有多种方式,最基础的是声明一个空的map:
cpp复制#include <map>
#include <string>
std::map<int, std::string> studentMap; // 键是int类型,值是string类型
也可以使用初始化列表在创建时就填充数据:
cpp复制std::map<std::string, int> ageMap = {
{"Alice", 23},
{"Bob", 25},
{"Charlie", 20}
};
对于自定义类型作为key的情况,需要提供比较函数:
cpp复制struct Person {
std::string name;
int age;
};
auto cmp = [](const Person& a, const Person& b) {
return a.name < b.name;
};
std::map<Person, int, decltype(cmp)> personMap(cmp);
向map中插入元素有几种常用方法:
cpp复制studentMap.insert(std::make_pair(1, "Tom"));
// 或者C++17风格
studentMap.insert({2, "Jerry"});
cpp复制studentMap.emplace(3, "Spike");
cpp复制studentMap[4] = "Tyke";
提示:insert和emplace在key已存在时不会修改value,而operator[]会覆盖现有value。
访问map元素也有多种方式:
cpp复制std::string name = studentMap[1]; // 如果key不存在会创建默认值
cpp复制try {
std::string name = studentMap.at(1);
} catch (const std::out_of_range& e) {
std::cerr << "Key not found: " << e.what() << std::endl;
}
cpp复制auto it = studentMap.find(1);
if (it != studentMap.end()) {
std::cout << "Found: " << it->second << std::endl;
} else {
std::cout << "Key not found" << std::endl;
}
删除map中的元素可以使用以下几种方法:
cpp复制studentMap.erase(1); // 删除key为1的元素
cpp复制auto it = studentMap.find(2);
if (it != studentMap.end()) {
studentMap.erase(it);
}
cpp复制auto first = studentMap.find(3);
auto last = studentMap.find(5);
if (first != studentMap.end() && last != studentMap.end()) {
studentMap.erase(first, last); // 删除[3,5)的元素
}
cpp复制studentMap.clear();
map提供了多种迭代器,可以灵活地遍历元素:
cpp复制// 正向遍历(按key升序)
for (auto it = studentMap.begin(); it != studentMap.end(); ++it) {
std::cout << it->first << ": " << it->second << std::endl;
}
// 反向遍历(按key降序)
for (auto rit = studentMap.rbegin(); rit != studentMap.rend(); ++rit) {
std::cout << rit->first << ": " << rit->second << std::endl;
}
// C++11范围for循环
for (const auto& [key, value] : studentMap) {
std::cout << key << ": " << value << std::endl;
}
map还提供了查找边界的方法:
cpp复制// lower_bound: 返回第一个不小于key的元素
auto lb = studentMap.lower_bound(3);
// upper_bound: 返回第一个大于key的元素
auto ub = studentMap.upper_bound(5);
// equal_range: 返回等于key的范围
auto range = studentMap.equal_range(4);
map提供了一些方法来查询其状态:
cpp复制if (studentMap.empty()) {
std::cout << "Map is empty" << std::endl;
}
std::cout << "Map size: " << studentMap.size() << std::endl;
std::cout << "Max size: " << studentMap.max_size() << std::endl;
默认情况下,map使用std::less来比较key,但我们可以自定义比较函数:
cpp复制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> caseInsensitiveMap;
cpp复制studentMap.emplace(5, "Droopy"); // 直接在map内部构造pair
cpp复制studentMap.try_emplace(6, "Sylvester");
cpp复制std::vector<std::pair<int, std::string>> entries = {{7, "Tweety"}, {8, "Henery"}};
studentMap.insert(entries.begin(), entries.end());
常见的错误是使用operator[]来检查key是否存在,这会意外插入元素。正确做法是使用find:
cpp复制// 错误方式:如果key不存在会插入默认值
if (studentMap[9]) { /* ... */ }
// 正确方式
if (studentMap.find(9) != studentMap.end()) { /* ... */ }
// C++20引入了contains方法更直观
if (studentMap.contains(9)) { /* ... */ }
当使用自定义类型作为key时,必须确保比较函数满足严格弱序关系:
cpp复制struct Point {
int x, y;
bool operator<(const Point& other) const {
return x < other.x || (x == other.x && y < other.y);
}
};
std::map<Point, std::string> pointMap;
map的迭代器在删除元素时会失效,但仅限于被删除元素的迭代器:
cpp复制auto it = studentMap.begin();
while (it != studentMap.end()) {
if (shouldRemove(*it)) {
it = studentMap.erase(it); // C++11后erase返回下一个有效迭代器
} else {
++it;
}
}
虽然map的查找是O(log n),但在极端情况下(如大量数据)可能成为瓶颈。可以考虑:
cpp复制std::map<std::string, int> wordCount;
std::string word;
while (std::cin >> word) {
++wordCount[word];
}
// 输出统计结果
for (const auto& [word, count] : wordCount) {
std::cout << word << ": " << count << std::endl;
}
cpp复制struct Student {
std::string name;
int score;
};
std::map<int, Student> studentMap; // 学号为key
// 添加学生
studentMap.emplace(1001, Student{"Alice", 90});
studentMap.emplace(1002, Student{"Bob", 85});
// 按学号查找
auto it = studentMap.find(1001);
if (it != studentMap.end()) {
std::cout << "Found: " << it->second.name << std::endl;
}
// 按学号范围查询
auto low = studentMap.lower_bound(1001);
auto high = studentMap.upper_bound(1003);
for (; low != high; ++low) {
std::cout << low->first << ": " << low->second.name << std::endl;
}
cpp复制template<typename Key, typename Value>
class LRUCache {
private:
using ListType = std::list<std::pair<Key, Value>>;
using MapType = std::map<Key, typename ListType::iterator>;
ListType items;
MapType keyMap;
size_t capacity;
public:
LRUCache(size_t cap) : capacity(cap) {}
Value* get(const Key& key) {
auto it = keyMap.find(key);
if (it == keyMap.end()) return nullptr;
items.splice(items.begin(), items, it->second);
return &(it->second->second);
}
void put(const Key& key, const Value& value) {
auto it = keyMap.find(key);
if (it != keyMap.end()) {
items.splice(items.begin(), items, it->second);
it->second->second = value;
return;
}
if (keyMap.size() >= capacity) {
auto last = items.end();
--last;
keyMap.erase(last->first);
items.pop_back();
}
items.emplace_front(key, value);
keyMap[key] = items.begin();
}
};
在实际使用map时,我发现对于小规模数据(元素数量少于100),map的性能通常足够好。但当数据量增大时,需要考虑是否真的需要有序性,因为unordered_map通常能提供更好的平均性能。另外,对于频繁插入删除的场景,要注意迭代器失效的问题,尽量使用erase返回的新迭代器来保证安全性。