深入理解deque

deque概述

deque是一种双向开口的连续线性空间,所谓双向开口,意思是可以在头尾两端分别做元素的插入和删除操作,如图所示:
在这里插入图片描述
deque允许常数时间内对起头端进行元素的插入和删除操作,deque没有所谓容量观念,因为它是动态地以分段连续空间组合而成,随时可以增加一段新的空间并链接起来,因此,deque没有必要提供所谓的空间保留功能
虽然deque也提高Ramdon Access Iterator,但它的迭代器并不是普通指针,这影响了各个运算层次,因此,除非必要,我们应尽可能选择使用vector而非deque

deque的中控器

deque是连续空间(至少逻辑上看来如此),deque由一段一段的定量连续空间构成,一旦有必要在deque的前端或后端增加新空间,便配置一段定量连续空间,串接在整个deque的头端或尾端

deque采用一块所谓的map(不是STL的map容器)作为主控,这里所谓map是一小块连续空间,其中每个元素(此处称为一个节点,node)都是指针,指向另一段连续线性空间,称为缓冲区,缓冲区才是deque的存储空间主体

template<class T,class Alloc=alloc,size_t BufSiz=0>
class deque
{
public:
	typedef T value_type;
	typedef value_type* pointer;
protected:
	typedef pointer* map_pointer;
	size_type map_size;
};

map其实是一个T**,也就是说它是一个指针,所指之物又是一个指针,指向型别为T的空间。
在这里插入图片描述

deque的迭代器

deque是分段连续空间,维持其"整体连续"假象的任务,落在了迭代器的operator++和operator–两个运算子身上
下面是实现方式:

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));}
	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 defference_type;
	typedef T** map_pointer;

	typedef _deque_iterator self;
	//保持与容器的联接
	T* cur;//此迭代器所指之缓冲区的现行元素
	T* first;//此迭代器所指之缓冲区的头
	T* last;//此迭代器所指之缓冲区的尾
	map_pointer node;//指向管控中心
};

其中用来决定缓冲区大小的函数buffer_size(),调用_deque_buf_size()

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));
}

在这里插入图片描述
假设现在我们产生一个deque,并令其缓冲区大小为32,于是每个缓冲区可容纳32/sizeof(int)=8个元素,经过某些操作之后,deque拥有20个元素,20个元素需要20/8=3个缓冲区,所以map之内运用了三个节点,注意,最后一个缓冲区有备用空间,稍后如果有新元素要插入于尾端,可直接拿此备用空间来使用
在这里插入图片描述
下面是deque迭代器的几个关键行为

void set_node(map_pointer new_node)
{
	node=new_node;
	first=*new_node;
	last=first+difference_type(buffer_size());
}
reference operator*() const { return *cur};
pointer operator->() const { return &(operator*()); }
difference_type operator-(const self& x) const
{
	return difference_type(buffer_size())*(node-x.node-1)+(cur-first)+(x.last-x.cur);
}
self& operator++()
{
	++cur;
	if(cur==last)
	{
		set_node(node+1);
		cur=first;
	}
	return *this;
}
self operator++(int)
{
	self tmp=*this;
	++*this;
	return tmp;
}
self& operator--()
{
	if(cur==first)
	{
		set_node(node-1);
		cur=last;
	}
	--cur;
	return *this;
}
self& operator+=(difference_type n)
{
	difference_type offset=n+(cur-first);
	if(offset>=0&&offset<difference_type(buffer_size()))//目标位置在同一缓冲区
		cur+=cur;
	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;
}
self operator+(difference_type n) const
{
	self tmp=*this;
	return tmp+=n;
}
self& operator-=(difference_type n) 
{
	return *this+=-n;
}
self opertor-(difference_type n) const
{
	self tmp=*this;
	return tmp-=n;
}
reference operator[](difference_type n) const { return *(*this+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数据结构

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

template <class T,class Alloc=alloc,size_t BufSiz=0>
class deque
{
public:
	typedef T value_type;
	typedef value_type* pointer;
	typedef size_t size_type;
public:
	typedef _deque_iterator<T,T&,T*,BufSiz> iterator;
protected:
	//元素的指针的指针
	typedef pointer* map_pointer;
protected:
	iterator start;//表示第一个节点
	iterator finish;//表示最后一个节点
	map_pointer map;
	size_type map_size;//map内有多少指针
	
};

deque的构造和内存管理

程序一开始声明一个deque

deque<int,alloc,32> ideq(20,9);

其缓冲区为32bytes,并令其保留20个元素空间,每个元素初值为9

deque自行定义了两个专属的空间适配器

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

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

template<class T,class Alloc,size_t BufSize>
void deque<T,Alloc,BufSize>::fill_initialize(size_type n,const value_type& value)
{
	create_map_and_nodes(n);//把deque的结构产生并安排好
	map_pointer cur;
	_STL_TRY
	{
		for(cur=start.node;cur<finish.node;++cur)
		{
			uninitialized_fill(*cur,*cur+buffer_size(),value);
		}
		uninitialized_fill(finish.first,finish.cur,value);
	}
	catch(...)
	{
		...
	}
}

其中create_map_and_nodes()负责产生并安排deque的结构:

template <class T,class Alloc,size_t BufSize>
void deque<T,Alloc,BufSize>::create_map_and_nodes(size_type num_elements)
{
	//需要节点数=(元素个数/每个缓冲区可容纳的元素个数)+1
	//如果刚好整除,会分配一个节点
	size_type num_nodes=num_elements/buffer_size()+1;
	//一个map要管理几个节点,最少8个,最多是所需节点数加2
	map_size=max(initial_map_size(),num_nodes+2);
	map=map_allocator::allocate(map_size);
	//保持在最中央,可使头尾两端的扩充能力一样大,每个节点可对应一个缓冲区
	map_pointer nstart=map+(map_size-num_nodes)/2;
	map_pointer nfinish=nstart+num_nodes-1;

	map_pointer cur;
	_STL_TRY
	{
		//为map内的每个现用节点配置缓冲区,所有缓冲区加起来就是deque的可用空间
		for(cur=nstart;cur<=nfinish;++cur)
			*cur=allocate_node();
	}
	catch(...)
	{
		//为deque内的两个迭代器start和end设定正确内容
		start.set_node(nstart);
		finish.set_node(nfinish);
		start.cur=start.first;
		finish.cur=finish.first+num_elements%buffer_size();
	}
}

接下来在尾端插入三个新元素
在这里插入图片描述
以下是push_back()函数内容

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

现在,如果再新添加一个新元素于尾端

ideq.push_back(3);

由于尾端只剩一个元素备用空间,于是push_back()调用push_back_aux()

template <class T,class Alloc,size_t BufSize>
void deque<T,Alloc,BufSize>::push_back_aux(const value_type& t)
{
	value_type t_copy=t;
	reserve_map_at_back();
	*(finish.node+1)=allocate_node();
	_STL_TRY
	{
		construct(finish.cur,t_copy);
		finish.set_node(finish.node+1);
		finish.cur=finish.first;
	}
	_STL_UNWIND(deallocate_node(*(finish.node+1)));
}

在这里插入图片描述
接下来在deque的前端插入一个新元素
在这里插入图片描述
什么时候map需要重新整治
这个问题的判断由reserve_map_at_back()和reserve_map_at_front()进行,实际操作由realocate_map()执行:

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);
}

void reserve_map_at_front(size_type nodes_to_add=1)
{
	if(nodes_to_add>start.node-map)
		//如果map前端的节点备用空间不足
		//符合以上条件则必须重换一个map
		realocate_map(node_to_add,true);
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值