deque源码分析

本文详细分析了C++标准库中的deque容器,重点探讨了deque的双向开口特性,允许在头部和尾部高效地进行插入和删除操作。deque由多个连续空间段组成,通过一个map指针管理这些缓冲区。迭代器的设计考虑了缓冲区边界判断,以支持跨缓冲区的移动。此外,文章还介绍了deque的构造、内存管理和关键操作如插入、弹出的实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

/*
vector是单向开口的连续线性空间,deque是双向开口的线性空间,所谓双向开口,意思就是可以在头尾两端分别做元素的插入和删除操作
deque可以在常规时间内对起头端分别做元素的插入和删除操作
deque没有所谓的容量概念,它是以动态地分段连续空间组合而成的,可以增加一段新的空间并连接起来
对deque进行排序操作,为了最高效率,可将deque先完成复制到一个vector中,将vecotr排序后,再复制回deque
deque采用一块所谓的map作为主控.这里所谓的map是一小块连续空间,其中每个元素都是指针,指向另一段连续的线性空间,称为缓冲区
*/


template<class T,class Alloc=alloc,size_t Bufsize=0>
class deque{
	public:
		typedef T value_type;
		typedef value_type *pointer;
	protected:
		//元素的指针的指针
		typedef pointer * map_pointer;
	protected:
		map_pointer map;//指向map,map是块连续的空间,其中每个元素都是一个指针,指向一个缓冲区
		size_type map_size;//map内可以容纳多少张指针

}

//deque迭代器
/*
迭代器首先必须能够指出分段连续空间在哪里,其次必须能够判断自己是否已经处于所在缓冲区的边缘,如果是,一旦前进或者后退就必须跳跃至上一个或者下一个缓冲区.
*/

template<class T,class Ref,class Ptr,size_t BufSiz>
struct __deque_iterator{//没有继承std::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));}
//因为没有继承std::iterator 所以必须自行撰写五个必要的迭代器相应型别
typedef random_access_iterator_ag 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;//指向管控中中心
}

/*
其中用来决定缓冲区大小的函数buffer_size(),调用__deque_buf_size() 后者是一个全局函数
//如果n不为0,传回n,表示buffer size由用户自定义
//如果n为0,表示buffer size 使用默认值
//如果sz(元素大小,sizeof(value_type))小于512,传回512/sz
如果sz不小于512,传回1
*/

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

在这里插入图片描述
下面是一些操作函数

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*());}
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 --(int ){//后置式标准写法
	self tmp=*this;
	--*this;
	return tmp;
}
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((-offsetr-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;//调用operator +=
}
self &operator -=(difference_type n){return *this+=-n;}
self operator-(difference_type n)const{
	self tmp=*this;
	return tmp-=n;//调用operator-=
}
//以下实现随机存取,迭代器可以直接跳跃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除了维护一个先前说过的指向map的指针外,也维护start,finish两个迭代器,分别指向第一缓冲区的第一个元素和最后一个缓冲区的最后一个元素,此外,它当然也必须记住目前的map大小.因为一旦map所提供的结点不足,就必须重新配置更大的一块map

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*());}
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 --(int ){//后置式标准写法
	self tmp=*this;
	--*this;
	return tmp;
}
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((-offsetr-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;//调用operator +=
}
self &operator -=(difference_type n){return *this+=-n;}
self operator-(difference_type n)const{
	self tmp=*this;
	return tmp-=n;//调用operator-=
}
//以下实现随机存取,迭代器可以直接跳跃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);
}

下面将会展示一个实例

#include<deque>
#include<iostream>
#include<algorithm>
using namespace std;
int main(){
	deque<int>ideq(20,9);
	cout<<"size="<<ideq.size()<<endl;//size=20
	//现在,应该已经构造了一个deque,有20个元素,初值皆为9
	//缓冲区大小为32bytes
	//为每个元素设置新值
	for(int i=0;i<ideq.size();++i)
		ideq[i]=i;
	for(int i=0;i<ideq.size();++i)
		cout<<ideq[i]<<' ';
	cout<<endl;
	//在尾端增加三个元素,其值位0,1,2
	for(int i=0;i<3;++i)
		ideq.push_back(i);
	cout<<endl;
	cout<<"size="<<ideq.size()<<endl;
	//在最前端增加一个元素,其值是99
	ideq.push_front(99);
	for(int i=0;i<ideq.size();++i)
		cout<<ideq[i]<<' ';
	cout<<"size="<<ideq.size()<<endl;
	//在最前端增加两个元素,其值分别是98,97
	ideq.push_front(98);
	ideq.push_front(97);
	for(int i=0;i<ideq.size();++i)
		cout<<ideq[i]<<' ';
	cout<<"size="<<ideq.size()<<endl;
//搜寻数值为99的元素,并打印出来
	deque<int>::iterator itr;
	itr=find(ideq.begin(),ideq.end(),99);
		cout<<*itr<<endl;
//	cout<<*(itr.cur)<<endl;	

	return 0;
}

