C++关联式容器详解:map、set与unordered_map的原理与应用
C++关联式容器涵盖map、set与unordered_map,分别基于红黑树与哈希表实现。解析了它们的特性、通用接口及具体用法,包括键值对存储、唯一元素管理及快速查找机制。通过性能测试代码对比了不同容器在大数据量下的耗时差异,并结合电话簿案例展示了实际应用场景。最后提供了选择策略与练习建议,帮助开发者根据有序性、查找效率等需求优化数据结构选型。

C++关联式容器涵盖map、set与unordered_map,分别基于红黑树与哈希表实现。解析了它们的特性、通用接口及具体用法,包括键值对存储、唯一元素管理及快速查找机制。通过性能测试代码对比了不同容器在大数据量下的耗时差异,并结合电话簿案例展示了实际应用场景。最后提供了选择策略与练习建议,帮助开发者根据有序性、查找效率等需求优化数据结构选型。

本章将深入探讨C++标准库中最常用的关联式容器——map、set和unordered_map的核心原理与应用。通过学习,你将能够:
关联式容器是一种将元素与键值关联起来的数据结构,分为两类:
所有关联式容器都提供了通用的接口:
size():获取容器大小empty():判断容器是否为空begin()/end():获取迭代器insert():插入元素erase():删除元素clear():清空容器find():查找元素map是一个有序键值对容器,具有以下特性:
#include <iostream>
#include <map>
#include <string>
int main() {
std::cout << "=== map容器示例 ===" << std::endl;
// 创建 map
std::map<int, std::string> idToName;
std::map<std::string, int> nameToAge;
// 添加元素
idToName[1] = "张三";
idToName[2] = "李四";
idToName[3] = "王五";
nameToAge["张三"] = 25;
nameToAge["李四"] = 30;
nameToAge["王五"] = 35;
// 访问元素
std::cout << "idToName[1]: " << idToName[1] << std::endl;
std::cout << "nameToAge[\"李四\"]: " << nameToAge["李四"] << std::endl;
// 遍历元素
std::cout << "遍历 idToName: " << std::endl;
for (const auto& pair : idToName) {
std::cout << "ID: " << pair.first << ", 姓名:" << pair.second << std::endl;
}
std::cout << "遍历 nameToAge: " << std::endl;
for (std::map<std::string, int>::const_iterator it = nameToAge.begin(); it != nameToAge.end(); ++it) {
std::cout << "姓名:" << it->first << ", 年龄:" << it->second << std::endl;
}
// 查找元素
auto findResult = idToName.find(2);
if (findResult != idToName.end()) {
std::cout << "找到 ID 为 2 的姓名:" << findResult->second << std::endl;
}
// 插入和删除
idToName.insert(std::make_pair(4, "赵六")); // 插入元素
std::cout << "插入后的 idToName: " << std::endl;
for (const auto& pair : idToName) {
std::cout << "ID: " << pair.first << ", 姓名:" << pair.second << std::endl;
}
idToName.erase(3); // 删除元素
std::cout << "删除后的 idToName: " << std::endl;
for (const auto& pair : idToName) {
std::cout << "ID: " << pair.first << ", 姓名:" << pair.second << std::endl;
}
// 其他操作
std::cout << "idToName 大小:" << idToName.size() << std::endl;
std::cout << "idToName 是否为空:" << idToName.empty() << std::endl;
std::cout << "idToName 包含 ID 为 1: " << (idToName.count(1) > 0) << std::endl;
return 0;
}
map常用于需要键值对映射的场景,如:
set是一个有序集合容器,具有以下特性:
#include <iostream>
#include <set>
#include <string>
int main() {
std::cout << "=== set容器示例 ===" << std::endl;
// 创建 set
std::set<int> numbers;
std::set<std::string> names;
// 添加元素
numbers.insert(10);
numbers.insert(20);
numbers.insert(30);
numbers.insert(20); // 重复元素不会插入
names.insert("张三");
names.insert("李四");
names.insert("王五");
// 遍历元素
std::cout << "遍历 numbers: ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
std::cout << "遍历 names: ";
for (const std::string& name : names) {
std::cout << name << " ";
}
std::cout << std::endl;
// 查找元素
auto findResult = numbers.find(20);
if (findResult != numbers.end()) {
std::cout << "找到元素 20" << std::endl;
}
// 插入和删除
numbers.insert();
std::cout << ;
( num : numbers) {
std::cout << num << ;
}
std::cout << std::endl;
numbers.();
std::cout << ;
( num : numbers) {
std::cout << num << ;
}
std::cout << std::endl;
std::cout << << numbers.() << std::endl;
std::cout << << numbers.() << std::endl;
std::cout << << (numbers.() > ) << std::endl;
;
}
set常用于需要存储唯一元素的场景,如:
unordered_map是一个无序键值对容器,具有以下特性:
#include <iostream>
#include <unordered_map>
#include <string>
int main() {
std::cout << "=== unordered_map容器示例 ===" << std::endl;
// 创建 unordered_map
std::unordered_map<int, std::string> idToName;
std::unordered_map<std::string, int> nameToAge;
// 添加元素
idToName[1] = "张三";
idToName[2] = "李四";
idToName[3] = "王五";
nameToAge["张三"] = 25;
nameToAge["李四"] = 30;
nameToAge["王五"] = 35;
// 访问元素
std::cout << "idToName[1]: " << idToName[1] << std::endl;
std::cout << "nameToAge[\"李四\"]: " << nameToAge["李四"] << std::endl;
// 遍历元素(顺序不确定)
std::cout << "遍历 idToName: " << std::endl;
for (const auto& pair : idToName) {
std::cout << "ID: " << pair.first << ", 姓名:" << pair.second << std::endl;
}
// 查找元素
auto findResult = idToName.find(2);
if (findResult != idToName.end()) {
std::cout << "找到 ID 为 2 的姓名:" << findResult->second << std::endl;
}
idToName.(std::(, ));
std::cout << << std::endl;
( & pair : idToName) {
std::cout << << pair.first << << pair.second << std::endl;
}
idToName.();
std::cout << << std::endl;
( & pair : idToName) {
std::cout << << pair.first << << pair.second << std::endl;
}
std::cout << << idToName.() << std::endl;
std::cout << << idToName.() << std::endl;
std::cout << << (idToName.() > ) << std::endl;
;
}
#include <iostream>
#include <map>
#include <unordered_map>
#include <chrono>
#include <string>
void testMap(int n) {
auto start = std::chrono::high_resolution_clock::now();
std::map<int, std::string> map;
for (int i = 0; i < n; i++) {
map[i] = "测试字符串" + std::to_string(i);
}
// 查找元素
for (int i = 0; i < n; i++) {
auto it = map.find(i);
if (it == map.end()) {
std::cout << "未找到元素" << i << std::endl;
}
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
std::cout << "map 操作" << n << "个元素耗时:" << duration.count() << "ms" << std::endl;
}
void testUnorderedMap(int n) {
auto start = std::chrono::high_resolution_clock::now();
std::unordered_map<, std::string> unorderedMap;
( i = ; i < n; i++) {
unorderedMap[i] = + std::(i);
}
( i = ; i < n; i++) {
it = unorderedMap.(i);
(it == unorderedMap.()) {
std::cout << << i << std::endl;
}
}
end = std::chrono::high_resolution_clock::();
duration = std::chrono::<std::chrono::milliseconds>(end - start);
std::cout << << n << << duration.() << << std::endl;
}
{
std::cout << << std::endl;
n = ;
(n);
(n);
;
}
| 特性 | map | set | unordered_map | unordered_set |
|---|---|---|---|---|
| 存储方式 | 键值对 | 元素值 | 键值对 | 元素值 |
| 存储顺序 | 有序(默认升序) | 有序(默认升序) | 无序(哈希值) | 无序(哈希值) |
| 查找时间 | O(log n) | O(log n) | O(1)平均 | O(1)平均 |
| 插入时间 | O(log n) | O(log n) | O(1)平均 | O(1)平均 |
| 删除时间 | O(log n) | O(log n) | O(1)平均 | O(1)平均 |
| 内存开销 | 高(平衡二叉树) | 高(平衡二叉树) | 中等(哈希表) | 中等(哈希表) |
| 适用场景 | 需要有序键值对映射 | 需要有序唯一元素 | 需要快速查找的键值对映射 | 需要快速查找的唯一元素 |
让我们通过一个综合案例来应用本章所学的知识:
#include <iostream>
#include <map>
#include <unordered_map>
#include <string>
#include <algorithm>
// 电话簿类
class PhoneBook {
private:
std::map<std::string, std::string> nameToNumber; // 姓名到电话号码的映射
std::unordered_map<std::string, std::string> numberToName; // 电话号码到姓名的映射
public:
// 添加联系人
void addContact(const std::string& name, const std::string& number) {
nameToNumber[name] = number;
numberToName[number] = name;
}
// 删除联系人
void removeContact(const std::string& name) {
auto it = nameToNumber.find(name);
if (it != nameToNumber.end()) {
numberToName.erase(it->second);
nameToNumber.erase(it);
}
}
// 查找联系人(按姓名)
std::string findByName(const std::string& name) const {
auto it = nameToNumber.find(name);
if (it != nameToNumber.end()) {
it->second;
}
;
}
{
it = numberToName.(number);
(it != numberToName.()) {
it->second;
}
;
}
{
std::cout << << std::endl;
( & pair : nameToNumber) {
std::cout << << pair.first << << pair.second << std::endl;
}
}
{
nameToNumber.(name) > ;
}
};
{
std::cout << << std::endl;
PhoneBook phoneBook;
phoneBook.(, );
phoneBook.(, );
phoneBook.(, );
phoneBook.();
std::string zhangNumber = phoneBook.();
std::cout << << zhangNumber << std::endl;
std::string liName = phoneBook.();
std::cout << << liName << std::endl;
exists = phoneBook.();
std::cout << << exists << std::endl;
phoneBook.();
std::cout << << std::endl;
phoneBook.();
;
}
本章介绍了C++标准库中三种常用关联式容器的核心知识,包括:

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online