STL源码剖析笔记——deque、stack,queue

系列文章目录

STL源码剖析笔记——迭代器

文章目录

  • 系列文章目录
    • 1. deque概述
    • 2. deque的中控器
    • 3. deque的迭代器
    • 4. deque的数据结构
    • 5. deque的构造和内存管理
    • 6. deque的元素操作
    • 7. 相关容器
      • (1)stack
      • (2)queue


1. deque概述

  vector是单向开口的连续线性空间,deque则是一种双向开口的连续线性空间。所谓双向开口,意思是可以在头尾两端分别做元素的插入和删除操作。STL源码剖析笔记——deque、stack,queue_第1张图片
  deque和vector的最大差异,一在于deque允许于常数时间内对起头端进行元素的插入或移除操作,二在于deque没有所谓容量(capacity)观念,因为它是动态地以分段连续空间组合而成,随时可以增加一段新的空间并链接起来。

2. deque的中控器

  deque系由一段一段的定量连续空间构成。一旦有必要在deque的前端或尾端增加新空间,便配置一段定量连续空间,串接在整个deque的头端或尾端。 deque的最大任务,便是在这些分段的定量连续空间上,维护其整体连续的假象, 并提供随机存取的接口。
  deque采用一块所谓的map (注意,不是STL的map容器)作为主控。这里所谓map是一小块连续空间,其中每个元素(此处称为一个节点,node)都是指针,指向另一段(较大的)连续线性空间,称为缓冲区。缓冲区才是deque的储存空间主体。
STL源码剖析笔记——deque、stack,queue_第2张图片
map其实是一个T,也就是说它是一个指针,所指之物又是一个指针,指向型别为T的一块空间。

3. deque的迭代器

  deque迭代器采用的是随机访问迭代器(random_access_iterator),deque迭代器的结构必须能够指出分段连续空间(亦即缓冲区)在哪里,其次它必须能够判断自己是否已经处于其所在缓冲区的边缘,如果是,一旦前进或后退时就必须跳跃至下一个或上一个缓冲区。 为了能够正确跳跃,deque迭代器必须有一个指针指向map中与当前缓冲区相对应的节点。

struct _deque_iterator {
...
//保持与容器的联结
	T* cur; //此迭代器所指之缓冲区中的现行(current)元素
	T* first; //此迭代器所指之缓冲区的头
	T* last; //此迭代器所指之缓冲区的尾(含备用空间)
	map_pointer node; //指向map中与当前缓冲区相对应的节点,即T**
...
}

STL源码剖析笔记——deque、stack,queue_第3张图片

  下面是deque迭代器的几个关键行为。由于迭代器内对各种指针运算都进行了重载操作,所以各种指针运算如加、减、前进、后退都不能直观视之。其中最关键的就是:一旦行进时遇到缓冲区边缘,要特别当心,视前进或后退而定,可能需要调用set_node()跳到正确的缓冲区:

void set_node(map_pointer new_node) {
	node = new_node;
	first = *new__node;
	last = first + difference_type(buffer_size()))

//以下各个重载运算子是_deque_iterator<>成功运作的关键
reference operator*() const { 
	return *cur; 
}
pointer operator->() const { 
	return &(operator*()); 
}
self& operator++() { 
	++cur;
	//切换至下一个元素
	if (cur == last) { 
		set_node(node + 1);
		//如果已达所在缓冲区的尾端
		//就切换至下一节点(亦即缓冲区)
		cur = first;
	}
	return *this;
}
...

4. deque的数据结构

  deque除了维护一个先前说过的指向map的指针外,也维护 start, finish 两个迭代器,分别指向第一缓冲区的第一个元素和最后缓冲区的最后一个元素(的下一位置)。此外,它当然也必须记住目前的map大小。因为一旦map所提供的节点不足,就必须重新配置更大的一块map。

template <class T, class Alloc = alloc, size_t BufSiz = 0>
class deque {
public: // Basic types
	typedef T   value_type;
	typedef value_type*   pointer;
	typedef size_t   size_type;
	
public: // Iterators
	typedef _deque_iterator<T, T&, T*, BufSiz>   iterator;
	
protected: // Internal typedefs
	//元素的指针的指针(pointer of pointer of T) typedef pointer* map_pointer;
	iterator start; //表现第一个节点
	iterator finish; //表现最后一个节点
protected:
	// Data members
	map_pointer map; // 指向 map, map 是块连续空间,
	//其每个元素都是个指针,指向一个节点(缓冲区) 
	size_type map_size; // map 内有多少指针
)

5. deque的构造和内存管理

  声明一个deque时,deque自行定义了两个专属的空间配置器,分别将map和缓冲区根据所设定的初值进行初始化:

	//专属之空间配置器,每次配置一个元素大小
	typedef simple_alloc<value_type, Alloc>   data_allocator;
	//专属之空间配置器,每次配置一个指针大小
	typedef simple_alloc<pointer, Alloc>   map_allocator;

需要节点数 = (元素个数/每个缓冲区可容纳的元素个数) + 1,如果刚好整除,会多配一个节点。
一个map要管理几个节点。最少8个,最多是“所需节点数 + 2" (前后各预留一个,扩充时可用)

当向deque尾部插入元素时:
  1.若缓冲区空间足够,则直接插入。
  2.若缓冲区刚好用完或者不够,则配置一个新的缓冲区,再插入元素。
向deque头部插入元素流程相同,但顺序是从end→begin进行插入。

6. deque的元素操作

push_back()//在队列的尾部插入元素。
emplace_front()//与push_front()的作用一样 
push_front()//在队列的头部插入元素。
emplace_back()//与push_back()的作用一样 
pop_back()//删除队列尾部的元素。
pop_front()//删除队列头部的元素。
back()//返回队列尾部元素的引用。
front()//返回队列头部元素的引用。
clear()//清空队列中的所有元素。
empty()//判断队列是否为空。
size()//返回队列中元素的个数。
begin()//返回头位置的迭代器
end()//返回尾+1位置的迭代器
rbegin()//返回逆头位置的迭代器 
rend()//返回逆尾-1位置的迭代器 
insert()//在指定位置插入元素 
erase()//在指定位置删除元素 

7. 相关容器

(1)stack

  stack是一种“ 先进后出 ”的数据结构。它只有一个出口。stack允许新增元素、移除元素、取得最顶端元素。但 除了最顶端外,没有任何其它方法可以存取stack的其它元素。换言之,stack 不允许有遍历行为
STL源码剖析笔记——deque、stack,queue_第4张图片
  由于stack系以底部容器完成其所有工作,称为adapter (配接器),因此,STL stack往往不被归类为container (容器),而被归类为container adapter(容器配接器)。stack可以通过deque或者list作为底层容器来实现。
  stack所有元素的进出都必须符合“先进后出”的条件,只有stack顶端的元素,才有机会被外界取用。stack不提供随机访问功能,也不提供迭代器。

(2)queue

  queue与stack非常相似,同样是一个容器配接器,不允许遍历,没有迭代器,可以通过deque或者list作为底层容器来实现。区别在于queue是一种先进先出的数据结构。它有两个出口。queue允许新增元素、移除元素、从最底端加入元素、取得最顶端元素。
STL源码剖析笔记——deque、stack,queue_第5张图片

你可能感兴趣的:(STL学习笔记,c++,笔记,开发语言)