简介
在了解了顺序容器之后,让我们进入关联容器的世界,学习键值对的基本原理。
C++ 有八种不同的关联容器。其中四种是键值有序的关联容器:std::set、std::map、std::multiset 和 std::multimap。另外四种是键值无序的关联容器:std::unordered_set、std::unordered_map、std::unordered_multiset 和 std::unordered_multimap。这些关联容器是特殊的容器,意味着它们支持本章“通用容器”中描述的所有操作。
概览
这八种有序和无序容器有一个共同点:它们都将一个键与一个值关联起来。我们可以使用键来获取对应的值。为了对关联容器进行分类,需要回答以下三个简单问题:
- 键是否有序?
- 键是否关联一个值?
- 键是否可以重复出现?
下表包含 行,给出了这三个问题的答案。第四个问题的答案也包含在表中:在最佳情况下,访问键的时间复杂度是多少?
| 关联容器 | 有序 | 关联值 | 允许重复键 | 访问时间 |
|---|---|---|---|---|
std::set | 是 | 否 | 否 | 对数级 |
std::unordered_set | 否 | 否 | 否 | 常数级 |
std::map | 是 | 是 | 否 | 对数级 |
std::unordered_map | 否 | 是 | 否 | 常数级 |
std::multiset | 是 | 否 | 是 | 对数级 |
std::unordered_multiset | 否 | 否 | 是 | 常数级 |
std::multimap | 是 | 是 | 是 | 对数级 |
std::unordered_multimap | 否 | 是 | 是 | 常数级 |
关联容器特性
自 C++98 起,就存在有序关联容器;从 C++11 开始引入了无序关联容器。这两类容器具有非常相似的接口。因此,下面的代码示例对于 std::map 和 std::unordered_map 是相同的。更准确地说,std::unordered_map 的接口是 std::map 接口的超集。其余三组无序关联容器也遵循同样的规律。因此,将代码从有序容器迁移到无序容器非常容易。
我们可以使用初始化列表来初始化容器,并通过下标运算符添加新元素。要访问键值对 p 的第一个元素,使用 p.first;第二个元素使用 p.second。其中 p.first 是键,p.second 是该键对应的值。
1// orderedUnorderedComparison.cpp 2#include <iostream> 3#include <map> 4#include <unordered_map> 5 6// std::map 7int main(){ 8 std::map<std::string, int> m {{"Dijkstra", 1972}, {"Scott", 1976}}; 9 m["Ritchie"]= 1983; 10 std::cout << m["Ritchie"]; // 1983 11 std::cout<<"\n"; 12 13 for(auto p : m) std::cout << "{" << p.first << "," << p.second << "}"; 14 // {Dijkstra,1972},{Ritchie,1983},{Scott,1976} 15 std::cout<<"\n"; 16 17 m.erase("Scott"); 18 for(auto p : m) std::cout << "{" << p.first << "," << p.second << "}"; 19 // {Dijkstra,1972},{Ritchie,1983} 20 std::cout<<"\n"; 21 22 m.clear(); 23 std::cout << m.size() << std::endl; // 0 24 25 // std::unordered_map 26 27 std::unordered_map<std::string, int> um {{"Dijkstra", 1972}, {"Scott", 1976}}; 28 um["Ritchie"]= 1983; 29 std::cout << um["Ritchie"]; // 1983 30 std::cout<<"\n"; 31 32 for(auto p : um) std::cout << "{" << p.first << "," << p.second << "}"; 33 // {Ritchie,1983},{Scott,1976},{Dijkstra,1972} 34 std::cout<<"\n"; 35 36 um.erase("Scott"); 37 for(auto p : um) std::cout << "{" << p.first << "," << p.second << "}"; 38 // {Ritchie,1983},{Dijkstra,1972} 39 std::cout<<"\n"; 40 41 um.clear(); 42 std::cout << um.size() << std::endl; // 0 43 44 return 0; 45}
两个程序执行之间存在一个细微差别:std::map 的键是有序的,而 std::unordered_map 的键是无序的。问题是:为什么 C++ 中会有如此相似的容器?我们已经在上表中指出原因。根本原因通常是性能。无序关联容器中访问键的时间复杂度为常数,因此与容器大小无关。当容器足够大时,这种性能差异会非常明显。请参阅“性能”小节。
更多信息
-
通用容器(见本课程相关章节)
-
性能(见本课程相关章节)
在下一课中,我们将探讨有序和无序容器在插入和删除操作上的区别。