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 16∗2+4∗2=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的底层容器时无法编译通过。