1. 程式人生 > >STL原始碼分析之deque有序容器 上

STL原始碼分析之deque有序容器 上

前言

deque的功能很強大, 其複雜度也比list, vector複雜很多. deque是一個random_access_iterator_tag型別. 前面分析過vector是儲存在連續的線性空間, 頭插入和刪除其代價都很大, 當陣列滿了還要重新尋找更大的空間; deque也是一個儲存在連續的線性空間中, 但是它是一個雙向開口, 頭尾插入和刪除都是O(1)的時間複雜度, 空間也是可擴充套件的, 不會經常尋找新的空間. deque的記憶體操作主要是由map來實現的.

打算分為3節來分析deque的重要實現部分. 本節分析deque的迭代器和構造解構函式.

__deque_iterator迭代器結構

雖然deque在某些方面和vector有些相似, 但是迭代器並不是一個普通指標, deque的迭代器很複雜, 現在我們就來分析一下.

全域性函式

inline size_t __deque_buf_size(size_t n, size_t sz)
{
  return n != 0 ? n : (sz < 512 ? size_t(512 / sz) : size_t(1));
}

型別定義

dequerandom_access_iterator_tag型別. 滿足traits程式設計.

這裡重點分析四個引數cur, first, last以及node

#ifndef
__STL_NON_TYPE_TMPL_PARAM_BUG
template <class T, class Ref, class Ptr, size_t BufSiz> struct __deque_iterator { // 迭代器定義 typedef __deque_iterator<T, T&, T*, BufSiz> iterator; typedef __deque_iterator<T, const T&, const T*, BufSiz> const_iterator; static size_t buffer_size
() {return __deque_buf_size(BufSiz, sizeof(T)); } #else /* __STL_NON_TYPE_TMPL_PARAM_BUG */ template <class T, class Ref, class Ptr> struct __deque_iterator { typedef __deque_iterator<T, T&, T*> iterator; typedef __deque_iterator<T, const T&, const T*> const_iterator; static size_t buffer_size() {return __deque_buf_size(0, sizeof(T)); } #endif // deque是random_access_iterator_tag型別 typedef random_access_iterator_tag iterator_category; // 基本型別的定義, 滿足traits程式設計 typedef T value_type; typedef Ptr pointer; typedef Ref reference; typedef size_t size_type; typedef ptrdiff_t difference_type; // node typedef T** map_pointer; map_pointer node; typedef __deque_iterator self; ... };
// 滿足traits程式設計
template <class T, class Ref, class Ptr, size_t BufSiz>
inline random_access_iterator_tag
iterator_category(const __deque_iterator<T, Ref, Ptr, BufSiz>&) {
  return random_access_iterator_tag();
}
template <class T, class Ref, class Ptr, size_t BufSiz>
inline T* value_type(const __deque_iterator<T, Ref, Ptr, BufSiz>&) {
  return 0;
}
template <class T, class Ref, class Ptr, size_t BufSiz>
inline ptrdiff_t* distance_type(const __deque_iterator<T, Ref, Ptr, BufSiz>&) {
  return 0;
}

cur, first, last這三個變數類似於vector中的3個迭代器一樣.

  • cur : 當前所指的位置
  • first : 當前陣列中頭的位置
  • last : 當前陣列中尾的位置

注意 : 因為deque的空間是由map管理的, 它是一個指向指標的指標, 所以三個引數都是指向當前的陣列, 這樣的陣列可能有多個, 只是每個陣列都管理這3個變數.

template <class T, class Ref, class Ptr, size_t BufSiz>
struct __deque_iterator {
	...
  typedef T value_type;
  T* cur;
  T* first;
  T* last;
  ...
};

每一個陣列都有一個node指標, 他是用來指向*map的指標.

template <class T, class Ref, class Ptr, size_t BufSiz>
struct __deque_iterator {
	...
	// node
  typedef T** map_pointer;
  map_pointer node;
  ...
};

建構函式

template <class T, class Ref, class Ptr, size_t BufSiz>
struct __deque_iterator {
	...
  	// 初始化cur指向當前陣列位置, last指標陣列的尾, node指向y
  	__deque_iterator(T* x, map_pointer y)  : cur(x), first(*y), last(*y + buffer_size()), node(y) {}
  	// 初始化為一個空的deque
  	__deque_iterator() : cur(0), first(0), last(0), node(0) {}
  	// 接受一個迭代器
  	__deque_iterator(const iterator& x) : cur(x.cur), first(x.first), last(x.last), node(x.node) {}
    ...
};

過載

__deque_iterator實現了基本運算子, deque過載的運算子操作都是呼叫__deque_iterator的運算子.

不過先分析一個待會會用到的函式set_node.

因為node是一個指向*map的指標, 當陣列填充滿了後, 要重新指向下一個陣列的頭, set_node就是更新指向陣列的頭的功能.

template <class T, class Ref, class Ptr, size_t BufSiz>
struct __deque_iterator {
	...
	void set_node(map_pointer new_node) 
	{
		// 讓node指標另一個數組的頭, 同時修改頭和尾的地址
    	node = new_node;
    	first = *new_node;
    	last = first + difference_type(buffer_size());
  	}
  	...
};

過載++和–

需要注意++和–都可能出現數組越界, 如果判斷要越界就得更新node的指向.

template <class T, class Ref, class Ptr, size_t BufSiz>
struct __deque_iterator {
	...  
 	// 這裡需要判斷是否達到當前陣列的尾部
  self& operator++() {
    ++cur;
    // 達到了尾部就需要更新node的指向
    if (cur == last) {
      set_node(node + 1);
      cur = first;
    }
    return *this; 
  }
  // 同理, 需要判斷是否到達陣列的頭. 到達就要更新node指向
  self& operator--() {
    if (cur == first) {
      set_node(node - 1);
      cur = last;
    }
    --cur;
    return *this;
  }
  
  self operator++(int)  {
    self tmp = *this;
    ++*this;
    return tmp;
  }
  self operator--(int) {
    self tmp = *this;
    --*this;
    return tmp;
  }
  ...
};

過載+, -等. 因為dequerandom_access_iterator_tag型別, 所以支援直接加減操作.

template <class T, class Ref, class Ptr, size_t BufSiz>
struct __deque_iterator {
	...
	// 過載指標
  	reference operator*() const { return *cur; }
  	reference operator[](difference_type n) const { return *(*this + n); } // 這個會呼叫過載+運算子
#ifndef __SGI_STL_NO_ARROW_OPERATOR
  	pointer operator->() const { return &(operator*()); }
#endif /* __SGI_STL_NO_ARROW_OPERATOR */

  	self& operator+=(difference_type n) 
  	{
    	difference_type offset = n + (cur - first);	// 要移動的距離
    	// 如果是在的當前陣列內並且向前移動就直接指向那個位置就行了.
    	if (offset >= 0 && offset < difference_type(buffer_size()))
    	  	cur += n;
    	 // 向後移動或已經不再當前陣列中
    	else 
    	{
        	// 計算需要跨多少個數組
      		difference_type node_offset = offset > 0 ? offset / difference_type(buffer_size()) : -difference_type((-offset - 1) / buffer_size()) - 1;
      		set_node(node + node_offset);
      		cur = first + (offset - node_offset * difference_type(buffer_size()));
    	}
    	return *this;
  	}
  	// 以下都是呼叫+運算子
	difference_type operator-(const self& x) const 
	{
    	return difference_type(buffer_size()) * (node - x.node - 1) + (cur - first) + (x.last - x.cur);
  	}
  	self operator+(difference_type n) const {
    	self tmp = *this;
    	return tmp += n;
  	}

  	self& operator-=(difference_type n) { return *this += -n; }
 
  	self operator-(difference_type n) const {
  	  	self tmp = *this;
    	return tmp -= n;
  	}
	// 
  	bool operator==(const self& x) const { return cur == x.cur; }
  	bool operator!=(const self& x) const { return !(*this == x); }
  	bool operator<(const self& x) const 
  	{
	    return (node == x.node) ? (cur < x.cur) : (node < x.node);
  	}
};

以上就是__deque_iterator的實現, 真的還是挺多的. 其中必須要知道的就是上面介紹的四個引數, 因為deque主要就是通過這4個引數來獲取元素的.

deque例項

下面要分析了很多關於deque的函式, 這裡就簡單的選擇一些函式來寫一下, 至少有個印象.

/*************************************************************************
    > File Name: deque.cpp
    > Author: Function_Dou
    > Mail: NOT
    > Created Time: 2018年11月24日 星期六 12時44分05秒
 ************************************************************************/

#include <iostream>
#include <stdlib.h>
#include <deque>

using namespace std;

int main()
{
	int a[10] = { 1, 2 ,3, 4, 5, 6 };
	deque<int> d;
	d.insert(d.begin(), a, a + 10);
	for(const auto& i : d)
		cout << i << " ";	// 1 2 3 4 5 6 0 0 0 0 
	cout << endl;

	deque<int> d1(d);
	deque<int> d2(a, a+10);
	deque<int> d3(d.begin(), d.end());
	cout << bool(d1==d2) << endl;	// 1
	cout << "size = " << d3.size();	// size = 10

	exit(0);
}

接下里的deque結構就很複雜, 現在就來看一下吧.

deque結構

分析了__deque_iterator結構後, 再來分析deque的基本函式就比較輕鬆了, deque的很多函式都會呼叫__deque_iterator中的操作, 本節也只探討其基本的構造, 析構, 過載等操作.

基本型別定義

deque滿足traits程式設計的巢狀定義型別.

template <class T, class Alloc = alloc, size_t BufSiz = 0> 
class deque {
public:                         // Basic types
    // 滿足traits程式設計
  typedef T value_type;
  typedef value_type* pointer;
  typedef const value_type* const_pointer;
  typedef value_type& reference;
  typedef const value_type& const_reference;
  typedef size_t size_type;
  typedef ptrdiff_t difference_type;

public:                         // Iterators
    // 定義迭代器
#ifndef __STL_NON_TYPE_TMPL_PARAM_BUG
  typedef __deque_iterator<T, T&, T*, BufSiz>              iterator;
  typedef __deque_iterator<T, const T&, const T&, BufSiz>  const_iterator;
#else /* __STL_NON_TYPE_TMPL_PARAM_BUG */
  typedef __deque_iterator<T, T&, T*>                      iterator;
  typedef __deque_iterator<T, const T&, const T*>          const_iterator;
#endif /* __STL_NON_TYPE_TMPL_PARAM_BUG */

#ifdef __STL_CLASS_PARTIAL_SPECIALIZATION
  typedef reverse_iterator<const_iterator> const_reverse_iterator;
  typedef reverse_iterator<iterator> reverse_iterator;
#else /* __STL_CLASS_PARTIAL_SPECIALIZATION */
  typedef reverse_iterator<const_iterator, value_type, const_reference, 
                           difference_type>  
          const_reverse_iterator;
  typedef reverse_iterator<iterator, value_type, reference, difference_type>
          reverse_iterator; 
#endif /* __STL_CLASS_PARTIAL_SPECIALIZATION */

protected:                      // Internal typedefs
    // map, 指向指標的指標
  typedef pointer* map_pointer;
  typedef simple_alloc<value_type, Alloc> data_allocator;	// value_type型別的空間配置器
  typedef simple_alloc<pointer, Alloc> map_allocator;		// 指標型別的空間配置器
    ...
};

注意map_pointer是一個指向指標的指標, deque就儲存一個map_pointer, 用它來指向我們分配的記憶體空間, 用來管理資料.

構造和解構函式

建構函式. 有多個過載函式, 接受大部分不同的引數型別. 基本上每一個建構函式都會呼叫create_map_and_nodes, 這就是建構函式的核心, 待會就來分析這個函式實現.

template <class T, class Alloc = alloc, size_t BufSiz = 0> 
class deque {
    ...
public:                         // Basic types
  deque() : start(), finish(), map(0), map_size(0)	// 預設建構函式
  {
    create_map_and_nodes(0);
  }
  deque(const deque& x) : start(), finish(), map(0), map_size(0)	// 接受一個deque
  {
    create_map_and_nodes(x.size());
    __STL_TRY {
      uninitialized_copy(x.begin(), x.end(), start);
    }
    __STL_UNWIND(destroy_map_and_nodes());
  }
    // 接受 n:初始化大小, value:初始化的值
  deque(size_type n, const value_type& value) : start(), finish(), map(0), map_size(0)
  {
    fill_initialize(n, value);
  }
  deque(int n, const value_type& value) : start(), finish(), map(0), map_size(0)
  {
    fill_initialize(n, value);
  } 
  deque(long n, const value_type& value) : start(), finish(), map(0), map_size(0)
  {
    fill_initialize(n, value);
  }
    // 接受 n:初始化大小
  explicit deque(size_type n) : start(), finish(), map(0), map_size(0)
  {
    fill_initialize(n, value_type());
  }
#ifdef __STL_MEMBER_TEMPLATES
  template <class InputIterator>
  deque(InputIterator first, InputIterator last) : start(), finish(), map(0), map_size(0)
  {
    range_initialize(first, last, iterator_category(first));
  }
#else /* __STL_MEMBER_TEMPLATES */
  deque(const value_type* first, const value_type* last) : start(), finish(), map(0), map_size(0)
  {
    create_map_and_nodes(last - first);
    __STL_TRY {
      uninitialized_copy(first, last, start);
    }
    __STL_UNWIND(destroy_map_and_nodes());
  }
    // 接受兩個迭代器, 構造一個範圍
  deque(const_iterator first, const_iterator last) : start(), finish(), map(0), map_size(0)
  
            
           

相關推薦

STL原始碼分析deque有序容器

前言 deque的功能很強大, 其複雜度也比list, vector複雜很多. deque是一個random_access_iterator_tag型別. 前面分析過vector是儲存在連續的線性空間, 頭插入和刪除其代價都很大, 當陣列滿了還要重新尋找更大的空間; deque也是一

STL原始碼分析slist有序容器

前言 不同於list是Forward Iterator型別的雙向連結串列, slist是單向連結串列, 也是Bidirectional Iterator型別. slist主要耗費的空間小, 操作一些特定的操作更加的快, 同樣類似與slist, 在執行插入和刪除操作迭代器都不會像vec

STL原始碼分析deque有序容器

前言 前一節我們分析了deque的基本使用, 本節我們來分析一下deque的對map的操作, 即插入, 刪除等. 但是本節只分析push, pop和刪除操作, 而insert操作有點複雜還是放到下節來分析. push, pop 因為deque的是能夠雙向操作, 所以其push

STL原始碼分析hashtable關聯容器

前言 前面我們分析過RB-tree關聯容器, RB-tree在插入(可重複和不可重複), 刪除等操作時間複雜度都是O(nlngn), 以及滿足5個規則, 以及以他為底層的配接器; 本節就來分析hashtable另個關聯容器, 他在插入, 刪除等操作都可以做到O(1)的時間複雜度.

STL原始碼分析slist有序容器

前言 上節我們對slist的基本構成, 構造析構做了分析, 本節 我們就來分析關於slist的基本元素操作. slist分析 基本屬性資訊 slist是隻有正向迭代, 所以只能直接獲取頭部的資料. template <class T, class Alloc =

STL原始碼分析list有序容器

前言 前兩節對list的push, pop, insert等操作做了分析, 本節準備探討list怎麼實現sort功能. list是一個迴圈雙向連結串列, 不是一個連續地址空間, 所以sort功能需要特殊的演算法單獨實現, 而不能用演算法中的sort. 當然還可以將list的元素插

STL原始碼分析deque

前言 前面兩節對deque基本所有的操作都分析了, 本節就分析deque的insert操作的實現. insert的過載函式有很多, 所以沒有在上節一起分析, 本節也只是對部分過載函式進行分析, 剩下的列出原始碼就行了. 原始碼分析 insert實現 這裡先將insert的

STL原始碼分析RB-tree關聯容器

前言 本節將分析STL中難度很高的RB-tree, 如果對紅黑樹有所認識的那麼分析起來的難度也就不是很大, 對紅黑樹沒有太多瞭解的直接來分析的難度就非常的大了, 可以對紅黑樹有個瞭解紅黑樹之原理和演算法詳細介紹. 紅黑樹是很類似與AVL-tree的, 但是因為AVL-tree在插入,

Spark原始碼分析Spark Shell(

https://www.cnblogs.com/xing901022/p/6412619.html 文中分析的spark版本為apache的spark-2.1.0-bin-hadoop2.7。 bin目錄結構: -rwxr-xr-x. 1 bigdata bigdata 1089 Dec

STL原始碼分析__type_traits型別

前言 上一篇探討的是traits是為了將迭代器沒能完善的原生指標, traits用特化和偏特化程式設計來完善. 這一篇準備探討__type_traits, 為了將我們在空間配置器裡面的提過的__true_type和false_type進行解答. 而type_traits型別對我們ST

STL原始碼分析traits萃取劑

前言 前面我們分析了迭代器的五類, 而迭代器所指向物件的型別被稱為value type. 傳入引數的型別可以通過編譯器自行推斷出來, 但是如果是函式的返回值的話, 就無法通過value type讓編譯器自行推斷出來了. 而traits就解決了函式返回值型別. 同樣原生指標不能內嵌型別

STL原始碼分析迭代器

前言 迭代器是將演算法和容器兩個獨立的泛型進行調和的一個介面. 使我們不需要關係中間的轉化是怎麼樣的就都能直接使用迭代器進行資料訪問. 而迭代器最重要的就是對operator *和operator->進行過載, 使它表現的像一個指標. 型別 迭代器根據移動特性和實施操作

STL原始碼分析記憶體池

前言 上一節只分析了第二級配置器是由多個連結串列來存放相同記憶體大小, 當沒有空間的時候就向記憶體池索取就行了, 卻沒有具體分析記憶體池是怎麼儲存空間的, 是不是記憶體池真的有用不完的記憶體, 本節我們就具體來分析一下 記憶體池 static data template的初始

STL原始碼分析第二級配置器

前言 第一級是直接呼叫malloc分配空間, 呼叫free釋放空間, 第二級三就是建立一個記憶體池, 小於128位元組的申請都直接在記憶體池申請, 不直接呼叫malloc和free. 本節分析第二級空間配置器, STL將第二級配置器設定為預設的配置器, 所以只要一次申請的空間不超過1

STL原始碼分析第一級配置器

前言 上一節我們分析了空間配置器對new的配置, 而STL將空間配置器分為了兩級, 第一級是直接呼叫malloc分配空間, 呼叫free釋放空間, 第二級三就是建立一個記憶體池, 小於128位元組的申請都直接在記憶體池申請, 不直接呼叫malloc和free. 本節我們就先分析第

STL原始碼分析空間配置器

前言 SGI STL將new的申請空間和呼叫建構函式的兩個功能分開實現, 如果對new不太清楚的, 可以先去看看這一篇new實現再來看配置器也不遲. 本節是STL分析的第一篇, 主要分析STL各個部分都會出現的alloc實現, 雖然每個部分都只會預設呼叫它, 不瞭解它也可以看懂分析,

STL原始碼分析multimap配接器

前言 前面我們分析了map, 知道map是不允許插入相同的鍵值的, 也不會儲存第二次的資料, 而本節分析的multimap與map不同, 它允許多個重複的鍵值插入. mutimap操作 int main() { multimap<string, int> mul

STL原始碼分析multiset配接器

前言 前面也分析過set, 並且set不能插入相同的鍵, 本節分析的multiset與set不同之處就是他允許插入相同的鍵. multiset操作 int main() { multiset<string> multi; // 允許重複插入鍵 mult

STL原始碼分析map配接器

前言 上一節分析了pair結構, 正是為map分析做鋪墊, map本身實現也不難, 其資料儲存是pair, 儲存結構是RB-tree, 即map也並不能說是關聯容器, 而應該是配接器. map操作 map的insert必須是以pair為儲存結構, 當然也可以直接使用make_

STL原始碼分析pair結構體

前言 前面在分析set, RB-tree都有在insert實現中出現pair, 下節分析map的時候更會經常出現pair, 所以打算在之前先對pair有個認識. pair是一個有兩個變數的結構體, 即誰都可以直接呼叫它的變數, 畢竟struct預設許可權都是public, 將兩個