C++主要有两大场景,面向对象编程 OOP 和 泛型编程 GP,二者的主要区别是 OOP 会把数据和方法包装在一起,通过多层的继承关系来实现复杂的功能,而 GP 则会把数据和方法分开来,很少会有继承的关系。
STL 是 c++ 的标准模板库,主要有 容器、分配器、算法、适配器、迭代器和函数对象等组成,设计之初就以 GP 为主,各类之间互相独立,比如容器和算法互相独立,通过迭代器来沟通;又比如各种容器之间并无直接的继承关系(早期)。
STL 六大组件及其关系
容器是 STL 中最被人熟知的一部分,那么容器又是如何分类的呢?
总的来说,容器可以分为序列容器和关联式容器。
一目了然
序列容器有 array(c++11)、vector、deque、List 和 Forward_List 。
array:定长数组,数组的包装类。
vector:动态数组,满了之后二倍大小扩容。
deque:双向队列。其实现是一个分段式的连续存储,由一个 vector 作为基准, 数组中的元素都是指针, 指向每一个节点,数据存储在每个节点上,每个节点可以认为是一个数组。stack 和 queue 都是受限的 deque ,接口底部都是由 deque 实现,因此有人认为 stack 和 queue 不是容器,而是一种容器适配器 container adapter 。
List:双向循环链表。
Forward_List:单链表。
关联式容器有 set / multiSet、 map / multiMap、 unordered set / multiSet、 unordered-map/multiMap 等。
set / multiSet:红黑树实现,区别在于 set 中的元素是唯一的,multiSet 可以重复插入相同元素,同时不允许迭代器修改 set 中的元素,这一点是通过 const_iterator 实现的。底层是调用了 rbTree 的 unique_insert() 和 equal_insert()方法,这里可以看出,set 也是一种容器适配器。
map / multiMap:也是由红黑树实现,和 set 的区别在于节点有 key/ data 组成,而 set 中的没有 data, 或者说 data 就是 key。类似的是,无法通过迭代器修改元素的 key ,但是可以修改 data。这点是通过在定义节点类型的时候,Pair<const keyType, dataType>,可见节点是一个 pair 类型,key 是常量无法修改。
map 的[key]操作符会查找对应 key 的data,如果没有就会插入 key , 对应的 data 是默认值。因此也可以使用 [] 来插入数据,类似于先查找后 insert 。
unordered set/map :基于哈希表实现,所以元素是无序的。桶子的个数默认是质数,比如53 97,采用链地址来解冲突。如果元素的个数大于桶的个数,就再散列。桶增长的速度类似于vector 的增长方式,2 倍增长,选择距离这个数最近的质数作为新的桶数。 stl 有一个写死的质数列表,增长的方式按照表的内容来增长。
/*prime list*/
这个图更加直观,来自侯捷PPT。字节大小是32位机器下的size。
上图中有缩进的代表衍生关系,并非继承,而是复合:类的内部有其他类的指针而已,比如 set 内部就有一个 rb_tree 的指针,这样做可以解耦。