STL源码剖析笔记(序列式容器)

Vector

  • vector的迭代器

vector 提供的是Random Access Iterators ,迭代器具备的操作有operator*,operator->,operator++,operator–,operator+, operator-,
operator+=,operator-=

  • vector的数据结构
    vector 使用线性连续空间, start finish迭代器指向连续空间中使用的范围,end_of_storage迭代器指向整块连续空间的尾端 。vector实际配置的空间 ->容量(capacity) 大于等于使用的范围 ->size ,以便扩充。

STL源码剖析笔记(序列式容器)_第1张图片

  • vector内存构建与管理
    vector预设使用alloc做为空间配置器,并通过data_allocator配置空间
template T, classAlloc = alloc> 
class vector { 
    //...
    protected: 
     typedef simple_allocdata_allocator;  
}; 

配置n个元素的空间

data_allocator::allocate(n)

当在vector安插新元素时,如果capacity足够大,直接挑中finish迭代器完成安插,
否则则扩充从一块新空间

新空间并非从原来基础上扩充,而是配置一块新的较大的空间(大小为原来2倍),将原来内容拷贝过来。一旦引起新空间配置,原有迭代器将会失效。

  • vector部分元素操作

    1. erase
iterator erase(iterator position)  
    {  
        if (position + 1 != end())  
            copy(position + 1, finish, position);  
        --finish;  
        destroy(finish);  
        return position;  
    }  


    iterator erase(iterator first, iterator last)  
    {  
        iterator i = copy(last, finish, first);  
        // 析构掉需要析构的元素  
        destroy(i, finish);  
        finish = finish - (last - first);  
        return first;  
    }  

其中copy的源码如下

template<class InputIterator, class OutputIterator>
  OutputIterator copy (InputIterator first, InputIterator last, OutputIterator result)
{
  while (first!=last) {
    *result = *first;
    ++result; ++first;
  }
  return result;
}

start- - -first- - - last - - - - - finish - - -end_of_storage

start- - -first(last)- - - - -(finish) - - -finish- - -end_of_storage

erase(first,last) 过程为将last~finish段复制到 first~first+(finish-last)之中,并释放掉first+(finish-last)~finish的值

2.insert

    template <class T, class Alloc>  
    void insert(iterator position, size_type n, const T& x)  
    {  
        // 如果n为0则不进行任何操作  
        if (n != 0)  
        {  
            if (size_type(end_of_storage - finish) >= n)  
            {      // 剩下的备用空间大于等于“新增元素的个数”  
                T x_copy = x;  
                // 以下计算插入点之后的现有元素个数  
                const size_type elems_after = finish - position;  
                iterator old_finish = finish;  
                if (elems_after > n)  
                {  
                    // 插入点之后的现有元素个数 大于 新增元素个数  
                    uninitialized_copy(finish - n, finish, finish);  
                    finish += n;    // 将vector 尾端标记后移  
                    copy_backward(position, old_finish - n, old_finish);  
                    fill(position, position + n, x_copy); // 从插入点开始填入新值  
                }  
                else  
                {  
                    // 插入点之后的现有元素个数 小于等于 新增元素个数  
                    uninitialized_fill_n(finish, n - elems_after, x_copy);  
                    finish += n - elems_after;  
                    uninitialized_copy(position, old_finish, finish);  
                    finish += elems_after;  
                    fill(position, old_finish, x_copy);  
                }  
            }  
            else  
            {   // 剩下的备用空间小于“新增元素个数”(那就必须配置额外的内存)  
                // 首先决定新长度:就长度的两倍 , 或旧长度+新增元素个数  
                const size_type old_size = size();  
                const size_type len = old_size + max(old_size, n);  
                // 以下配置新的vector空间  
                iterator new_start = data_allocator::allocate(len);  
                iterator new_finish = new_start;  
                __STL_TRY  
                {  
                    // 以下首先将旧的vector的插入点之前的元素复制到新空间  
                    new_finish = uninitialized_copy(start, position, new_start);  
                    // 以下再将新增元素(初值皆为n)填入新空间  
                    new_finish = uninitialized_fill_n(new_finish, n, x);  
                    // 以下再将旧vector的插入点之后的元素复制到新空间  
                    new_finish = uninitialized_copy(position, finish, new_finish);  
                }  
#         ifdef  __STL_USE_EXCEPTIONS  
                catch(...)  
                {  
                    destroy(new_start, new_finish);  
                    data_allocator::deallocate(new_start, len);  
                    throw;  
                }  
#         endif /* __STL_USE_EXCEPTIONS */  
                destroy(start, finish);  
                deallocate();  
                start = new_start;  
                finish = new_finish;  
                end_of_storage = new_start + len;  
            }  
        }  
    }  
}; 
  • 当插入元素小于能够被容纳
    • 插入量>旧值
      STL源码剖析笔记(序列式容器)_第2张图片
    • 插入量<旧值插入
      STL源码剖析笔记(序列式容器)_第3张图片
  • 当插入元素无法被容纳,即容量不够,配置新空间(2倍),在新空间内copy插入如下三段start—-insert_start—-insert_last—–end
    // 以下首先将旧的vector的插入点之前的元素复制到新空间
    new_finish = uninitialized_copy(start, position, new_start);
    // 以下再将新增元素(初值皆为n)填入新空间
    new_finish = uninitialized_fill_n(new_finish, n, x);
    // 以下再将旧vector的插入点之后的元素复制到新空间
    new_finish = uninitialized_copy(position, finish, new_finish);

