STL源码剖析(侯杰)——读书笔记

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也是一个独立容器,但不开放给外界使用。

二叉搜索树是一种特殊的二叉树,其具有如下性质:

  1. 若左子树不空,则左子树所有结点的值均小于它的根结点的值
    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

2. https://www.cnblogs.com/runnyu/p/5986709.html

3. https://blog.csdn.net/lsjseu/article/details/9351141

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值