STL-源码剖析 简单总结

STL 六大组件 功能与运用

1. 容器

各种数据结构 ,vector ,list ,deque,set,map,从实现角度来看,STL容器是一种class template

2. 算法

各种常用算法,sort,search,copy,erase,从实现角度来看,STL容器是一种function template

3. 迭代器

扮演容器与算法之间的胶合剂,泛型指针,有五种类型,从实现角度来说,就是把operator*,operator->,opreator++,opreator–等指针相关操作予以重载的class template ,每个STL容器都附带有自己专属的迭代器。原生指针也是一种迭代器。

4. 仿函数

什么是仿函数?
行为类似函数。 好好体会下这句话

5. 配接器(adapters)

顾名思义,想想配接器模式(adapter design)

6. 配置器(allocators)

负责空间配置与管理,实现了动态空间配置,空间管理,空间释放。


空间配置器(allocator)

在这里插入图片描述在这里插入图片描述
SGI设计了双层级配置器(面试会被问道),主要是为了解决小型区块所可能造成的内存破碎问题。
SGI第一级配置器通过 malloc和 free 完成内存的配置与释放。第二级配置器则通过不同的策略来进行操作:首先,看配置区块的大小,看其是否超过128bytes,是的话,给第一级配置器,当配置器区块小于128bytes时,采用memory pool 整理方式。
那么,究竟只用第一级就OK,还是同时都用呢,要看__USE_MALLOC 这个参数是否被定义。(侯捷老师这里注:说这个参数名字并不理想,因为无论如何malloc总是要被调用的,对吧)。具体过程看下图。
在这里插入图片描述


下面逐个讲解第一级配置器和第二级配置器

第一级配置器

实现类似C++ new-handler 的机制,不能直接运用该机制,是因为他并非使用operator new 来配置内存。

那么什么是new-handler 机制呢,可以要求系统在内存配置需求无法被满足的时候,调用一个你指定的函数,换句话说,当内存配置不好使的时候,你可以让系统执行你指定的函数。

也就是 :: operator new 没有完成任务,在丢出std::bad_alloc 异常状态之前,会先调用有客户端也就是你指定的处理例程,处理例程通常叫做new-handler。

侯老师在这里多嘱咐了几句:说设计和设定“内存不足处理例程”是客户端的责任,有特定的套路

第二级配置器:
小于128bytes时,以内存池管理(memory pool),该方法又称为(sub-allocation);每次配置一大块内存,并维护对应之自由链表(free-list)。
有16个free-lists,各自管理不同大小的小额区块,例如8,16,32,
每次配置一大块内存,并维护对应的free list,若下次还是相同大小的内存需求,直接从free-list中拨出。

  1. allocate()函数 ,该函数,首先判断区块大小,大于128,嗯,你懂得,小于的话,就看对应的free-list。如果free-list 内有能用的区块,就直接拿来用,没有的话,将区块上调至8倍数边界,然后调用refill()函数,准备为free-list重新填充空间。
  2. dellocate()函数,同样的,首先判断区块大小,大于128,小于128,就找到对应的free-list ,将区块回收。
  3. chunk_alloc() 函数,从内存池中取空间给free-list,首先会判断水量够不够,够的话,就取出20个区块给free-list,不够20个区块,还可以供应一个以上的话,就把这些区块拨出去,问题来了,假如一个都不够的话,该怎么办,对客户端显然无法交代,对不对,通过malloc()从heap中配置内存,为池子里注入新的水以供使用。新水量为需求的两倍,再加上一个随着配置次数增加而越来越大的附加量。
    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述

内存池(面试题)


迭代器(iterators)概念与traits编程技法

STL 的中心思想在于:将数据容器和算法分开,彼此独立设计,最后再以一贴胶水将两者联系在一起。

迭代器是一种smart pointer

序列式容器

vector

vector 与array 的区别是什么呢?
array 是静态空间,一旦配置了就不能改变,vector是动态空间。vector数据空间是连续线性空间,为了降低空间配置是的速度成本,vector实际配置的大小可能比客户端需求量更大一些。

vector 动态增加大小,并不是在原来的空间之后接续新空间(因为无法保证原始空间后面是不是还有可以分配的空间),而是以原大小的两倍另外配置一块较大的空间,然后将原内容拷贝过来,然后才开始在原内容之后构造新元素,并释放原始空间。

vector 为单向数组,只能pushback 和pop back
在这里插入图片描述

腾讯面试题:通过三个迭代器:start,finish,end_of_storage 完成内存分配。


list

list 有个一个prev 和next ,所以为双向链表,而且还是环装双向链表


deque

deque是一个双向开口的连续线性空间。可以在头尾两端分别做元素的插入和删除操作。

deque 的 迭代器的比较复杂相比于vector
因此,除非必要,我们应尽可能选择使用vector,而非deque,例如,对deuqe进行排序操作,为了高效,可以将deque先完整复制到一个vector,将vector排序后(利用STL sort 算法),在复制回deque。

deque 数据结构 采用所谓map 作为主控,map是一小块连续空间,其中每个元素(此处叫node,节点)都是指针,指向另一段(较大的)连续线性空间,称为缓冲区。缓冲区才是deque 的存储空间主体。我们可以指定缓冲区的大小。默认为512bytes。

在这里插入图片描述


stack

以某种既有容器作为底部结构,将其接口改变,让其满足“先进后出”的特性,形成一个stack,是很容易做到的。

在这里插入图片描述

queue

在这里插入图片描述

heap

在这里插入图片描述

priority_queue

在这里插入图片描述

slist

单向链表

关联式容器

RB-tree

在这里插入图片描述


set

底层数据结构 是RB-tree

面对关联式容器,应该使用其所提供的find函数来搜寻元素,效率更高

itel = iset.find(3);          //查找3这个元素 而不是用find(first,last,value) 

map

底层也是RB-tree 树 <key,value>

multiset & multimap

multiset 的特性以及用法和set一致的,唯一的区别在于,它允许键值重复,map和multimap 也是如此

hash_set & hash_map & hash_multiset & hash_multimap

底层都是hashtable

算法

int a[] = {0,1,2,3,4,5,6,6,6,7,8}


for_each(begin(),end(),display<int>());  // 可以用来打印  
adjacet_find(begin(),end());// 找到相邻元素值相等的第一个元素 ,这里为6
adjacet_find(begin(),end(),equal_to<int>());// 找到相邻元素值相等的第一个元素 ,这里为6
count(begin(),end(),6) ;// 数组中值为6的个数
count_if(begin(),end(),bind2nd(less<int>()),7); // 找出小于7的元素个数 
find_if(begin(),end(),bind2nd(greater<int>()),2)// 找出第一个大于2的元素的位置

注:假如未来有一天,我会在这里加上代码,记上常用的算法

仿函数(另名 函数对象 function objects)

//用户继承 binary_function,便可以取得该仿函数的各种相应的类型
eg:
template<class T>
struct plus :public binary_function<T,T,T>{
    T  opreator()(const T& x,const T& y) const {return x+y}
}

配接器

猜你喜欢

转载自blog.csdn.net/shaoye_csdn1/article/details/90481312