STL源码剖析阅读笔记--线性容器(1)

好久好久么写了,看的书不少却很难落实记录下来,难得浮生半日闲,实验室项目暂告一段,最近阿里和中行面试终于基本完了,终于可以再捧起侯捷大神的STL,Mark一下,督促自己多书多记录,希望2019自己能在项目,毕设和找工作之间游刃有余。

语法两则:

语法:

1、临时对象的产生—一种无名对象,不在程序预期之下(passer by value会产生临时对象,负担)

有时刻意制造临时对象,程序更加清楚----在类型后面加()且可指定初值,int(8),shape(3,5)

仿函数中的应用:

Template<typename T>

Class print

{

       public:void operator()(const T&element) const

{

       Cout<<element<<””;

}

}

for_each(iv.begin(),iv.end(),print<int>());    //请求的临时对象

2、静态常量整数成员在class内部可直接初始化

Class{

       Static const int a = 10;

       Static const long b = 20;

       Static const char c = ‘c’;

}

线性容器:

空间配置器

SGI标准空间配置器 std::allocator 仅仅是对new和delete的包装而已;效率低,不用。

SGI STL 每个容器的缺省配置为alloc

new ->operator new配置内存->构造函数

delete->调用析构函数->delete释放内存

 

内存配置器:

向heap申请空间,

考虑多线程状态,

考虑内存不足的应变措施,

考虑过多的小型区块可能造成的内存碎片问题

为了解决内存碎片问题alloc祭出双层配置器:

一级配置器:allocateà调用mallocà调用不成功调用oom_malloc

                     Realloc->调用realloc->调用不成功调用oom_realloc

二级配置器:(好像有点类似于linux内存分配的伙伴系统)

小额区块导致内存碎片,且配置时需要额外负担(需要一部分内存交给系统进行管理,交税,内存越小越浪费)。小于128字节则以内存池管理,维护与之对应的自由链表(free_list)

。分配内存则从freelist中找,释放小额内存则回收。

内存池维护16个free_list 8 16 24……128字节,为了方便管理内存,将内存碎片调整到8的倍数。内存节点使用共用体,当内存没有分配时,节点填充有指向节点的指针,维持其在链表中的位置,当节点分配后,填充客户数据,降低指针对内存的浪费。

allocate:

分配空间大于128字节则调用一级配置器,否则检查对应的free_list,如果有则拿来用,没有则调用refill为free_list蓄水。

 

refill chunk_alloc 内存池:

当free_list没有内存时,refill在内存池取多个(默认20)的相应内存给链表,其底层调用chunk_alloc函数,chunk_alloc以end_free – start_free判断内存池水位,水量充足则调出20个内存单位给free_list,其中一个给用户,19个给free_list维护。如果不足,但够一个内存单元则分配数目以引用的参数返回,如果内存池不够一个内存单元则malloc从heap中配置,引入新的活水,新水量为需求量的2倍(40)+附加量(正相关配置次数)。

deallocate():

       该函数先判断区块是否大于128字节,是则交给一级配置器去回收,否则搜索对应的free_list进行回收。

 

Vector

Vector的迭代器就是普通的指针。

空间配置器:alloc 动态内存分配,扩容会以原来两倍的空间拷贝赋值,原空间释放,。连续空间,任何引起内存重新配置操作到会导致迭代器失效。

 

erase pop_back等删除函数只能改变size不能改变capacity 根据源代码很容易推断出vector的capacity只增不减。

clear ->调用erase也只是改变size

Insert若空间不足会2倍扩容,迭代器失效。

list

底层是双向环状链表,一个指针即可完成所有操作,如下图,刻意增加一个空节点(尾后节点),便于头尾操作,其迭代器是封装节点类型的指针,重载* -> ++ --等操作符。配置器使用alloc作为配置器,封装了一个list_node_allocator便于节点的配置。由于链表的数据结构特性,除了操作的迭代器,其他迭代器不会失效。

 

List的成员函数操作:

Insert->返回迭代器在插入哨兵元素迭代器之前。

       push_back push_front调用insert

erase->删除哨兵节点之后的节点

       pop_front pop_back调用erase   remove进行循环调用erase;

clear->以尾后节点开始,尾后节点结束进行循环删除;最后置node为空节点。

