1. C++中的map类概述
在C++标准模板库(STL)中,map是一种关联式容器,它提供了一种将键(key)与值(value)配对存储的高效方式。map内部通常实现为红黑树(一种自平衡二叉查找树),这保证了元素的有序性和操作的高效性。
map的核心特性包括:
- 键值对存储:每个元素都是一个pair对象,包含唯一的key和对应的value
- 自动排序:元素按照key的升序自动排列(可通过比较函数修改)
- 快速查找:基于红黑树的实现使得查找时间复杂度为O(log n)
- 唯一键值:每个key在map中只能出现一次(如需重复key可使用multimap)
2. map的基本用法
2.1 头文件与声明
使用map需要包含头文件:
cpp复制#include <map>
基本声明语法:
cpp复制std::map<KeyType, ValueType> mapName;
示例:
cpp复制std::map<std::string, int> studentScores; // 学生姓名到分数的映射
std::map<int, std::string> employeeMap; // 员工ID到姓名的映射
2.2 常用操作
插入元素
cpp复制// 使用insert方法
studentScores.insert(std::make_pair("Alice", 90));
studentScores.insert({"Bob", 85});
// 使用下标操作符
studentScores["Charlie"] = 95;
注意:使用下标操作符时,如果key不存在会自动创建并初始化value(基本类型为0,类类型调用默认构造函数)
访问元素
cpp复制// 使用下标操作符(注意与vector不同,map的下标是key不是数字)
int aliceScore = studentScores["Alice"];
// 使用at方法(key不存在会抛出out_of_range异常)
try {
int bobScore = studentScores.at("Bob");
} catch(const std::out_of_range& e) {
std::cerr << "Key not found: " << e.what() << '\n';
}
// 使用迭代器
auto it = studentScores.find("Charlie");
if(it != studentScores.end()) {
std::cout << "Charlie's score: " << it->second << '\n';
}
删除元素
cpp复制// 通过key删除
studentScores.erase("Alice");
// 通过迭代器删除
auto it = studentScores.find("Bob");
if(it != studentScores.end()) {
studentScores.erase(it);
}
// 删除所有元素
studentScores.clear();
遍历map
cpp复制// 使用迭代器
for(auto it = studentScores.begin(); it != studentScores.end(); ++it) {
std::cout << it->first << ": " << it->second << '\n';
}
// 使用范围for循环(C++11及以上)
for(const auto& pair : studentScores) {
std::cout << pair.first << ": " << pair.second << '\n';
}
// 使用结构化绑定(C++17及以上)
for(const auto& [name, score] : studentScores) {
std::cout << name << ": " << score << '\n';
}
3. map的高级特性
3.1 自定义比较函数
默认情况下,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 ca, char cb) { return tolower(ca) < tolower(cb); }
);
}
};
std::map<std::string, int, CaseInsensitiveCompare> caseInsensitiveMap;
3.2 性能考虑
map的各种操作时间复杂度:
- 插入:O(log n)
- 删除:O(log n)
- 查找:O(log n)
- 遍历:O(n)
当需要频繁查找但不需要有序性时,可以考虑std::unordered_map(哈希表实现,平均O(1)时间复杂度)。
3.3 与其它容器的交互
map可以方便地与其它STL容器交互:
cpp复制// 从vector初始化map
std::vector<std::pair<int, std::string>> vec = {{1, "one"}, {2, "two"}};
std::map<int, std::string> numMap(vec.begin(), vec.end());
// map转vector
std::vector<std::pair<int, std::string>> vecFromMap(numMap.begin(), numMap.end());
4. 实际应用案例
4.1 词频统计
cpp复制std::string text = "this is a test this is only a test";
std::map<std::string, int> wordCount;
std::istringstream iss(text);
std::string word;
while(iss >> word) {
++wordCount[word];
}
for(const auto& [word, count] : wordCount) {
std::cout << word << ": " << count << '\n';
}
4.2 学生成绩管理系统
cpp复制class GradeBook {
private:
std::map<std::string, std::map<std::string, int>> studentGrades;
// 外层map: 学生姓名 -> 课程成绩map
// 内层map: 课程名称 -> 分数
public:
void addGrade(const std::string& student,
const std::string& course,
int grade) {
studentGrades[student][course] = grade;
}
void printStudentGrades(const std::string& student) {
auto it = studentGrades.find(student);
if(it != studentGrades.end()) {
std::cout << "Grades for " << student << ":\n";
for(const auto& [course, grade] : it->second) {
std::cout << course << ": " << grade << '\n';
}
}
}
};
4.3 配置管理系统
cpp复制class ConfigManager {
private:
std::map<std::string, std::map<std::string, std::string>> configs;
public:
void loadConfig(const std::string& filename) {
std::ifstream file(filename);
std::string line, section, key, value;
while(std::getline(file, line)) {
if(line.empty() || line[0] == '#') continue;
if(line[0] == '[' && line.back() == ']') {
section = line.substr(1, line.size()-2);
} else {
size_t pos = line.find('=');
if(pos != std::string::npos) {
key = line.substr(0, pos);
value = line.substr(pos+1);
configs[section][key] = value;
}
}
}
}
std::string getValue(const std::string& section,
const std::string& key,
const std::string& defaultValue = "") {
auto secIt = configs.find(section);
if(secIt != configs.end()) {
auto keyIt = secIt->second.find(key);
if(keyIt != secIt->second.end()) {
return keyIt->second;
}
}
return defaultValue;
}
};
5. 常见问题与解决方案
5.1 判断key是否存在
错误做法:
cpp复制if(studentScores["Alice"]) { ... } // 会创建不存在的元素
正确做法:
cpp复制// 方法1:使用find
if(studentScores.find("Alice") != studentScores.end()) { ... }
// 方法2:使用count(对于map,结果只能是0或1)
if(studentScores.count("Alice")) { ... }
5.2 高效插入
当插入大量元素时,可以预先分配空间(虽然map是动态增长的):
cpp复制studentScores.reserve(1000); // 错误!map没有reserve方法
// 正确做法:对于map,无法预分配空间,但可以:
// 1. 使用insert的批量插入版本
std::vector<std::pair<std::string, int>> initialData = {...};
studentScores.insert(initialData.begin(), initialData.end());
// 2. 使用emplace直接构造元素,避免临时对象
studentScores.emplace("Alice", 90);
5.3 自定义类型的key
如果使用自定义类型作为key,需要提供比较函数或重载operator<:
cpp复制struct Student {
int id;
std::string name;
bool operator<(const Student& other) const {
return id < other.id; // 按id排序
}
};
std::map<Student, int> studentScores;
或者使用外部比较函数:
cpp复制struct Student {
int id;
std::string name;
};
struct StudentCompare {
bool operator()(const Student& a, const Student& b) const {
return a.id < b.id;
}
};
std::map<Student, int, StudentCompare> studentScores;
5.4 内存管理
map的每个元素都是独立分配的,当存储大量小对象时可能会有内存碎片问题。可以考虑:
- 使用自定义分配器
- 对于小对象,考虑使用连续存储结构(如vector)加上二分查找
- 使用内存池技术
6. 性能优化技巧
6.1 使用emplace代替insert
cpp复制// 低效
studentScores.insert(std::make_pair("Alice", 90));
// 高效 - 直接在map中构造元素,避免临时pair
studentScores.emplace("Alice", 90);
6.2 利用lower_bound和upper_bound
对于有序map,可以利用这些方法进行范围查询:
cpp复制std::map<int, std::string> data = {
{1, "one"}, {2, "two"}, {3, "three"}, {4, "four"}, {5, "five"}
};
// 查找所有key >= 2且 < 4的元素
auto low = data.lower_bound(2); // 第一个不小于2的元素
auto high = data.upper_bound(4); // 第一个大于4的元素
for(auto it = low; it != high; ++it) {
std::cout << it->first << ": " << it->second << '\n';
}
6.3 使用try_emplace (C++17)
避免不必要的临时对象构造:
cpp复制std::map<std::string, std::vector<int>> data;
// 如果key不存在,构造value(空vector)
auto [it, inserted] = data.try_emplace("key1");
if(inserted) {
it->second.push_back(42); // 直接操作新插入的元素
}
6.4 使用extract修改key (C++17)
避免先删除再插入:
cpp复制std::map<int, std::string> data = {{1, "one"}, {2, "two"}};
auto node = data.extract(1); // 提取节点但不释放内存
if(!node.empty()) {
node.key() = 3; // 修改key
data.insert(std::move(node)); // 重新插入
}
7. 替代方案
7.1 unordered_map
当不需要元素有序时,unordered_map(基于哈希表)通常有更好的性能:
cpp复制#include <unordered_map>
std::unordered_map<std::string, int> hashMap;
7.2 多键map
有时需要多个键对应一个值,可以考虑:
cpp复制// 方法1:使用tuple作为key
std::map<std::tuple<std::string, int>, std::string> multiKeyMap;
// 方法2:嵌套map
std::map<std::string, std::map<int, std::string>> nestedMap;
7.3 第三方库
对于高性能需求,可以考虑:
- Google的absl::flat_hash_map
- Boost.MultiIndex(支持多键索引)
- uthash(C风格的哈希表)