List

double linked-list 链式数据结构的优势 ,具有效率较高的插入及删除效率

  • List节点
template <class T>
struct__list_node {
 typedef void* void_pointer;
 void_pointer prev;  //型别为 void*。其实可设为 __list_node*
 void_pointer next;
 Tdata;
};
  • list迭代器
    迭代器具备具备前移后移的能力 Bidirectional Iterators
    operator*,operator->,operator++,operator–, operator+=,operator-=

  • list数据结构
    双向循环链表
    STL源码剖析笔记(序列式容器)_第4张图片
    list末尾为添加的空白节点,保证前闭后开

  • list构建与内存管理
    与vector类似,预设使用alloc,并通过list_node_allocator配置空间

template  T, classAlloc = alloc>// 预设使用  alloc 为配置器
class list {
protected:
 typedef __list_node<T> list_node;

 typedef simple_alloclist_node_allocator;
...
};

配置n个节点的空间

list_node_allocator(n)

creat_node配置新节点,用于安插,对空间配置相较vector简单。

link_type create_node(const T& x) {
 link_type p =get_node();
construct(&p->data, x); //全域函式,建构/解构基ᴀ工具。
 return p;
 }
  • list元素操作
    transfer 用于将[first,last)的元素搬移至position节点之前,并非公开接口。 用于实现splice(),merge(), reverse(), sort()。
    其中sort()使用quick sort算法
 void transfer(iterator position, iterator first, iterator last) {
 if (position != last) {
 (*(link_type((*last.node).prev))).next = position.node;
 (*(link_type((*first.node).prev))).next = last.node;

 (*(link_type((*position.node).prev))).next = first.node; 
 link_type tmp = link_type((*position.node).prev);
 (*position.node).prev = (*last.node).prev;
 (*last.node).prev = (*first.node).prev;
 (*first.node).prev = tmp;
 }
 }

deque

双向开口的连续线性空间

  • deque与vector

    1. deque允许于常数时间内对起头端进行元素的安插或移除动作
    2. deque没有所谓容量( capacity)观念,因为
      它是动态地以分段连续空间组合而成,随时可以增加一段新的空间并链接起来。
    3. deque迭代器架构复杂,效率低,除非必要,我们应尽可能选择使用 vector而非deque。对 deque进行的排序动作,为了最高效率,可将deque先完整复制到一个 vector身上,将vector排序后(利用 STL sort算法), 再复制回 deque
  • deque中控器
    采用一小块连续的空间(map),其中每一个节点指向较大的线性连续空间(缓冲区),即deque存储空间主体,SGI STL允许指定缓冲区大小

