C++ 中的map容器详解
什么是map容器?
Map是C++ STL(标准模板库)中提供的一种关联容器,它存储的是键值对(key-value pairs)元素,其中每个键都是唯一的。Map容器内部实现是一棵红黑树(平衡二叉搜索树),这使得map中的元素按照键(key)自动排序。
map容器的主要特点:
- 键值对存储 - 每个元素包含一个键和一个相关联的值
- 唯一键 - 每个键在map中只能出现一次
- 自动排序 - 元素按照键的顺序被自动排序
- 快速访问 - 可以通过键快速查找对应的值
提示
如果你需要一个可以通过键快速查找值的数据结构,而且希望这些元素能够保持顺序,map是一个很好的选择!
map容器的基本语法
头文件和声明
要使用map容器,需要先包含相应的头文件:
cpp
#include <map>
int main() {
// 创建一个将字符串映射到整数的map
std::map<std::string, int> studentScores;
// 其他代码...
return 0;
}
插入元素
向map中插入元素有多种方法:
cpp
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, int> studentScores;
// 方法1:使用insert()函数插入pair
studentScores.insert(std::pair<std::string, int>("Alice", 95));
// 方法2:使用make_pair()函数
studentScores.insert(std::make_pair("Bob", 89));
// 方法3:使用数组下标操作符
studentScores["Charlie"] = 78;
studentScores["David"] = 92;
// 输出map中的元素
for (const auto& student : studentScores) {
std::cout << student.first << ": " << student.second << std::endl;
}
return 0;
}
输出结果:
Alice: 95
Bob: 89
Charlie: 78
David: 92
备注
注意到输出的元素按照键(学生姓名)的字母顺序排列,这是因为map默认使用<
运算符对键进行排序。
map容器的常用操作
查找元素
cpp
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, int> studentScores;
studentScores["Alice"] = 95;
studentScores["Bob"] = 89;
studentScores["Charlie"] = 78;
// 使用find()函数查找元素
auto it = studentScores.find("Bob");
if (it != studentScores.end()) {
std::cout << "找到了Bob的分数:" << it->second << std::endl;
} else {
std::cout << "没有找到Bob" << std::endl;
}
// 使用count()函数检查键是否存在
if (studentScores.count("David") > 0) {
std::cout << "David的分数是:" << studentScores["David"] << std::endl;
} else {
std::cout << "没有David的记录" << std::endl;
}
return 0;
}
输出结果:
找到了Bob的分数:89
没有David的记录
删除元素
cpp
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, int> studentScores;
studentScores["Alice"] = 95;
studentScores["Bob"] = 89;
studentScores["Charlie"] = 78;
studentScores["David"] = 92;
std::cout << "初始map大小: " << studentScores.size() << std::endl;
// 通过键删除元素
studentScores.erase("Bob");
std::cout << "删除Bob后map大小: " << studentScores.size() << std::endl;
// 通过迭代器删除元素
auto it = studentScores.find("Charlie");
if (it != studentScores.end()) {
studentScores.erase(it);
}
std::cout << "删除Charlie后map大小: " << studentScores.size() << std::endl;
// 删除所有元素
studentScores.clear();
std::cout << "清空后map大小: " << studentScores.size() << std::endl;
return 0;
}
输出结果:
初始map大小: 4
删除Bob后map大小: 3
删除Charlie后map大小: 2
清空后map大小: 0
遍历map
cpp
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, int> studentScores;
studentScores["Alice"] = 95;
studentScores["Bob"] = 89;
studentScores["Charlie"] = 78;
// 方法1:使用范围for循环(C++11及以上)
std::cout << "使用范围for循环遍历:" << std::endl;
for (const auto& pair : studentScores) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
// 方法2:使用迭代器
std::cout << "\n使用迭代器遍历:" << std::endl;
for (auto it = studentScores.begin(); it != studentScores.end(); ++it) {
std::cout << it->first << ": " << it->second << std::endl;
}
return 0;
}
输出结果:
使用范围for循环遍历:
Alice: 95
Bob: 89
Charlie: 78
使用迭代器遍历:
Alice: 95
Bob: 89
Charlie: 78
map的其他常用功能
检查容器是否为空
cpp
if (studentScores.empty()) {
std::cout << "容器为空" << std::endl;
} else {
std::cout << "容器不为空,包含 " << studentScores.size() << " 个元素" << std::endl;
}
获取map大小
cpp
std::cout << "map中有 " << studentScores.size() << " 个元素" << std::endl;
交换两个map
cpp
std::map<std::string, int> map1 = {{"A", 1}, {"B", 2}};
std::map<std::string, int> map2 = {{"X", 24}, {"Y", 25}};
map1.swap(map2); // 现在map1包含{"X", 24}和{"Y", 25},map2包含{"A", 1}和{"B", 2}
获取上下界
cpp
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<int, std::string> codeNames;
codeNames[1] = "Alpha";
codeNames[3] = "Charlie";
codeNames[5] = "Echo";
codeNames[7] = "Golf";
// 找到键值为3的元素
auto it_lower = codeNames.lower_bound(3); // 指向键为3的元素
std::cout << "lower_bound(3): " << it_lower->first << " -> " << it_lower->second << std::endl;
// 找到键值大于3的第一个元素
auto it_upper = codeNames.upper_bound(3); // 指向键为5的元素
std::cout << "upper_bound(3): " << it_upper->first << " -> " << it_upper->second << std::endl;
return 0;
}
输出结果:
lower_bound(3): 3 -> Charlie
upper_bound(3): 5 -> Echo
实际应用场景
场景1:单词频率统计
cpp
#include <iostream>
#include <map>
#include <string>
#include <sstream>
int main() {
std::string text = "apple banana apple orange banana apple pear";
std::istringstream iss(text);
std::map<std::string, int> wordFreq;
std::string word;
// 统计每个单词出现的次数
while (iss >> word) {
// 每次遇到单词时,对应的计数加1
wordFreq[word]++;
}
// 输出每个单词及其频率
std::cout << "单词频率统计:" << std::endl;
for (const auto& pair : wordFreq) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
输出结果:
单词频率统计:
apple: 3
banana: 2
orange: 1
pear: 1
场景2:学生成绩管理系统
cpp
#include <iostream>
#include <map>
#include <string>
#include <iomanip>
class StudentManager {
private:
std::map<std::string, std::map<std::string, int>> studentScores; // 学生id -> {课程名 -> 分数}
public:
// 添加或更新学生成绩
void addScore(const std::string& studentId, const std::string& course, int score) {
studentScores[studentId][course] = score;
}
// 获取学生特定课程的成绩
int getScore(const std::string& studentId, const std::string& course) {
if (studentScores.find(studentId) != studentScores.end() &&
studentScores[studentId].find(course) != studentScores[studentId].end()) {
return studentScores[studentId][course];
}
return -1; // 表示不存在
}
// 计算学生的平均分
double getAverage(const std::string& studentId) {
if (studentScores.find(studentId) == studentScores.end() ||
studentScores[studentId].empty()) {
return 0.0;
}
double sum = 0.0;
for (const auto& course : studentScores[studentId]) {
sum += course.second;
}
return sum / studentScores[studentId].size();
}
// 显示所有学生的所有成绩
void displayAllScores() {
for (const auto& student : studentScores) {
std::cout << "学生ID: " << student.first << std::endl;
for (const auto& course : student.second) {
std::cout << " " << course.first << ": " << course.second << std::endl;
}
std::cout << " 平均分: " << std::fixed << std::setprecision(2)
<< getAverage(student.first) << std::endl << std::endl;
}
}
};
int main() {
StudentManager manager;
// 添加一些学生成绩
manager.addScore("10001", "Math", 95);
manager.addScore("10001", "English", 87);
manager.addScore("10001", "Physics", 92);
manager.addScore("10002", "Math", 82);
manager.addScore("10002", "English", 78);
manager.addScore("10002", "Chemistry", 90);
// 显示所有成绩
manager.displayAllScores();
// 查询特定学生的特定课程分数
std::cout << "学生10001的英语成绩: " << manager.getScore("10001", "English") << std::endl;
return 0;
}
输出结果:
学生ID: 10001
English: 87
Math: 95
Physics: 92
平均分: 91.33
学生ID: 10002
Chemistry: 90
English: 78
Math: 82
平均分: 83.33
学生10001的英语成绩: 87
使用自定义类型作为键
要在map中使用自定义类型作为键,需要为该类型定义一个比较函数:
cpp
#include <iostream>
#include <map>
#include <string>
// 自定义类型
struct Student {
int id;
std::string name;
// 定义小于操作符,用于map排序
bool operator<(const Student& other) const {
return id < other.id; // 按学生ID排序
}
};
int main() {
// 使用Student作为键,int作为值的map
std::map<Student, int> studentScores;
// 添加元素
studentScores[{101, "Alice"}] = 95;
studentScores[{102, "Bob"}] = 89;
studentScores[{103, "Charlie"}] = 78;
// 遍历并输出
for (const auto& pair : studentScores) {
std::cout << "ID: " << pair.first.id
<< ", 姓名: " << pair.first.name
<< ", 分数: " << pair.second << std::endl;
}
return 0;
}
输出结果:
ID: 101, 姓名: Alice, 分数: 95
ID: 102, 姓名: Bob, 分数: 89
ID: 103, 姓名: Charlie, 分数: 78
警告
对于自定义类型,必须定义比较操作符(通常是<
),否则编译器将无法排序map的元素。
map与unordered_map的比较
C++ STL提供了两种键值对容器:map
和unordered_map
。以下是它们之间的主要区别:
特性 | map | unordered_map |
---|---|---|
内部实现 | 红黑树 | 哈希表 |
元素顺序 | 按键排序 | 无序 |
查找复杂度 | O(log n) | 平均O(1),最坏O(n) |
插入复杂度 | O(log n) | 平均O(1),最坏O(n) |
删除复杂度 | O(log n) | 平均O(1),最坏O(n) |
内存消耗 | 较少 | 较多(需要哈希表开销) |
迭代器稳定性 | 删除元素时不会失效 | 如果rehash可能失效 |
适用场景 | 需要有序,需要稳定的迭代器 | 速度优先,不关心顺序 |
根据您的具体需求选择适当的容器:
- 如果需要元素自动排序,选择
map
- 如果查找速度是最重要的,选择
unordered_map
总结
map是C++ STL中一种非常实用的关联容器,它提供了键值对的存储方式,并且会根据键自动对元素进行排序。本文中,我们学习了:
- map的基本概念和特点
- 如何创建map并添加、访问、删除元素
- map的各种常用操作,如find()、count()、size()等
- 如何遍历map中的元素
- 使用自定义类型作为map的键
- map与unordered_map的区别和选择标准
- map在实际应用中的案例
使用map容器可以帮助我们轻松实现需要键值映射的各种功能,例如单词频率统计、学生成绩管理、电话簿等。随着您对C++的理解加深,map将成为您编程工具箱中一个不可或缺的工具。
练习题
- 编写一个程序,统计一个文本文件中各字符出现的频率。
- 实现一个简单的电话簿程序,可以添加、查找、删除联系人。
- 使用map实现一个单词翻译器,将英文单词映射到中文释义。
- 编写一个程序,统计给定文章中每个单词出现的次数,并按照出现频率降序输出。
- 创建一个使用自定义类型Person作为键的map,其中Person包含姓名和年龄,并实现按姓名排序。
进一步学习资源
- C++ STL官方文档
- 《C++ Primer》第11章:关联容器
- 《Effective STL》:Scott Meyers著
- C++ Reference网站的map部分
- 《The C++ Standard Library》:Nicolai Josuttis著