STL源码剖析(侯杰)——读书笔记
1. STL概论
STL的设计思维:对象的耦合性极低,复用性极高,符合开发封闭原则的程序库。
STL的价值:
1.带给我们一套极具实用价值的零部件,以及一个整合的组织。
2.带给我们一个高层次的以泛型思维为基础的、系统化的、条理分明的“软件组件分类学”。
在STL接口之下,任何组件都有最大的独立性,并以所谓迭代器胶合起来,或以配接器互相配接,或以所谓仿函数动态选择某种策略。
stl是一套标准库,提高了代码的复用性。主要有容器、算法、迭代器、仿函数、适配器、空间配置器六部分组成。
2.空间配置器
在STL中,容器的定义中都带有一个模板参数,如vector
template <class T, class Alloc=alloc> //默认使用alloc空间配置器
class vector{...}
SGI标准空间配置器, std::allocator
SGI中定义了一个符合部分标准、名为allocator的配置器,但从不从不使用它。主要效率不佳,只是简单包装了下::operator new
和 ::operator delete
部分代码,说明此问题
template <class T>
inline T* allocate(ptrdiff_t, T*){
set_new_handler(0);
T* tmp = (T*)(::operator new((size_T)(size*sizeof(T*))));
if(tmp==0){
cerr<<"out of memory"<<endl;
exit(1);
}
return tmp;
}
template <class T>
inline void deallocate(T* buffer){
::operator delete(buffer);
}
template <class T>
class allocator{
//内部allocate和deallocate的实现采用上面的模板函数
};
SGI特殊的空间配置器, std::alloc
class Foo {...};
Foo* pf = new FOO(); //先配置空间、后构造对象
delete pf; //先对象析构、后释放空间
构造和析构 construct()和destroy()
核心代码
template <class T1, class T2>
inline void construct(T1* p, const T2& value){
new (p) T1(value); //placement new; 调用T1::T1(value);
}
// 第一个版本,接受一个指针
template <class T>
inline void destory(T* pointer){
pointer->~T();
}
// 第二个版本,接受了个迭代器, 根据元素的类型进行删除
template <class ForwardIterator, class T>
inline void destory(ForwardIterator first, ForwardIterator last, T*){
_destory_aux(first, last, value_type(first));
}
//数值类型有non-trivial destructor
template<class ForwardIterator>
inline void _destroy_aux(ForwardIterator first, ForwardIterator last, __false_type){
for(; first<last; ++first)
destroy(&*first);
}
//数值类型有trival destructor
template<class ForwardIterator>
inline void _destroy_aux(ForwardIterator first, ForwardIterator last, __true_type){
// 针对迭代器为char*和wchar*的特化版
inline void destroy(char*, char*){}
inline void destroy(wchar_t*, wchar_t*){}
}
空间配置与释放
设计理念
- 向system heap要求空间
- 考虑多线程我状态
- 考虑内存不足的应对措施
- 考虑过多“小型区块”可能造成的内存碎片问题
alloc定义了两级的空间配置器
第一级是对malloc/free简单的封装。 C++内存配置基本操作::operator new
,::operator delete
相当于C的malloc()
和free()
,SGI正是使用malloc()
和free()
完成空间配置的。
而为了解决小型区块可能造成的内存破碎问题,alloc采用了第二级空间配置器。第二级空间配置器在分配大块内存(大于128bytes)时,会直接调用第一级空间配置器,而分配小于128bytes的内存时,则使用内存池跟free_list进行内存分配/管理。
第一级空间配置器核心代码实现
template<int inst>
class _malloc_alloc_template {
private:
//以下函数处理内存不足的情况, oom:out of memory
static void *oom_malloc(size_t);
static void *oom_realloc(void *, size_t);
static void (* __malloc_alloc_oom_handler)();
public:
// 只是对malloc/free的简单封装
static void* allocate(size_t n)
{
void* res = malloc(n);
if (0 == res) res = oom_malloc(n);
return res;
}
static void* reallocate(void* p, size_t new_sz)
{
void* res = realloc(p, new_sz);
if (0 == res) res = oom_realloc(p, new_sz);
return res;
}
static void deallocate(void* p)
{
free(p);
}
// 用来设置内存不足时的处理函数 该函数参数跟返回值都是一个函数指针
// 一般会抛出异常/尝试回收内存
static void(*set_handler(void(*f)()))()
{
void(*old)() = __malloc_alloc_oom_handler;
__malloc_alloc_oom_handler = f;
return old;
}
private:
// 用来处理内存不足的情况
static void* oom_malloc(size_t n)
{
void(*my_handler)();
void* res;
for (;;)
{
my_handler = _oom_handler;
if (0 == my_handler) { return NULL; }
(*my_handler)();
if (res = malloc(n)) return res;
}
}
// 用来处理内存不足的情况
static void* oom_realloc(void* p, size_t n)
{
void(*my_handler)();
void* res;
for (;;)
{
my_handler = _oom_handler;
if (0 == my_handler) { return NULL; }
(*my_handler)();
if (res = reallocate(p, n)) return res;
}
}
// 由用户设置,在内存不足的时候进行处理,由上面两个函数调用
static void(*_oom_handler)();
};
// 处理函数默认为0
typedef _malloc_alloc_template<0> malloc_alloc;
第二级空间配置器
该配置器维护一个free_list,这是一个指针数组。节点结构如下
union obj{
union obj *free_list_link;
char client_date[1];
}
在分配内存的时候,补足8bytes的倍数,free_list数组中每个指针分别管理分配大小为8、16、24、32…128bytes的内存。
下图表示从二级空间配置器中分配内存时是如何维护free_list的(建议参考下面源码阅读)。
开始所有指针都为0,没有可分配的区块时(就是free_list[i]==0)会从内存池中分配内存(默认分配20个区块)插入到free_list[i]中。
然后改变free_list[i]的指向,指向下一个区块(free_list_link指向下一个区块,如果没有则为0)。
二级空间配置器中回收内存
enum { _ALIGN = 8 }; // 对齐
enum { _MAX_BYTES = 128 }; // 区块大小上限
enum { _NFREELISTS = _MAX_BYTES / _ALIGN }; // free-list个数
class default_alloc {
private:
// 将bytes上调到8的倍数
static size_t ROUND_UP(size_t bytes)
{
return (bytes + _ALIGN - 1) & ~(_ALIGN - 1);
}
private:
union obj {
union obj* free_list_link;
char client_data[1];
};
private:
// 16个free-lists 各自管理分别为8,16,24...的小额区块
static obj* free_list[_NFREELISTS];
// 根据区块大小,决定使用第n号free-list
static size_t FREELIST_INDEX(size_t bytes)
{
return (bytes + _ALIGN - 1) / _ALIGN - 1;
}
// 分配内存,返回一个大小为n的区块,可能将大小为n的其他区块加入到free_list
static void* refill(size_t n)
{
// 默认分配20个区块
int nobjs = 20;
char* chunk = chunk_alloc(n, nobjs);
obj** my_free_list;
obj* result, *current_obj, *next_obj;
// 如果只分配了一个区块,直接返回
if (1 == nobjs) return chunk;
// 否则将其他区块插入到free list
my_free_list = free_list + FREELIST_INDEX(n);
result = (obj*)chunk;
// 第一个区块返回 后面的区块插入到free list
*my_free_list = next_obj = (obj*)(chunk + n);
for (int i = 1;; ++i)
{
current_obj = next_obj;
next_obj = (obj*)((char*)next_obj + n);
// 最后一个next的free_list_link为0
if (nobjs - 1 == i)
{
current_obj->free_list_link = 0;
break;
}
current_obj->free_list_link = next_obj;
}
return result;
}
// 分配内存
// 在内存池容量足够时,只调整start_free跟end_free指针
// 在内存池容量不足时,调用malloc分配内存(2 * size * nobjs + ROUND_UP(heap_size >> 4),每次调整heap_size += 本次分配内存的大小)
static char* chunk_alloc(size_t size, int& nobjs);
static char* start_free; //内存池的起始位置
static char* end_free; //内存池的结束位置
static size_t heap_size; //分配内存时的附加量
public:
static void* allocate(size_t n)
{
obj** my_free_list;
obj* result;
// 大于128就调用第一级空间配置器
if (n > (size_t)_MAX_BYTES)
return base_alloc::allocate(n);
// 寻找适当的free-list
my_free_list = free_list + FREELIST_INDEX(n);
result = *my_free_list;
// 没有可用的free list
if (result == 0)
return refill(ROUND_UP(n));
// 调整free list 移除free list
*my_free_list = result->free_list_link;
return result;
}
static void deallocate(void* p, size_t n)
{
obj* q = (obj*)p;
obj** my_free_list;
// 大于128就调用第一级空间配置器
if (n > (size_t)_MAX_BYTES)
{
base_alloc::deallocate(p);
return;
}
// 寻找对应的free list
my_free_list = free_list + FREELIST_INDEX(n);
// 调整free list 回收区块(将区块插入到my_free_list)
q->free_list_link = *my_free_list;
*my_free_list = q;
}
static void reallocate(void* p, size_t old_sz, size_t new_sz);
};
char* default_alloc::start_free = 0;
char* default_alloc::end_free = 0;
size_t default_alloc::heap_size = 0;
default_alloc::obj* default_alloc::free_list[_NFREELISTS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
3.迭代器概念与traits编程
概念
迭代器是一种行为类似指针的对象,用于连接容器和算法。实现对于一个容器不必知道它的类型,直接获得它的迭代器就可以用于算法的执行。因此,在迭代器要传递所指对象类型或者可以获取到对象的类型。
迭代器相应型别传递
利用function template的参数推导机制
template <class I, class T>
void func_impl(I iter, T t){ // 在这里可以推导出*iter的类型。
T tmp;
};
template <class I>
inline void func(I iter){
func_impl(iter, *iter);
}
Traits编程技法——STL源码门钥
迭代器所指对象的型别,称为该迭代器的value type。
template参数推导机制推导的只是参数,无法推导返回值类型。声明内嵌型别可以解决这个问题
template <class T>
struct MyIter{
typedef T value_type; //内嵌型别声明
T* ptr;
MyIter(T* p=0):ptr(p) {}
T& operator*() const {return *ptr;}
};
template <class I>
typename I::value_type //这一行是返回值类型, typename指明这是一个类型
func(I ite){
return *ite;
}
MyIter<int> ite(new int(8));
cout<<func(ite);
Partial Specialization(偏特化)的意义
原生指针不是class,无法定义内嵌型别,因此这里引入偏特化。主要意思是:class template有一个以上的template参数时,可以针对其中若干个template参数进行特化处理。
使用class template来“萃取”迭代器的特性
template <class T>
struct iterator_traits{
//创建类型别名、指定时类型、类型别名
typedef typename I::value_type value_type;
};
也就是说,如果I定义了自己的value_type
,先前的func
可以改写成如下
template <class I>
typename iterator_traits<I>::value_type // 函数返回值
func(I ite) { return *ite; }
在带来了一层间接性外,偏特化也带来了traits可以拥有特化版本的好处。
template <class T>
struct iterator_traits<T*>{ // 偏特化版 -- 迭代器是原生指针
typedef T value_type;
};
//对于迭代器是const T*类型的偏特化,萃取类型为T
template <class T>
struct iterator_traits<const T*>{
typedef T value_type;
};
最常用到的迭代器相应类别有五种:value_type
, difference type
, pointer
, reference
, iterator categoly
。为了使容器可以融入STL中,一定要为容器的迭代器定义着五种相应类型。“特性萃取机”traits会把这些特性萃取出来。
template <class I>
struct iterator_traits{
typedef typename I::iterator_category iterator_category;
typedef typename I::value_type value_type;
typedef typename I::difference_type difference_type;
typedef typename I::pointer pointer;
typedef typename I::reference reference;
}
// 针对原生指针设计的特化版本
template <class T>
struct iterator_traits<T*>{
typedef random_access_iterator_tag iterator_category;
typedef T value_type;
typedef ptrdiff_t difference_type;
typedef T* pointer;
typedef T& reference;
}
// 针对原生const指针设计的特化版本
template <class T>
struct iterator_traits<const T*>{
typedef random_access_iterator_tag iterator_category;
typedef const T value_type;
typedef ptrdiff_t difference_type;
typedef const T* pointer;
typedef const T& reference;
}
-
value type
迭代器所指向的对象型别
-
difference type
表示两个迭代器之间的距离,STL中的count()返回值就是difference type
-
reference type
引用
-
pointer type
指针
-
iterator_category
std::iterator的保证
为了符合规范,任何迭代器都应该提供五个内嵌相应型别,以利于traits萃取,否则无法与STL其他组件搭配。STL提供一个iterators class,新设计的迭代器可以继承它,可以保证符合STL的规范。
template <class Category,
class T, class Distance=ptrdiff_t, class Point = T*,class Reference=&T>
struct iterator{
typedef Category iterator_category;
typedef T value_type;
typedef Distance difference_type;
typedef Pointer pointer;
typedef Reference reference;
};
个人小结:本章主要介绍如何通过迭代器使用容器中的数据执行算法。迭代器对容器常用的操作有指针、引用、递增、是否相等等操作,需要重载operator*
,operator->
,operator++
,operator==
,operator!=
,operator++(int)
。在使用迭代器去“撮合”算法和容器时,需要知道容器对象的类型。由此,首先引入了function template参数推导机制,该方法解决了函数形参的推导,但是无法推导出函数的返回值类型。接着有介绍了class的内嵌类型声明,在类(或结构体)中通过typedef T value_type
取得参数类型,然后让返回值类型为typename T::value_type
获得T的参数类型,但这种方法只在class对象中有效,在原生指针中无法定义内嵌类型。此时,本章最重要的traits特性萃取机就闪亮登场了。在iterator_traits中typedef重命名I中的对象类型,对原生指针特化的iterator_traits中重命名其对象类型,名称保持一致。这样实现了获取任何迭代器所指对象的类型。
4.序列式容器
vector
vector的数据定义
template <class T, class Alloc=alloc>
class vector{
private:
//vector的嵌套型别定义
typedef T value_type;
typedef value_type* pointer;
typedef value_type* iterator;
typedef value_type& reference;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
protect:
// simple_alloc是SGI STL默认的空间配置器
typedef simple_alloc<value_type, Alloc> data_allocator;
iterator start; // 表示目前使用空间的头
iterator finish; // 表示目前使用空间的尾
iterator end_of_storage; // 表示目前可用空间的尾
};
vector的数据结构采用线性连续空间,通过两个迭代器start, finish分别指向配置空间中已被使用的范围,end_of_storage指向整块连续空间的尾端。
一个vector的容量永远大于等于其大小,当vector空间不够用时,容器的扩张必须经过“重新配置空间、元素移动、释放原空间“等过程。扩充空间的事件成本比较高,为避免多次扩充,我们会将容量扩充两倍。如果两倍还不够用,就扩充更大的容量。
vector提供的接口:包括得到vector的属性接口、vector的操作接口以及构造函数:
(1)构造函数:vector()、vector(size_type n、const T& value)、vector(size_type n);
(2)属性函数:begin、end、size、capacity、empty、operator[]、front和back
(3)操作函数:push_back()、pop_back()、erase()、resize()、clear()。
list
每次插入或删除都需要配置或释放一个元素空间,数据空间不连续
list提供的接口
list提供的接口:包括得到list的属性接口、list的操作接口以及构造函数:
(1)构造函数:list()、list(size_type n、const T& value)、list(size_type n)。
(2)属性函数:begin、end、empty、size、front和back。
(3)操作函数:push_back()、pop_back()、push_front、pop_front、erase()、resize()、clear()、unique、splice、merge、reverse、sort、insert。
list的数据结构
template<class T>
struct __list_node{
typedef void* void_pointer;
void_pointer prev;
void_pointer next;
T data;
};
list迭代器设计
template<class T,class Ref,class Ptr>
struct __list_iterator{
typedef __list_iterato<T,T&,T*> iterator;
typedef __list_iterato<T,Ref,Ptr> self;
typedef bidirectional_iterator_tag iterator_category;//双向迭代器
tyepdef __list_node<T>* link_type;
link_type node;//包含了一个指向__list_node节点
.....
};
deque
双向开口的连续线性空间,可以在常数时间内对头尾两端分别插入和删除元素。
deque是由一段一段的定量连续空间构成,一旦有必要在deque的首端或尾端增加新空间,便配置一段定量的连续空间,串接在整个deque的头端或尾端,deque的最大任务就是维护其整体连续的假象,并提供随机存取的接口。避开”重新配置、复制、释放“的轮回,代价是复杂的迭代器架构。
deque采用一块所谓的map(不是STL的map容器)作为主控。这里的map是一小块连续空间,其中每个元素(称为一个节点node)都是指针,指向另一段连续的线性空间(称为缓冲区),缓冲区才是duque存储空间主体。
deque的iterator设计
template <class T, class Ref, class Ptr, size_t BUfSiz>
struct __deque_iterator{
typedef __deque_iterator<T, T&, T*, BugSiz> iterator;
typedef __deque_iterator<T, const T&, const T*, BugSiz> const_iterator;
static size_t buffer_size() {return __deque_buf_size(BufSiz, sizeof(T));}
//为继承std::iterator,需要自定义五个必要的迭代器类型
typedef random_access_iterator_tag iterator_category;
typedef T value_type;
typedef Ptr pointer;
typedef Ref reference;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef T** map_pointer;
typedef __deque_iterator self;
//保持于容器的联结
T* cur; //迭代器所指缓冲区的现行元素
T* first; //迭代器所指缓冲区的头
T* last; //迭代器所指缓冲区的尾
map_pointer node; // 指向管控中心
};
deque的数据结构
template<class T,class Alloc = alloc,size_t BufSize=0>
class deque{
public:
typedef T value_type;
typedef T* pointer;
typedef __deque_iterator<T,T&,T*,BufSiz> iterator;
protected:
typedef pointer* map_pointer;
iterator start;
iterator finish;
map_pointer map;
size_type map_size;
}
deque提供的接口
deque提供的接口:包括得到deque的属性接口、deque的操作接口以及构造函数:
(1)构造函数:deque()、deque(size_type n、const T& value)、deque(size_type n)。
(2)属性函数:begin、end、size、empty、maxsize()、operator[]、front和back。
(3)操作函数:push_back()、pop_back()、push_front、pop_front、erase()、resize()、clear()、insert。
stack
stack是一种先进后出的数据结构。stack只允许新增元素、移除元素、取得栈顶元素。stack以deque为底部结构并封闭其头端开口,便轻而易举形成一个stack。SGI STL默认以deque作为stack底部结构。stack是一个适配器, stack不允许有任何遍历行为。(stack没有迭代器)
1.stack提供的接口
stack提供的接口:包括得到stack的属性接口、stack的操作接口以及构造函数:
(1)构造函数:stack()。
(2)属性函数:size、empty、top。
(3)操作函数:push、pop。
template<class T,class sequece=deque<T> >
class stack{
protected:
Sequence c;//所有的接口转到调用C的接口但是只操作一端
}
queue
queue是一种先进后出(FIFO)的数据结构。它有两个出口,形式如图所示。stack允许新增元素、从底端移除元素、取得最顶元素。但除了底端可以加入,最顶端可以取出外,没有其他任何方法可以存取queue的其他元素。换言之,queue不允许有任何遍历行为。(queue没有迭代器)
1.queue提供的接口
stack提供的接口:包括得到stack的属性接口、stack的操作接口以及构造函数:
(1)构造函数:queue()。
(2)属性函数:size、empty、front和back。
(3)操作函数:push、pop。
2.queue的数据结构
template<class T,class sequece=deque<T>>
class queue{
protected:
Sequence c;//这个跟上面不同的是两端不封死
}
heap
采用以vector保存的完全二叉,并可通过sift_up和sift_down进行堆调整。heap提供的接口:make_heap、sort_heap、push_heap、pop_heap
priority_queue
priority_queue有一个优先级的概念,默认采用max-heap。它是利用一个make_heap完成,而heap又是以vector呈现。priority_queue提供的接口:
构造函数:
priority_queue(InputIterator first,InputIterator last,const Compare &x)
priority_queue(InputIterator first,InputIterator last)
其他接口:size、empty、top、push和pop。
slist
slist是一种单链表结构,slist和list的差别:list提供的Bidirectional iterator迭代器,而slist提供的是Forward Iterator。slist和list共同具有的特色是他们的插入、移除、结合等操作并不会造成迭代器失效。插入操作会将新元素插入于指定位置之前,而非之后。这样slist每次插入都要从头遍历找到前一个节点。
1.slist提供的接口
slist提供的接口:包括得到slist的属性接口、slist的操作接口以及构造函数:
(1)构造函数:slist()。
(2)属性函数:begin、size、empty、front。
(3)操作函数:front、pop_front、push_front。
2.slist的数据结构
struct __slist_node_base{
__slist_node_base *next;//可以作为头节点
}
template<class T>
struct __slist_node:public __slist_node_base{
T data;
}
struct __slist_iterator_base{
typedef forward_iterator_tag iterator_category;
__slist_node_base *node;
...
}
template<class T,class Ref,class Ptr>
struct __slist_iterator: public __slist_iterator_base{
typedef __slist_iterator<T,T&,T*> iterator;
...
}
template<class T,class Alloc=alloc>
class slist{
typedef __slist_node<T> list_node;
typedef __slist_iterator<T,T&,T*> iterator;
...
}
5.关联式容器
标准STL关联式容器分set和map两大类,以及这两大类衍生multiset和multimap。容器的底层机制均以RB-tree完成。RB-tree也是一个独立容器,但不开放给外界使用。
二叉搜索树是一种特殊的二叉树,其具有如下性质:
- 若左子树不空,则左子树所有结点的值均小于它的根结点的值
2)若右子树不空,则右子树所有节点的值均大于它的根节点的值
3)左右子树也分别为二叉搜索树
二叉搜索树支持各种动态集合操作,包括:插入、查找、删除,其操作的时间复杂度与树的高度成正比,在遇到二叉树极端不平衡的情况下,其形状就与链表是一样的,二叉树插入、查找、删除的时间复杂度都退化为O(n)。
*平衡二叉搜索树是一种特殊的二叉搜索树,其没有一个节点深度过大,不同的平衡条件,造就不同的效率表现。常见的平衡二叉搜索树有:AVL-tree和RB-tree。
关联容器一般以平衡二叉搜索树作为内部数据结构,RB-tree的应用尤其广泛。
RB-tree是许多平衡二叉查找树的一种,一颗有n个内结点的红黑树的高度至多为2lg(n+1),它能保证在最坏情况下,基本的动态集合操作时间为O(lgn)。
set
set的特性是所有元素的键值自动被排序,set的不允许有两个相同的键值,其中的元素不能被改变,以RB-tree为底层机制
template <class Key, class Compare=less<Key>, class Alloc=alloc> //默认采用递增排序
class set{
public:
typedef Key key_type;
typedef Key value_type;
typedef Compare key_compare;
typedef Compare value_compare;
private:
template <class T> struct identity:public unary_function<T, T>{
const T& operator()(const T& x) const { return x; }
}
typedef rb_tree<key_type, value_type, identity<value_type>, key_compare, Alloc> rep_type;
rep_type t; //从用rb-tree来表现set
public:
typedef typename rep_type::const_pointer pointer;
typedef typename rep_type::const_pointer const_pointer;
typedef typename rep_type::const_reference reference;
typedef typename rep_type::const_reference const_reference;
typedef typename rep_type::const_iterator iterator; // set的迭代器无法执行写入操作
....
// set一定使用rb-tree的insert_unique(),multiset才使用insert_equal()
// set不允许相同键值存在, multiset允许相同键值存在
template <class InputIterator>
set(InputIterator first, InputIterator last):t(comp){
t.insert_unique(first, last);
}
};
set提供的常用接口
方法 | 作用 |
---|---|
erase(iterator) | 删除定位器iterator指向的值 |
erase(first,second) | 删除定位器first和second之间的值 |
erase(key_value) | 删除键值key_value的值 |
lower_bound(key_value) | 返回第一个大于等于key_value的定位器 |
upper_bound(key_value) | 返回最后一个大于key_value的定位器 |
count() | 用来查找set中某个某个键值出现的次数 |
begin() | 返回set容器的第一个元素 |
end() | 返回set容器的最后一个元素 |
clear() | 删除set容器中的所有的元素 |
empty() | 判断set容器是否为空 |
max_size() | 返回set容器可能包含的元素最大个数 |
size() | 返回当前set容器中的元素个数 |
rbegin | 返回的值和end()相同 |
rend() | 返回的值和rbegin()相同 |
map
map的特性是所有元素都会根据元素的键值自动被排序。map的元素由pair组成,同时拥有key和value。不允许由相同的key。
pair的定义
template <class T1, class T2>
struct pair{
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
pair() : first(T1()), second(T2()) {}
pair(const T1& a, const T2& b) : first(a), second(b){}
};
map实现核心代码
template <class Key, class T, class Compare=less<Key>, class Alloc=alloc> //默认采用递增排序
class map{
public:
typedef Key key_type; // 键值
typedef T data_type; // 实值
typedef T mapped_type;
typedef pair<const Key, T> value_type; //元素型别(键值/实值)
typedef Compare key_comapre; //键值比较函数
class value_compare: public binary_functioon<value_type, value_type, bool>{
friend class map<Key, T, Compare, Alloc>;
protected:
Compare comp;
value_compare(Comapre c):comp(c){}
public:
bool operator()(const value_type& x, const value_type& y) const{
return com(x.first, y.first);
}
};
private:
typedef rb_tree<key_type, value_type, selectlst<value_type>, key_compare, Alloc> rep_type;
rep_type t; //使用rb_tree;
public:
typedef typename rep_type::pointer pointer;
typedef typename rep_type::const_pointer const_pointer;
typedef typename rep_type::reference reference;
typedef typename rep_type::const_reference const_reference;
typedef typename rep_type::iterator iterator; // map的iterator可以修改元素的值(value)
typedef typename rep_type::const_iterator const_iterator;
....
// map使用insert_unique插入
template <class InputIterator>
map(InputIterator first, InputIterator last, const Comapre& comp):t(comp){
t.insert_unique(first, last);
}
}
方法 | 作用 |
---|---|
begin() | 返回指向map头部的迭代器 |
clear() | 删除所有元素 |
count() | 返回指定元素出现的次数 |
empty() | 如果map为空则返回true |
end() | 返回指向map末尾的迭代器 |
equal_range() | 返回特殊条目的迭代器对 |
erase() | 删除一个元素 |
find() | 查找一个元素 |
insert() | 插入元素 |
key_comp() | 返回比较元素key的函数 |
lower_bound() | 返回键值>=给定元素的第一个位置 |
max_size() | 返回可以容纳的最大元素个数 |
rbegin() | 返回一个指向map尾部的逆向迭代器 |
rend() | 返回一个指向map头部的逆向迭代器 |
size() | 返回map中元素的个数 |
swap() | 交换两个map |
upper_bound() | 返回键值>给定元素的第一个位置 |
value_comp() | 返回比较元素value的函数 |
multiset/multimap
于set/map唯一差别在于插入时使用insert_equal()
hashtable
使用hash function
将元素映射的某一位置上,但这无法避免的会产生碰撞解决方法有线性探测、二次探测、开链等。
线性探测
使用hash function
计算出某个元素的插入位置,如何该位置上的空间不可用时,继续向下寻找可用空间。
存在的问题:可能过去的元素集中在某一区域,导致需要不断的解决碰撞问题。
二次探测
采用F(i)=i^2映射函数,当发生碰撞时,每次向下寻找第1、4、9、16…位置上的空间是否可用
开链
每一个表格元素维护一个list,hash function
会分配某一个list
hashtable的数据结构
template <class Value, class Key,
class HashFcn, //hash function的函数类别
class ExtractKey, //取出键值的方法(函数或仿函数)
class EqualKey, //判断键值是否相同的方法(函数或仿函数)
class Alloc> // Alloc默认使用alloc
class hashtale{
public:
typedef HashFcn hasher;
typedef EqualKey key_value;
typedef size_t size_type;
private:
hasher hash;
key_value equals;
ExtractKey get_key;
typedef __hashtable_node<Value> node;
typedef Simple_alloc<node, Alloc> node_allocator;
vector<node*, Alloc> buckets; // 以vector表示
size_type num_elements;
public:
// bucket个数代表bucket vector的大小
size_type bucket_count() const { return bucket.size(); }
...
};
hash_map/hash_set/hash_multiset/hash_multimap
均采用hashtable为底层机制,它们的使用方式与采用br-tree的map/set类似。
7.仿函数/函数对象
unary_function
定义一元函数的参数类型和返回值类型,任何Adaptation Unary Function都应该继承此类别
template <class Arg, class Result>
struct unary_function{
typedef Arg argument_type;
typedef Result result_type;
};
// nagete继承unary_function
template <class T>
struct negate:public unary_function<T, T>{
T operator()(const T& x) const { return -x; }
};
binary_function
定义二元函数的第一参数类型、第二参数类型,以及返回值类型
template <class Arg1, class Arg2, class Result>
struct binary_function{
typedef Arg1 first_argument_type;
typedef Arg2 second_argumet_type;
typedef Result result_type;
};
// 以下仿函数继承binary_function
template <class T>
struct plus: public binary_function<T, T, T>{
T operator()(const T& x, const T& y) const {return x+y;}
};
8.配接器
STL各种配接器中改变仿函数接口者称为function adapter
, 改变容器接口者称为container adapter
,改变迭代器接口者称为iterator adapter
。
容器配接器
queue, stack
迭代器配接器
insert iterators
reverse iterators
iostream iterators
back_inserter(Container& x)
尾端插入
front_inserter(Constainer& x)
头端插入
inserter(Container& x, Iterator i)
在i后插入
仿函数配接器
not1(bind2nd(less<int>, 12)); //不小于12的元素
// 表示f(g(x)) , f=x*3, g = y+2
compose1(bind2nd(multiplies<int>(), 3), bind2nd(plus<int>(), 2))
Reference:
1. https://www.cnblogs.com/xxiaoye/p/3950771.html