template T, classAlloc = alloc, size_t BufSiz = 0>
class deque {
public: // Basic types
 typedef Tvalue_type;
 typedef value_type*pointer;
 ...
protected: // Internal typedefs
// 元素的指针的指针( pointer of pointer of T)
 typedef pointer* map_pointer;
protected: // Data members

 map_pointermap; //指向 map, map 是块连续空间,其内的每个元素
 // 都是一个指标(称为节点), 指向一块缓冲区。
 size_typemap_size; // map 内可容纳多少指标。
...
};

STL源码剖析笔记(序列式容器)_第5张图片

  • deque迭代器

deque迭代器需要知道自己所在的缓冲区位置,以及能够区分是否处在缓冲区域边缘,来确定是否要跳跃到上/下 个缓冲区域
start 和finish迭代器指向第一个缓冲区和最后一个缓冲区,start.first前和finish.last后均有一定备用空间
跳跃函数void set_node(map_pointer new_node) {
node = new_node;
first = *new_node;
last = first + difference_type(buffer_size());
}
operator++和operator--判断cur与first/last的关系 决定是否调用set_node

T* cur; //此迭代器所指之缓冲区中的现行(current)元素
 T* first; //此迭代器所指之缓冲区的头
 T* last; //此迭代器所指之缓冲区的尾(含备用空间)
map_pointer node; //指向管控中心
  • deque 的数据结构
    即map,缓冲区,start与finsh两个迭代器
    map为块连续空间,当map不够时候,重新开辟新的一块

  • deque 的建构与内存管理
    预设使用alloc 并通过data_allocator和map_allocator分配空间

protected: // Internal typedefs
// 专属之空间配置器,每次配置一个元素大小
 typedef simple_alloc data_allocator;
// 专属之空间配置器,每次配置一个指针大小
 typedef simple_alloc map_allocator;

constructor如下

deque(int n, const value_type& value)
 : start(), finish(), map(0), map_size(0)
 {
fill_initialize(n, value);
 }

fill_initialize 内部调用create_map_and_nodes()负责产生并安排好deque的结构。并且该函数(f…)完成初始化

push_back如下

void push_back(const value_type& t) {
 if (finish.cur != finish.last - 1)
// 最后缓冲区尚有一个以上的备用空间
construct(finish.cur, t); //直接在备用空间上建构元素
 ++finish.cur; //调整最后缓冲区的使用状态
 }
 else // 最后缓冲区已无(或只剩一个)元素备用空间。
push_back_aux(t);
 }

其需要完成内容
1确定是否更换map若是并执行之通过(push_back_aux所调用的函数 reserve_map_ai_back所为)

void reserve_map_at_back (size_type nodes_to_add = 1) {
 if (nodes_to_add + 1 > map_size - (finish.node - map))
// 如果 map尾端的节点备用空间不足
// 符合以上条件则必须重换一个 map(配置更大的,拷贝原来的,释放原来的)
reallocate_map(nodes_to_add, false);
 }

2改变finish迭代器指向的位置,调整cur

push_front语义与之类似
同样,在执行pop相关操作时候,也会根据相应条件选择释放缓冲区

  • deque元素操作
    文字简述一下erase和insert
    1. erase
      vector 仅通过copy向前搬移元素,然后析构搬移后多余的元素。
      deque与之类似 ,因为deque是双向开口所以通过判断erase first前和last后元素个数 决定向前搬移和向后搬移两种(同样是copy的办法),然后析构掉搬移后多余的元素
    2. insert
      与erase相似,进行安插点前后判断,前方元素少时,

push_front(front())

