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中拨出。
- allocate()函数 ,该函数,首先判断区块大小,大于128,嗯,你懂得,小于的话,就看对应的free-list。如果free-list 内有能用的区块,就直接拿来用,没有的话,将区块上调至8倍数边界,然后调用refill()函数,准备为free-list重新填充空间。
- dellocate()函数,同样的,首先判断区块大小,大于128,小于128,就找到对应的free-list ,将区块回收。
- 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}
}