上一篇文章中我们简单的介绍了一下STL中的序列容器和容器适配器,这篇文章中我们将重点介绍STL中的关联容器(最后四个在概念上应该不是关联容器,但是因为和前面的容器联系太紧密,统一放在这里讲解),主要内容包括:
通常提到关联容器,大家可能首先会想到的是std::map
,这里先讲解std::set
是避免出现关联数组
和关联容器
概念上的混淆。前者是数据结构中的概念,而后则是C++中特有的概念。
关联容器这个概念,在C++里面是一个Concept(关于concept的讨论我们在上一篇文章中有讲解,这里不再赘述),它的基本定义如下:
An AssociativeContainer is an ordered Container that provides fast lookup of objects based on keys.
翻译成中文,大概是说关联容器是提供了基于key的快速查找功能的有序容器。它实际上是 Container 这个 Concept 的 Refinement。
Refinement 在泛型编程中是一个非常重要的概念,它和 Concept 的关系类似于OO世界中父子继承关系。我们知道 Concept 表示的不是一个类型而是一个类型集合,所以 Refinement 表示的其实也是一个类型集合,这些类型比 Concept 中的定义的类型要有更多的约束和特性(约束和特性其实是一个问题的两个方面,你约束的越是严格,你能够使用的特性越多,但是你能使用的场景越少,比如 Java 中 Object 类哪儿都能用,又哪儿都用不了)。
在数学概念上,集合表示具有某种特定性质的事物的总体,它有三个特性:无序性、互异性、确定性。
个人认为,C++世界里面的std::set
其实并不是传统意义上的集合,因为它不符合无序的特性(当然你也可以理解成无序表示任何顺序都可以,有序也属于无序的一种状态)。
需要特别提醒的是,因为关联容器使用key值排序,你不能修改key值,否则你会破坏关联容器的内部结构(虽然有些情况下更改key值在逻辑上是合理并且合法的【1】)。在C++11之后,关联容器返回的迭代器,key都默认是const。如果你需要更改key的值,最好的方式是先删除,再插入(有move的存在,这一步的开销相对小一些)。
互异是指集合内部的任意两个元素都不相同,这一点在std::set
中满足,但是在std::multi_set
中并不满足。
std::set
元素的互异在编程中有一个非常大的作用是去重,当然你也可以使用sort+unique的方式来处理去重,但是std::set
的方式在逻辑上更具有表达力。
std::set
中的很多成员和std::map
类似,只不过它们的value_type
不一样,std::set<std::string>
的value_type
是std::string
,而std::map<int, std::string>
的value_type
是std::pair<const int, std::string>
。因此这一小节不介绍相关的成员函数,你可以查看std::map
的相关成员函数的介绍,然后对照std::set
的文档来理解这一部分内容。
std::map
可能是C++世界使用得最广泛的关联容器,它以键值对的形式存储数据的一种关联容器,可以用于充当关联数组。
在数据结构的范畴里面,map又被称为关联数组。一个普通的数组,可以使用int(严格来说应该是size_t)做为下标来访问数组内部的元素,关联数组则可以使用任意类型做为下标来存取数组内部的数组。当然这里说的数组是一个逻辑上的概念,关联数组的实现物理上的实现通常是树或者哈希表等等。
需要注意的是关联数组和关联容器是完全不同的两个概念,关联容器强调的是提供快速访问的有序容器,这个概念只存在于C++中;而关联数组强调的是键值对的形式存储数据,并通过key来快速访问数据,这个概念是传统数据结构中的概念,在不同的语言对应不同的类型,比如Java的map,Python的dict。关联容器不一定是关联数组,比如set;关联数组也不一定是关联容器,比如unordered_map。
map和set的实现通常是一个颗红黑树,红黑树是一种平衡二叉排序树。它的节点要么是黑要么是红,但是