《STL源码剖析》阅读笔记

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yyf_it/article/details/52292257

1.空间的配置与释放

对象构造前的空间配置和对象析构后的空间释放,由<stl_alloc.h>负责,SGI对此的设计哲学:

  • 向system heap要空间
  • 考虑多线程状态
  • 考虑内存不足时的应变措施
  • 考虑过多“小型区块”可能造成的内存碎片(fragment)问题
考虑到小型区块可能造成的内存碎片问题,SGI设计了双层级配置器(通过开启一个标识符,来确定是哪个级别的)
  • 第一级配置器直接使用malloc() 和free()
  • 第二级配置器则视情况采用不同的策略,当配置区块超过128bytes时,视之为“足够大”,便调用第一级配置器;当配置块小于128bytes时,认为其“过小”,为了降低overhead,采用memory pool(内存池)的方式来正例,而不再求助于第一级配置器。

第一级配置器中的allocate()直接调用malloc(),deallocate()直接调用free(), reallocate()直接调用realloc()来申请和回收空间

第二级别的做法是,如果区域足够大(超过128bytes时),就交由第一级别配置器处理,否则以内存池管理:每次配置一大块内存,并维护对应的自由链表(free-list)。下次如果有相同大小的内存需求,就直接从free-lists中取,如果释放,就由配置器回收到free-list中,free-list中的块的大小通常为8的倍数。但维护不同指向多个链表的指针也会浪费空间。

第二级的allocate函数做法:如果申请的空间大于128bytes,那么直接调用第一级配置器,小于128bytes就检查对应的free list,如果free list中有,直接分配;否则调用refill()函数;deallocate的做法:该函数首先判断区块大小,大于128bytes就调用第一级配置器,否则找到对应的free list,然后将区块收回。

refill()函数通过调用chunk_alloc()从内存池中获取一块空间(内存池如何管理/)


2.vector

vector中有三个重要的迭代器:start, finish, end_of_storage

start:表示目前使用空间的头

finish:表示目前使用空间的尾

end_of_storage:表示目前可用空间的尾

其中的很多操作如begin(), end(), size(), capacity(), empty(), operator[]等都是通过这三个指针来操作

事实上:迭代器就是普通的指针 vector<int> a; a的迭代器就是int *指针

vector的内部就是数组,初始化时,会分配一个默认大小的capacity,但push_back()向vector中插入元素时,如果当时的容量不够,那么容量会扩充至两倍。容量的扩充过程中,可能需要重新分配一块更大的空间,元素移动,并释放原来的空间。

vector缺省使用alloc作为分配器。

注意:对vector的任意操作,如果引起空间的重新配置,那么指向原来vector的迭代器就会失效,务必注意。


3.关联容器

所谓关联容器,就是每个数据元素是key+value,当元素插入到关联容器时,根据key,以某种特定规则将元素放置于适当位置,关联容器没有所谓的头尾,不能进行push_back(), push_front(), pop_back(), pop_front()等操作 

通常关联容器的内部结构是一个balanced binary tree(平衡二叉树),为的是高效率的操作(lgN),平衡二叉树有很多种:AVL-tree、RB-tree等,STL中的map, set, multimap, multiset内部采用RB-tree实现。

值得注意的是:我们在讨论红黑树之前,首先要记住:红黑树是一个二叉查找树,具备二叉查找树的所有性质

除了二叉查找树的性质,红黑树还有的五个基本性质:

  1. 每个节点要么为红要么为黑
  2. 根节点为黑
  3. 叶子节点黑
  4. 如果一个节点为红,那么其子节点必须为黑
  5. 从根节点到叶子节点的每条路径上的黑节点数相同,即黑高相同

当在对红黑树进行插入和删除等操作时,对树做了修改可能会破坏红黑树的性质。为了继续保持红黑树的性质,可以通过对结点进行重新着色,以及对树进行相关的旋转操作,即通过修改树中某些结点的颜色及指针结构,来达到对红黑树进行插入或删除结点等操作后继续保持它的性质或平衡的目的。

红黑树的调整的大致思想分两步:

  • 首先,将一个节点插入获删除,需要保证二叉查找树的性质
  • 其次,为了保证平衡性,就需要左旋或者右旋转,保证黑高相同
  • 最后,当一个节点已经插入或者删除以后,还需要修改节点的红黑性来保持红黑树的基本性质,需要看父节点的红黑性
4.set和map
set中所有元素会根据key进行排序,注意不能通过迭代器更改set的key,当用户对set中的元素进行insert获erase后,迭代器不会失效(是由其内部结构决定的)
map的元素是pair类型(#include <utility>),key+val,并根据key进行自动排序,map与set一样,不能通过迭代器改变pair中的key值,因为map元素的key关系到map元素的排列规则,任意改变key会严重破坏map的组织。但科一改变value的值。
当然map在执行insert和erase后,迭代器不会失效

5.List
每一个设计过list的人都知道,list本身和list的节点是不同的结构,需要分开设计。
STL list 是一个双向链表,迭代器必须具备前移、后移的能力。
list是一个环状双向链表,所以它只需要一个指针,便科一完整表现整个链表
list有一个重要性质:insert和接合操作都不会造成原有的list迭代器失效,删除一个迭代器不影响其他迭代器。
list类中只有一个指针node,有一个空白节点,如果让node指向刻意置于尾端的一个空白节点,node便能符合STL对于“前闭后开”区间的要求,成为last迭代器
<span style="font-family:Microsoft YaHei;font-size:14px;">iterator begin() {
    return (link_type)(*node).next;
}
iterator end() {
    return node;
}</span>
list 缺省使用alloc作为空间配置器,并据此另外定义一个list_node_allocator,为的是方便地以节点大小为配置单位。
list的操作很多
clear()清除所有节点,insert(pos, value)插入节点, push_front(x)插入一个节点作为头节点,push_back()插入一个节点作为尾节点,erase(itr)删除一个位置itr的节点。pop_front()移除头节点,pop_back()移除尾节点
remove(value):将数值为value的所有元素移除
unique();移除数值相同的连续元素,注意,只有“连续而相同的元素”,才会被移除剩下一个
splice(const_iterator pos, list &other): splice(const_iterator pos, list &other, const_iterator first, const_iterator last);
将某连续范围的元素从一个list移动到另一个list的某个定点。
merge(list &other):将x合并到*this身上,两个lists的内容都必须先经过递增排序
reverse():将*this的内容逆向重置
sort():list不能使用STL算法sort(),必须使用自己的sort()
sort()使用quick sort实现,如何实现?

6.deque

deque是一种双向开口的连续线性空间,即可以在头尾两端分别做元素的插入和删除操作。vector虽然也可以进行pop_front, push_front,但是其效率非常低。

vector与deque的差别:1.deque允许o(1)时间对头进行插入和移除;2.deque没有所谓容量(capacity)观念,因为它是动态地以分段连续空间组合而成,随时可以增加一些新的空间并链接起来。

deque由一段一段的定量连续空间构成,一旦有必要在deque的前端或尾端增加新空间、便配置一段定量连续空间,串接在整个deque的头端或尾端。

deque采用一块所谓的map(注意:不是STL的map容器)作为主控,称为中控器,这里所谓的map是一块连续空间,其中每个元素都是指针,指向一段连续线性空间,称为缓冲区,缓冲区才是deque的存储空间主体。
deque是分段连续空间,维持其“整体连续”假象的任务,落在了迭代器的operator++和operator--两个运算身上。



猜你喜欢

转载自blog.csdn.net/yyf_it/article/details/52292257