容器前push一个与front相同的节点,然后通过
front2(old)~pos1 向前搬移一位至front1(new)~pos1-1然后插入到pos中

copy(front2, pos1, front1);

搬移节点。后方元素少于此相同。

heap & priority_queue

heap

heap是通过vector实现的一个堆,并不归属STL容器组件,作为priority queue的底层机制
本质就是堆(完全二叉树)。
通过数组表示树i子节点为2*i 和2*i+1,在排序算法中都有堆及堆排序,并不赘述。以下函数是STL实现的堆排序算法
描述一下heap所提供的函数(以max-heap 大根堆 来解释)

  • push_heap()
    最终逻辑函数__push_heap
void__push_heap(RandomAccessIterator first, Distance holeIndex,
 Distance topIndex, T value) {
 Distance parent = (holeIndex - 1) / 2; //找出父节点
 while (holeIndex > topIndex && *(first + parent) < value) {
// 当尚未到达顶端,且父节点小于新值(于是不符合 heap 的次序特性)
 // 由于以上使用  operator<,可知 STL heap 是一种 max-heap(大者为父)。
 *(first + holeIndex) = *(first + parent); //令洞值为父值
 holeIndex = parent; //percolate up:调整洞号,向上提升至父节点。
 parent = (holeIndex - 1) / 2; //新洞的父节点
 } // 持续至顶端,或满足 heap 的次序特性为止。
 *(first + holeIndex) = value; //令洞值为新值,完成安插动作。
}

此函数前提 须是新元素已经push入在vector尾部,用于调整该尾部元素位置,插入堆中合适位置

  • pop_heap()
    及首部元素(根)至队尾,并在内部调用__adjust_heap调整n-1的元素为堆。

  • sort_heap()即堆排序

void sort_heap(RandomAccessIterator first,
 RandomAccessIterator last) {
 // 以下,每执行一次 pop_heap() ,极值(在 STL heap 中为极大值)即被放在尾端。
 // 扣除尾端再执行一次 pop_heap() ,次极值又被放在新尾端。 一直下去,最后即得
 // 排序结果。
 while (last - first > 1)
pop_heap(first, last--); //每执行 pop_heap() 一次,操作范围即退缩一格。
}
  • make_heap
template <class RandomAccessIterator>
inline void make_heap(RandomAccessIterator first,
 RandomAccessIterator last) {
__make_heap(first, last, value_type(first), distance_type(first) );
}
//以下这组 make_heap() 不允许指定「大小比较标准」。
template <class RandomAccessIterator, class T, class Distance>
void__make_heap(RandomAccessIterator first,
 RandomAccessIterator last, T*,
 Distance*) {
 if (last - first < 2) return; //如果长度为 0或 1,不必重新排列。
 Distance len = last - first;
 // 找出第一个需要重排的子树头部,以 parent 标示出。由于任何叶节点都不需执行
 // perlocate down,所以有以下计算。 parent命名不佳,名为 holeIndex更好。
 Distance parent = (len - 2)/2;
 while (true) {
// 重排以 parent 为首的子树。 len 是为了让 __adjust_heap() 判断操作范围
__adjust_heap(first, parent, len, T(*(first + parent)));
 if (parent == 0) return; //走完根节点,就结束。
 parent--;
 }
}

即将现有数据转换为堆

priority_queue

用于权值的queue,利用max-heap完成。
构建时候调用make_heap,添加元素(push)时候使用用push_heap,
弹出元素pop使用pop_heap(只是放在容器尾部)+底层容器的pop_back实现弹出

slist

slist 单向链表,迭代器为单也 Forward Iterator
其节点及迭代器架构如下图
STL源码剖析笔记(序列式容器)_第6张图片

//单向串行的节点基ᴀ结构
struct__slist_node_base
{
 __slist_node_base* next;
};
//单向串行的节点结构
template <class T>
struct__slist_node : public __slist_node_base
{
 T data;
};

你可能感兴趣的:(C/C++)