C++ STL和泛型编程(二)---- deque

一、结构(G2.9)

在这里插入图片描述
由图知,其有中控器map,在源码里面实际上是通过vector来实现且里面存放的元素是指针元素。另外虽然其是通过vecor来实现的(所以也是两倍增长的)但在源码操作中实际上它是可以双向扩充的。
deque的迭代器是class iterator类型,里面有4个指针变量:cur\ first\ last\ node。每个迭代器指代一个buffer,其first指向该buffer的头,last则指向buffer的尾的下一个位置,而node指代当前iterator所负责的buffer在map中的位置,cur则表示当前iterator所负责的buffer段中所当前所指向的元素的位置。
注意:在同一个buffer上操作时,迭代器中first\ last\ node都是不变的,只有cur可移动变化。当然若由于cur的移动,使iterator变成负责下一个(或前一个)buffer时,此时first\ last\ node\ cur都发生变化。

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

template<class T, class Ref, class Ptr, size_t BufSiz>
sttuct __deque_iterator{
	typedef random_access_iterator_tag iterator_category; // (1)
	typedef T value_type; // (2)
	typedef Ptr pointer; // (3)
	typedef Ref reference; // (4)
	typedef size_t size_type; 
	typedef ptrdiff_t difference_type; // (5)
	typedef T** map_pointer;
	typedef __deque_iterator self;

	// 由此知,迭代器的大小为16
	T* cur;
	T* first;
	T* last;
	map_pointer node; // T**
...
};


template<class T, class Alloc = alloc, size_t BufSize = 0> 
// 这里设置BufSize = 0,即n = 0,表示buffer size由放入数据元素的类型进行设定。
class deque{
public:
	typedef T value_type;
	typedef __deque_iterator<T, T&, T*, BufSiz> iterator; // 迭代器是__deque_iterator<T, T&, T*, BufSiz>类的类对象
protected:
	typedef pointer* map_pointer; // T**
protected:
	iterator start; // 迭代器变量,大小为16
	iterator finish; // 迭代器变量,大小为16
	map_pointer map; // 指针类型变量
	size_type map_size; // unsigned long 或 unsigned int 类型变量
public:
	iterator begin(){
		return start;
	}
	iterator end(){
		return finish;
	}
	size_type size() const{
		return finish - start;
	}
...
};

由上知,当创建一个deque容器时,其大小为 16 ∗ 2 + 4 ∗ 2 = 40 16*2+4*2=40 162+42=40,至于容器内部放多少元素,那是动态分配后的,跟deque容器对象本身没关系。

而对于buffer size即每个buffer容纳的元素个数,在G2.9版本中可以由使用者设定,在新版本中则不可以。
return n != 0 ? n : (sz < 512 ? size_t(512/sz) : size_t(1)):
传入参数中第一参数为指定将buffer的切分为n段,第二参数为所放入容器的元素的类型的大小
如果n不为0,则返回n,表示buffer size由使用者自定切分为n段大小;
如果n为0,表示buffer size 根据存放的数据元素的类型(通过第二参数来进一步判断)来设定预设值,即

若sz【sz = (sizeof(value_type))】小于512,则设置buffer划分为512/sz份;
若sz不小于512,则设置buffer只存放一个数据元素。

二、迭代器(G2.9)

由上知,iterator中设定了5个associated types,且可以看到其迭代器类型iterator_category为random_access_iterator_tag即指针是可以任意跳动的。

三、insert()函数(G2.9)

template<class T, class 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;
}


// 在 position 处插入一个元素,其值为x
iterator insert(iterator position, const value_type& x){
	if(posiion.cur == start.cur){ // 如果安插点是deque最前端
		push_front(x);			  // 交给push_front()做,且此时start的位置已经前移一位
		return start;			  // 返回插入后当前元素所在的位置
	}
	else if(position.cur == finish.cur){ // 如果安插点是deque最尾端
		push_back(x);		  			 // 交给push_back()做,且此时finish的位置已经后移一位
		iterator tmp = finish;			 
		--tmp;
		return tmp;						 // 返回插入后最后元素所在的位置,即为更新的finish - 1,所以进行--temp的操作
	}
	else{
		return insert_aux(position, x); // 调用辅助函数insert_aux()
	}
}

在插入新元素时,
1.先进行极端情况得判断,若插入元素在deque最前端或最尾端,则调用push_front()或push_back()进行插入操作,并返回插入后所在的位置。
2.若不是极端操作情况,则调用辅助函数insert_aux(),而由上知,insert_aux()的返回值类型是deque<T, Alloc, BufSize>::iterator类型。计算插入点前的元素个数index,进行判断怎样挪动元素进行插入放置:

1.若index<size()/2,插入点在靠前端,所以选择将插入点前端的元素往前挪,先在最前端加入与第一元素同值的元素,然后将更新后的已经前移一位的start位置赋值给front1,++front1表示返回原来的start位置并将其赋值给front2。而++front2表示front2指向原来的第二个数据元素。而此时pos = start + index,因为start的位置已经往前挪一位,所以pos此时的位置相当于也往前挪一位了。将pos赋值给pos1,并且++pos1,则表示pos1重新指回原来pos所指向的那个元素。此时copy(front2, pos1, front1)则表示选取从原来的第二个元素开始到原插入点前的元素,在扩充后的第二个位置【即原先的start位置】开始复制。复制操作完之后则会在 原插入点前 空出一个位置,即现在新的pos所指向的位置,所以此时 *pos = x_copy即实现在插入点上设定新值。
2.若index>size()/2,插入点在靠后端,所以选择将插入点后端的元素往后挪,先在最后端加入与最后元素同值的元素,然后将更新后后移一位的finish位置赋值给back1,–back1表示返回原来的finish的位置并将其赋给back2。而–back2则表示此时的back2指向原来的最后元素的位置。此时pos仍未原来的位置不变。copy_backward(pos, back2, back1)表示选取从原来pos位置的元素到倒数第二个元素,从扩充后的倒数第二个位置开始逆序复制。复制操作完之后则会在 原插入点处 空出一个位置,即原pos所指向的位置,所以此时 *pos = x_copy即实现在插入点上设定新值。
注意以上两种操作无论是挪动插入点前半段还是后半段,最终都是在插入点前将新元素插入

四、deque模拟连续空间(G2.9)

在这里插入图片描述

实现连续空间的操作,最终是落到++--这些操作上的,同时还需要检查边界问题即在进行加减等操作时是否调到了另一个buffer上。

// 这些不是迭代器的操作
reference operator[](size_type n){
	return start[difference_type(n)];
}
reference front(){
	return *start;
}
reference back(){
	iterator tmp = finish;
	--tmp; // fnish表示最末端元素的下一个位置,所以要取最末端元素时,应为--tmp
	return tmp;
}
size_type size() const{
	return finish - start; // 表示从头到尾一共存放有多少个数据元素,具体底层实现过程在后面operator--()
}
bool empty() const{
	return finish == start;
}

而++--这些操作又是基于迭代器的,所以下面便是迭代器的操作功劳以实现连续空间的操作。

- operator -()

// 两个iterators之间的距离相当于
// (1)两个iterator间的buffers的总长度 + 
// (2)后一个(减数)iterator的buffer起始位置至其cur位置的长度 +
// (3)前一个(被减数)iterator的cur位置至其buffer的末端位置的长度
difference_type operator-(const self& x) const{
	return difference_type(buffer_size()) * (node - x.node - 1) + (cur - first) + (x.last - x.cur);
}

1.difference_type(buffer_size())表示一个buffer的长度,(node - x.node - 1)表示后一个(被减数)iterator与前一个(减数)iterator之间 相隔有多少个完整的buffer【除去此时前后的两个迭代器的buffer,因为它们可能不是完整的一个buffer长度】。如上图中,finish与start中间相隔有3个完整的buffer。
2.然后再加上,后一个(减数)iterator的buffer起始位置至其cur位置的长度,即cur - first;
3.接着再加上,前一个(被减数)iterator的cur位置至其buffer的末端位置的长度,即x.last - x.cur。

- operator ++()/ operator ++(int) & operator --()/ operator --(int)

在这里插入图片描述

void set_node(map_pointer new_node){ // 当移动超出当前buffer范围时,进行node的移动
	node = new_node;
	first = *new_node;
	last = first + difference_type(buffer_size());
}

self& operator++(){ // ++i
	++cur;	// 切换至下一元素
	if(cur == last){	// 如果抵达buffer的尾端
		set_node(node + 1);	// 跳至后一buffer
		cur = first;		// 的起点
	}
	return *this;	// 返回迭代器
}

self operator++(int){ // i++
	self tmp = *this;
	++*this; // 调用operator++()
	return tmp;
}

self& operator--(){
	if(cur == first){	// 如果抵达buffer的头端
		set_node(node - 1);	// 跳至前一buffer
		cur = last;		// 的最末端
	}
	--cur; // 往前移一元素(此即最末元素),因为cur = last指向的是最后一个末端元素的下一位,所以此时还要--cur才能取得末端元素。
	return *this;
}