下面是deque的构造与内存管理

//deque 自行定义了两个专属的空间配置器
protected:
	//专属空间配置器,每次配置一个元素大小
	typedef simple_alloc<value_type Alloc>data_allocator;
	//专属空间配置器,每次配置一个指针的大小
	typedef simple_alloc<pointer,ALloc>map_allocator;
	//并且提供了一个constructor如下
	deque(int n,const value_type& value):start(),finish(),map(0),map_size(0){
		fill_initiaize(n,value);
	}
	//其内调用的fill_initialize()负责产生并安排好deque的结构,并将元素的初值设定妥当
	template<class T,class Alloc,size_t Buffize>
	void deque<T,Alloc,Buffsize>::fill_initialize(size_type n,const value_type& value){
		create_map_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 BuffSize>
	void deque<T,Alloc,BufSize>::create_map_and_nodes(size_type num_elements){
		//需要的节点数=(元素的个数/每个缓冲区可容纳的元素个数)+1
		//如果刚好整除,会多分配一个结点
		size_type num_nodes=num_elements/buffer_size()+1;
		//一个map要管理几个节点,最少是八个,最多是"所需节点数加2"
		//(前后各预留一个 扩充时使用)
		map_size=max(initial_map_size(),num_nodes+2);
		map=map_allocator::allocate(map_size);
		//以上配置出一个"具有map_size()个节点"的map
		//以下令nstart和nfinish指向map所拥有之全部结点的最中央区段
		//保持在最中央,可使头尾两端的扩充能量一样大,每个结点对应一个缓冲区
		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<=nfinsih;++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();
	//前面说过 如果刚好整除 会多分配一个结点
	//此时即指令cur指向这多分配的一个节点(所对应缓冲区)的起始处
	}

两个重要函数 前插和后插

//deque 自行定义了两个专属的空间配置器
protected:
	//专属空间配置器,每次配置一个元素大小
	typedef simple_alloc<value_type Alloc>data_allocator;
	//专属空间配置器,每次配置一个指针的大小
	typedef simple_alloc<pointer,ALloc>map_allocator;
	//并且提供了一个constructor如下
	deque(int n,const value_type& value):start(),finish(),map(0),map_size(0){
		fill_initiaize(n,value);
	}
	//其内调用的fill_initialize()负责产生并安排好deque的结构,并将元素的初值设定妥当
	template<class T,class Alloc,size_t Buffize>
	void deque<T,Alloc,Buffsize>::fill_initialize(size_type n,const value_type& value){
		create_map_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 BuffSize>
	void deque<T,Alloc,BufSize>::create_map_and_nodes(size_type num_elements){
		//需要的节点数=(元素的个数/每个缓冲区可容纳的元素个数)+1
		//如果刚好整除,会多分配一个结点
		size_type num_nodes=num_elements/buffer_size()+1;
		//一个map要管理几个节点,最少是八个,最多是"所需节点数加2"
		//(前后各预留一个 扩充时使用)
		map_size=max(initial_map_size(),num_nodes+2);
		map=map_allocator::allocate(map_size);
		//以上配置出一个"具有map_size()个节点"的map
		//以下令nstart和nfinish指向map所拥有之全部结点的最中央区段
		//保持在最中央,可使头尾两端的扩充能量一样大,每个结点对应一个缓冲区
		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<=nfinsih;++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();
	//前面说过 如果刚好整除 会多分配一个结点
	//此时即指令cur指向这多分配的一个节点(所对应缓冲区)的起始处
	}

下面是pop和insert函数的实现

//前面展示过push_back和push_front(),下面是对应的pop_front()和pop_back(),pop是将元素拿掉,无论从deque的最前端或最尾端取元素,都需要考虑在某种条件下,将缓冲区释放掉
void pop_back(){
	if(finish.cur!=finish.first){
	//最后缓冲区有一个或多个元素
	--finish.cur;//调整指针,相当于排除了最后元素
	destory(finish.cur);//将最后元素析构
}else
	//最后缓冲区没有任何元素
	pop_back_aux();//将最后元素析构
}
//只有当finish.cur==finish.first时才会被调用
template <class T,class Alloc,size_t BufSize>
void deque<T,Alloc,BufSize>::pop_back_aux(){
	deallocate_node(finish.first);//释放最后一个缓冲区
	finish.set_node(finish.node-1);//调整finish的状态
	finish.cur=finish.last-1;//上一个缓冲区的最后一个元素
	destory(finish.cur);//将该元素析构
}
void pop_front(){
	if(start.cur!=start.last-1){
	//第一个缓冲区有两个或多个元素
	destory(start.cur);//将第一个元素析构
	++start.cur;
}else{
	//第一缓冲区仅有一个元素
	pop_front_aux();//这里将进行缓冲区的释放工作
}
//只有当start.cur==start.last-1时才会被调用
template<class T,class Alloc,size_t BufSize>
void deque<T,Alloc,BufSize>::pop_front_aux(){
	destory(start.cur);//将缓冲区的第一个元素 也就是最后一个元素析构 
	deallocate_node(start.first);//释放第一缓冲区
	start.set_node(start.node+1);//调整start的状态 使其指向下一个缓冲区的第一个元素
	start.cur=start.first;
}
}
/*
下面这个例子是clear(),用来清除整个deque,请注意,deque的最初状态(无任何元素时)保有一个缓冲区,因此在clear()完成之后,恢复到初始状态,也一样保留一个缓冲区
*/
template<class T,class Alloc,size_t BufSize>
void deque<T,Alloc,BufSize>::clear(){
	//以下对头尾以外的每一个缓冲区(他们一定都是饱满的)
	for(map_pointer node=start.node+1,node<finish.node;++node){
	//将缓冲区内的所有元素析构
	destory(*node,*node+buffer_size());
}
if(start.node!=finish.node){//至少有头尾两个缓冲区
	destory(start.cur,start.last);//将头缓冲区的目前所有元素析构
	//以下释放尾部缓冲区 注意头缓冲区保留
	data_allocator::deallocate(finish.first,buffer_size());
}else{//只有一个缓冲区
	destory(start.cur,finish.cur);//将此唯一缓冲区内的所有元素析构
	//注意  并不释放缓冲区空间,这唯一的缓冲区将保留

}
	finish=start;//调整状态
}
//下面是erase(),用来清除某个元素
//清楚pos所指的元素,pos为清除点
iterator erase(iterator pos){
	iterator next=pos;
	++next;
	difference_type index=pos-start;//清楚点之前的元素个数
	if(index<(size()>>1)){//如果清除点之前的元素个数比较少
		copy_backward(start,pos,next);//就移动清除点之前的元素
		pop_front();//移动完毕 最前一个元素冗余,删除
	}
	else{//清楚点之后的元素比较少
	copy(next,finish,pos);//就移动清除点之后的元素
	pop_back();//移动完毕 ,最后一个元素冗余,删除 
}
	return start+inedx;
}
//下面的erase(),用来清除[first,last)区间的所有元素
template <class T,class Alloc,size_t BufSize>
deque<T,Alloc,BufSize>::iterator
deque<T,Alloc,BufSize>::erase(iterator first,iterator last){
	if(first==start&&last=finish){
	//如果清除的是整个区间 那么直接调用clear()即可
	clear();
	return finish;
}else{
	difference_type n=last-first;//清除区间的长度
	difference_type elems_before=first-start;//清除区间前方的元素个数
	if(elems_before<(size()-n)/2){//如果前方的元素比较少
		copy_backward(start,first,last);//向后后方移动前方元素(覆盖清楚区间)
		iterator new_start=start+n;//标记deque的新起点
		destory(start,new_start);//移动完毕,将冗余的元素析构	
		//以下将冗余缓冲区释放
		for(map_pointer cur=start.node;cur<new_start.node;++cur)
			data_allocator::deallocate(*cur,buffer_size());
			start=new_start;//设定deque的新起点
}else{//如果清除区后方元素比较少
	copy(last,finish,first);//向前移动后方元素(覆盖清除区间)
	iterator new_finish=finish-n;//标记deque的新尾点
	destory(new_finish,finsh);//移动完毕,将冗余的元素析构
	//以下将冗余的缓冲区释放
	for(map_pointer cur=new_finish.node+1;cur<=finish.node;++cur)
		data_allocator::deallocate(*cur,buffer_size());
		finish=new_finish;//设定deque的新尾点
}
	return start+elems_before;
}
}
//在position位置插入一个新元素 ,其值为x
iterator insert(iterator poistion,const value_type &x){
	if(position.cur==start.cur){
	//如果插入点是deque的最前端
	push_front(x);//交给push_front去做
	return start;
}else if(position.cur==finish.cur){
//如果插入点是deque的最尾端  交给push_back()去做
	push_back(x);
	iterator tmp=finish;
	--tmp;
	return tmp;
}else{
	return insert_aux(position,x);//交给inset_aux去做
}
}
template<class T,Alloc,size_t BufSize>
typename deque<T,Alloc,BufSize>::iterator
deque<T,Alloc,BufSize>::insert_aux(iterator pos,const value_type &x){
	difference_type index=pos-start;//插入点之前的元素个数
	value_type x_copy=x;
	if(index<size()/2){
	//如果插入点之前的元素个数比较少
	push_front(front());//在最前端加入与第一个元素同值的元素
	iterator front1=start;//以下标示记号 ,然后进行元素移动
	++front1;
	iterator front2=front1;
	++front2;
	pos=start+index;
	iterator pos1=pos;
	++pos1;
	copy(front2,pos1,front1);//元素移动
}else{
	push_back(back());//在最尾端加入与最后元素同值的元素
	iterator back1=finish;//以下标记号然后进行元素移动
	--back1;
	iterator back2=back1;
	--back2;
	pos=start+index;
	copy_backward(pos,back2,back1);//元素移动
}
	*pos=x_copy;//在插入点上设置新值
	return pos;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值