Protected方法:transfer(position,first,last)将同类(一)链表的某一段移到链表的某个位置前面。

List.splice(iter,list2) 将list2拼接到iter之前à调用内部方法transfer

List.merge(list2)  合并有序链表->在某一链表结束后transfer剩余节点

Reverse()->调用transfer

Sort算法,由于STL算法里的sort必须支持Random Access Iterator所以只能用成员函数list.sort();

deque:

允许双端插入,常数时间,vector头插效率奇差(大量移动);dequeue没有容量概念,提供随机访问,其迭代器设计比较复杂,运算效率差,如对于sort算法先拷贝到vector在sort在拷贝回来。能不用尽量少用。

 

Deque的数据结构:一个中控器存储指针,各个指针指向各个buffer(默认512字节)。即一个个连续的小空间被中控器连接起,相比于vector扩容避免了复制等操作高效,但是迭代器很复杂,内存维护也复杂。

迭代器:迭代器需要维护关于当前buffer的指针,first,last,cur和所在buffer在中控器中的位置node。

++操作时,3判定cur==last,否则cur++,否则,set_node下一个节点,cur指向下一个缓冲头位置。--操作类似。

push_back和push_front:

map会维护start和finish两个迭代器,表示node的范围,create_map_and_node进行deque结构安排时,会根据元素个数和缓冲区大小分配node个数,num_nodes num_elements/buffer_size() + 1,多分配一个node。map的start和finish也会放在map的中间,便于两边的插入。

当缓冲区不够时进行map扩容,map扩容不需要复制拷贝,在原来的基础上进行扩充,需要复杂的迭代器操作。

 

成员函数:

pop_back():最后缓冲区有元素则cur指针前移,析构后面。没有则释放最后一个缓冲区,调整finish状态到前一个缓冲区最后一个位置,析构该元素。

pop_front():类似。

clear():将缓冲区析构释放掉,留一个缓冲区(初始状态)。

erase(position): 首先判断当前位置,if(index > size>>1)若在缓冲区前半段,将前面元素进行移位操作,将最前面冗余的元素去掉。否则后面的元素移动,去除最后的冗余元素。

Insert(position,value):在position之前插入value。若position.cur == start.cur即最前端则调用push_front,若在最后端则调用push_back。否则交给insert_aux操作,关于insert_aux也是首先判断index>size()/2,若前面元素少,则在最前端加入一个与第一元素相同的元素,然后向前逐个移动,否则在最后加一个,向后逐个移动。

stack:

适配器:默认以deque为底层数据结构,双向端口封闭头端,不能遍历,没有迭代器。

       Stack<int,list<int>>istack;  以list为底层容器,双向端口封闭头端。一般不用vector,扩容效率太差。

queue

适配器:默认以deque为底层数据结构,不能遍历,没有迭代器。

Queue<int,list<int>>iqueue;以list为底层容器。一般不用vector,扩容效率差。

heap

二叉堆,完全二叉树,默认为大顶堆。以连续空间表述,父子关系为i/2 2i 2i+1;所以底层一般用vector进行实现(可动态改变大小且连续空间)。

算法里面有make_heap push_heap pop_heap sort_heap 等STL组件有priority_queue实现,入队以任何顺序进入,出队以权值大小出队。Heap不提供迭代器,不支持遍历。

push_heap(first,last)算法:末端加空节点,以50比较,向上浮动,浮不动填入即可。被调用时,新元素必须在底层vector的尾端,所以v.push_back() + push_heap(begin,end)。

pop_heap算法:堆顶置空,用68和子节点较大的比较,将空节点进行下沉,沉到底为止。

调用时弹出原素在底层vector的尾端,并未删除,所以pop_heap(begin,end)+v.pop_back()。

 

make_heap(first,last):将一段现有的数据转化为heap顺序。

sort_heap(first,last):将一个堆的数据进行排序(持续调用调用pop_heap将最大的放到vector最后面,弹完所有元素则vector已经排好序),破坏堆的特性。

priority_queue:优先级队列,默认Maxheap 底层封装vector+push_heap+pop_heap算法,适配器,没有迭代器,不能遍历。

priority_queue<int>ipq(first,last);ipq.top();ipq.pop();ipq.push(val);

猜你喜欢

转载自blog.csdn.net/WSTONECH/article/details/90144804