self operator--(int){
	self tmp = *this;
	--*this; // 调用operator--()
	return tmp;
}

- operator +=()/ operator +()

self& operator+=(difference_type n){
	difference_type offset = n + (cur - first);
	if(offset >= 0 && offset < difference_type(buffer_size()))
		cur +=n;
	else{
		// 目标位置不在同一buffer内
		difference_type node_offset = 
			offset > 0 ? offset / difference_type(buffer_size()) : 
					-difference_type((-offset - 1) / buffer_size()) - 1;
		// 切换至正确的buffer
		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;
}

1.当目标位置在同一buffer内时:

(1)当传入参数n是正数时,因为是在目标位置之内的情况即说明 offset = n + (cur - first)时满足 条件(offset >=0 && offset < difference_type(buffer_size()) 的【如n=2,cur - first=5,buffer_size()=8】,所以cur += n;
(2)当传入参数n是负数时,因为是在目标位置之内的情况即说明 offset = n + (cur - first)时满足 条件(offset >=0 && offset < difference_type(buffer_size()) 的【如n=-3,cur - first=5,buffer_size()=8】,所以cur += n;

2.当目标位置不在同一buffer内时:

(1)当传入参数n是正数时,此时进入else后,因为offset > 0成立,所以node_offset = offset / difference_type(buffer_size()),即判断目标位置超出了多少个buffer区。然后退回到中控器map,调用set_node()函数移动node指针跳到对应的目标buffer区。接着再在目标buffer区内移动cur位置即cur = first + (offset - node_offset * difference_type(buffer_size()))
如n=6,cur - first=5,buffer_size()=8,此时 offset=11,
即由node_offset = offset / difference_type(buffer_size())超过一个buffer区,
然后set_node移动到下一个node指向的buffer区。
此时注意,offset - node_pffset * difference_type(buffer_size())表示的是减去填满上一个buffer的位置移动后,在下一个buffer剩余的移动位置,因而cur = first + 在新的buffer中的剩余移动位置。

(2)当传入参数n是负数时,此时进入else后,因为offset > 0不成立即offset是小于0的【offset !=0否则直接进入if条件执行】,所以node_offset = -difference_type((-offset - 1) / buffer_size()) - 1,因为offer是负数所以先取反,而-offset - 1是往前填补buffer然后接着移动到前一个buffer时是移动到前一个buffer的last位置的,而实际上计算填补了多少位置时应该是考虑到移动到前一个buffer的最末端元素的位置的,所以应-1操作。
而最终difference_type((-offset - 1) / buffer_size()) 还需要 - 1,则是因为最终计算cur = first + (offset - node_offset * difference_type(buffer_size()))时,因为cur是以first为基准开始的,所以(offset - node_pffset * difference_type(buffer_size()))在填满buffer后,剩余的移动位置是逆序的,所以应该再补上一个node_offset因而-difference_type((-offset - 1) / buffer_size()) - 1考虑了多一个node_offset进去来使offset完成填满buffer后剩余的逆序移动位置,在移动到前面的buffer时变为以first为基底开始的顺序剩余位置移动。

- operator -=()/ operator -()

self& operator-=(difference_type n){
	return *this += -n; // 调用operator+=()
}

self operator-(difference_type n) const{
	self tmp = *this;
	return tmp -= n; // 调用operator-=()
}

- operator[ ]()

reference operator[](difference_type n) const{
	return *(*this + n); // 调用operator+()来完成连续空间容器的取值操作
}

上面便是调用operator+(),把迭代器移动n个位置,然后把它的值解引用取出来。

五、G4.9

在这里插入图片描述由上知,在G4.9版本下,deque<_Tp>的大小为40,且不能再自由指定buffer size的大小了,而是由系统默认指定分配。

在这里插入图片描述
由图知,一个deque对象,里面包含了四个变量,_M_map指针变量指向一个一个的buffer区,_M_map_size变量表示由vector实现的控制中心中能容纳多少个_M_map指针变量,两个迭代器变量_M_start和_M_finish指向所有buffer区的头buffer区和尾buffer区。

另外,在通过vector实现两倍空间扩充进行原内容拷贝时,其是将原内容拷贝到新空间的中段【比如本来有8个元素空间,现在需要扩充到16个元素空间时,其是扩充到16个元素空间的中段的】,这样可以保证新的空间左边、右边都有余裕出来,这样无论往左扩充buffer区或往右扩充buffer区都可以满足且都是等量扩充。

六、queue\ stack(deque adapter)

- 底层容器为deque

  • queue 和 stack 都是基于 deque 实现(内含一个deque,并限制某些操作),技术上本质为容器适配器

在这里插入图片描述

template<class T, class Sequence = deque<T>>
class queue{
...
public:
	typedef typename Sequence::value_type value_type;
	typedef typename Sequence::size_type size_type;
	typedef typename Sequence::reference reference;
	typedef typename Sequence::const_reference const_reference;
protected:
	Sequence c; // 底层容器
public:
	 bool empty() const{
		return c.empty();
	}
	size_type size() const{
		return c.size();
	}
	reference front(){
		return c.front();
	}
	const_reference front(){
		return c.front();
	}
	reference back(){
		return c.back();
	}
	const_reference back(){
		return c.back();
	}
	void push(const value_type& x){
		c.push_back(x);
	}
	void pop(){
		c.pop_front();
	}
};

template<class T, class Sequence = deque<T>>
class stack{
...
public:
	typedef typename Sequence::value_type value_type;
	typedef typename Sequence::size_type size_type;
	typedef typename Sequence::reference reference;
	typedef typename Sequence::const_reference const_reference;
protected:
	Sequence c; // 底层容器,由传入参数知其底层实现为deque<T>
public:
	 bool empty() const{
		return c.empty();
	}
	size_type size() const{
		return c.size();
	}
	reference top(){
		return c.back();
	}
	const_reference top(){
		return c.back();
	}
	void push(const value_type& x){
		c.push_back(x);
	}
	void pop(){
		c.pop_back();
	}
};

由代码知,queue和stak内含一个deque,而通过转调用其部分功能而实现queue/stack的先进先出/后今后出。

  • queue和stack都不允许遍历,所以不提供iterator
stack<string>::iterator ite;  
// [Error]'iterator' is not a member of 'std::stack<std::basic_string<char>>'
queue<string>::iterator ite;  
// [Error]'iterator' is not a member of 'std::queue<std::basic_string<char>>'

- 若底层容器为list

stack和queue都可选用。

stack<string, list<string>> c;
for(long i = 0; i < 10; i++){
	snprintf(buf, 10, "%d", rand());
	c.push(string(buf));
}
cout << "stack.size()=" << c.size() << endl;
cout << "stack.top()=" << c.top() << endl; 
c.pop();

queue<string, list<string>> c1;
for(long i = 0; i < 10; i++){
	snprintf(buf, 10, "%d", rand());
	c1.push(string(buf));
}
cout << "queue.size()=" << c1.size() << endl;
cout << "queue.front()=" << c1.front() << endl; 
cout << "queue.back()=" << c1.back() << endl; 
c1.pop();

- 若底层容器为vector

stack可以满足,但queue不可

queue<string, vector<string>> c1;
for(long i = 0; i < 10; i++){
	snprintf(buf, 10, "%d", rand());
	c1.push(string(buf));
}
cout << "queue.size()=" << c1.size() << endl;
cout << "queue.front()=" << c1.front() << endl; 
cout << "queue.back()=" << c1.back() << endl; 
c1.pop(); // [Error]'class std::vector<std::basic_string<char>>' has no member named 'pop_front'

因为vector只可以实现单向地后进后出,无法实现queue的先进先出。

stack<string, vector<string>> c;
for(long i = 0; i < 10; i++){
	snprintf(buf, 10, "%d", rand());
	c.push(string(buf));
}
cout << "stack.size()=" << c.size() << endl;
cout << "stack.top()=" << c.top() << endl; 
c.pop();

vector单向地后进后出,可以满足stack的后进后出的操作。

- 若底层容器为set或map

stack和queue均不可选用。

stack<string, set<string>> c;
for(long i = 0; i < 10; i++){
	snprintf(buf, 10, "%d", rand());
	c.push(string(buf)); // [Error]'class std::set<std::basic_string<char>>' has no member named 'push_back'
}
cout << "stack.size()=" << c.size() << endl; 
cout << "stack.top()=" << c.top() << endl; // [Error]'class std::set<std::basic_string<char>>' has no member named 'back'
c.pop(); // [Error]'class std::set<std::basic_string<char>>' has no member named 'pop_back'


stack<string, map<string>> c; // [Error]template argument...
queue<string, map<string>> c; // [Error]template argument...
// 因为map传入的是需要key和value两个参数,所以在一开始就编译错误!

因为关联式容器set和map都不提供c.push_back/ c.back()/ c.pop_back 操作,因而若用其作为stack/queue的底层容器时无法编译通过。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值