STL源码剖析—序列式容器—list、deque、stack、queue

list

-数据结构
(1)环装双向链表,只需要一个指针,可以完整表现整个链表
(2)只要刻意在环状链表的尾端加上一个空白节点,便可满足“前闭后开”区间。
在这里插入图片描述

  • 空间分配
    (1)非连续存储空间,容量大小=元素个数;
    (2)每次配置一个节点的空间,当元素删除时,相应空间一并删除;

  • 迭代器
    (1)因为节点不连续保存在存储空间,不可以使用普通指针做迭代器;
    (2)双向链表,双向迭代器;
    (3)插入操作和接合操作都不会造成原有的list迭代器失效,只有在删除元素时,“指向被删除元素”的那个迭代器失效,其他迭代器不受影响

  • 插入
    分配一个节点,位于目前所指迭代器之前
    在这里插入图片描述

  • 删除
    删除元素,释放空间
    在这里插入图片描述

  • 优缺点
    优点:无预留空间。插入和删除在常数时间内完成
    缺点:不能进行随机存取。时间复杂度为O(N)。

  • 外部接口

#include <list>
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
	std::list<int> iList;
	std::cout<<"size="<<iList.size()<<std::endl;//0
	
	iList.push_back(0);
	iList.push_back(1);
	iList.push_back(2);
	iList.push_back(3);
	iList.push_back(4);
	std::cout<<"size="<<iList.size()<<std::endl;//5
	std::list<int>::iterator it;
	for(it=iList.begin();it!=iList.end();++it) cout<<*it<<'';//01234
	it=find(iList.begin(),iList.end(),3);
	if(it!=0) iList.insert(it,99);
	cout<<*it<<endl;//3 迭代器依然指向3,是在3前面插入
	for(it=iList.begin();it!=iList.end();++it) cout<<*it<<'';//0129934
	it=find(iList.begin(),iList.end(),1);
	if(it!=0)       cout<<*(iList.erase(it))<<endl;//2
	
}

  • 相关方法
// 申请一个节点
link_type get_node() { return list_node_allocator::allocate(); }

// 释放一个节点
void put_node(link_type p) { list_node_allocator::deallocate(p); }

// 创建一个节点并初始化
link_type create_node(const T& x) 
{
    link_type p = get_node();
    construct(&p->data, x); // 
    return p;
}

// 删除一个节点
void destroy_node(link_type p) 
{
    destroy(&p->data); // 
    put_node(p);
}

// 无参构造函数
list() { empty_initialize(); }

void empty_initialize()
{
    node = get_node(); 
    node->next = node; 
    node->prev = node;
}

// 返回头结点
iterator begin() { return (link_type)((*node).next); }

// 放回尾节点
iterator end() { return node; }

// 判断链表是否为空
bool empty() const { return node->next == node; }

// 计算链表的大小
size_type size() const 
{
    size_type result = 0;
    distance(begin(), end(), result);
    return result;
}

// 返回头文件的数据
reference front() { return *begin(); }

// 返回尾节点的数据
reference back() { return *(--end()); }

// 尾部插入数据
void push_back(const T& x) { insert(end(), x); }

// 头部插入数据
void push_front(const T& x) {insert(begin(), x);}

// 删除头部节点
void pop_front() { erase(begin()); }

// 删除尾节点
void pop_back()
{
    iterator tmp = end();
    erase(--tmp);
}

// 在position前面插入一个节点,数据为x   先处理待插入的节点,再处理前后节点
iterator insert(iterator position, const T& x)
{
    link_type tmp = create_node(x); 

    tmp->next = position.node;
    tmp->prev = position.node->prev;
    (link_type(position.node->prev))->next = tmp;
    position.node->prev = tmp;
    
    return tmp;
}

// 删除指定位置的节点
iterator erase(iterator position) 
{
    link_type next_node = link_type(position.node->next);
    link_type prev_node = link_type(position.node->prev);
    prev_node->next = next_node;
    next_node->prev = prev_node;
    destroy_node(position.node);
    return iterator(next_node);
}

// 清空链表
template <class T, class Alloc>
void list<T, Alloc>::clear()
{
    link_type cur = (link_type) node->next; 
    while (cur != node) 
    { 
        link_type tmp = cur;
        cur = (link_type) cur->next;
        destroy_node(tmp); 
    }
    
    node->next = node;
    node->prev = node;
}

// 将数据为value的节点全部删除
template <class T, class Alloc>
void list<T, Alloc>::remove(const T& value)
{
    iterator first = begin();
    iterator last = end();
    while (first != last)
    {
        iterator next = first;
        ++next;
        
        if (*first == value) 
            erase(first);
        
        first = next;
    }
}

deque

  • 特点
    (1)vector是单向开口连续线性空间,deque是双向开口连续线性空间,允许在头尾进行插入删除操作,vector也可以但是在头部操作效率低;
    (2)deque前后开口,没有容量的概念
    (3)deque是由一段一段的定量连续空间构成,利用迭代器实现“整体连续”的假象
    (4)利用中控器(MAP)实现从一个缓冲区跳跃到下一个缓冲区

  • 中控器
    (1)deque采用一块所谓的map(不是map容器)作为主控。这里所谓的map是一小块连续空间,其中每个元素都是指针,指向另一段(较大的)连续线性空间,称为缓冲区。缓冲区才是deque的存储空间主体。
    (2)deque的连续空间其实是假象,是由一段一段的定量连续空间构成
    (3)Map是T,也就是说他是一个指针,所指之物又是一个指针,指向型别为T的一块空间**。
    在这里插入图片描述

  • 迭代器
    (1)deque是分段连续,迭代器要实现其“整体连续”的假象。迭代器要指出分段连续空间(缓冲区)在哪里,并且判断是否处于缓冲区的边缘,如果是就要跳跃到下一个或者上一个缓冲区,deque必须随时掌握管控中心(map)
    (2)deque的迭代器包含四个指针
    cur:指向缓冲区的现行元素
    first:指向缓冲区的头部
    last:指向缓冲区的尾部
    **node:**指向掌控中心MAP
    在这里插入图片描述

  • 构造及内部原理
    在前端、后端添加元素,要考虑是否需要新开辟一个缓冲区;
    在前端、后端删除元素,要考虑是否要是否一个缓冲区。
    具体添加删除原理,详见<STL源码剖析>P185
    以find()函数为例,

std::deque<int> MyDeque(5,0);//构造,初始化为5个0
for(int i=0;i<MyDeque.size();i++)
MyDeque[i]=i+1;//重新赋值,因为是连续性容器,所以允许这样赋值,类似vector
MyDeque.push_back(7);//队尾添加
MyDeque.push_front(99);//队首添加
std::deque<int>::iterator itd;//迭代器
itd=find(MyDeque.begin(),MyDeque.end(),99);//find()返回一个迭代器,99前端的位置
cout<<*itd<<endl;//99
cout<<*(itd.cur)<<endl;//99

在这里插入图片描述
begin()返回start迭代器;end()返回finish迭代器。
stack(堆栈容器)

  • 特点:
    (1)stack是一种先进后出的数据结构;
    (2)只能对栈顶元素进行操作,只能在栈顶新增元素、移除、获得最顶端元素,不接受遍历行为,因此没有迭代器
    (3)以双向开口的deque和list作为底部结构,例如将deque为底部结构并封闭其底端开口,形成stack, deque 作为缺省情况下的 stack 底部结构。因为stack是以底部容器完成所有工作,并且具有“修改某物接口,形成另一种风貌”的特点,是一种配接器,一般在STL中被归类为容器配接器
    在这里插入图片描述
    -源码
#ifndef __SGI_STL_INTERNAL_STACK_H
#define __SGI_STL_INTERNAL_STACK_H
 
__STL_BEGIN_NAMESPACE
 
// 如果编译器不能根据前面模板参数推导出后面使用的默认参数类型,
// 那么就需要手工指定, 本实作stack内部容器默认使用deque
// 选用deque可以在存储空间不足时可以动态增加, 而且代价很低
#ifndef __STL_LIMITED_DEFAULT_TEMPLATES
template <class T, class Sequence = deque<T> >
#else
template <class T, class Sequence>
#endif
class stack
{
  // 特化的全局运算符, 提供operator==和<重载则构建出所有运算符
  // 其具体细节见<stl_pair.h>中的说明
  friend bool operator== __STL_NULL_TMPL_ARGS (const stack&, const stack&);
  friend bool operator< __STL_NULL_TMPL_ARGS (const stack&, const stack&);
 
public:
  // 由于stack仅支持对栈顶元素的操作, 所以不定义STL要求的
  // pointer, iterator, difference_type
  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:
  // 下面的操作完全使用内部容器的成员函数实现
  // 这再次体现了STL高度的可复用性:-)
 
  // 判断stack是否为空
  bool empty() const { return c.empty(); }
 
  // stack中元素个数
  size_type size() const { return c.size(); }
 
  // 返回栈顶元素, 注意这里返回的是引用!!!
  reference top() { return c.back(); }
  const_reference top() const { return c.back(); }
 
  // 在栈顶追加新元素
  void push(const value_type& x) { c.push_back(x); }
 
  // 移除栈顶元素, 注意不返回元素的引用,
  // 很多初学者随机用此容器时经常误认为pop()操作同时会返回栈顶元素的引用
  void pop() { c.pop_back(); }
};
 
// 判断两个stack是否相等, 就要测试其内部维护容器是否相等
// x.c == y.c会调用容器重载的operator ==
template <class T, class Sequence>
bool operator==(const stack<T, Sequence>& x, const stack<T, Sequence>& y)
{
  return x.c == y.c;
}
 
template <class T, class Sequence>
bool operator<(const stack<T, Sequence>& x, const stack<T, Sequence>& y)
{
  return x.c < y.c;
}
__STL_END_NAMESPACE
#endif /* __SGI_STL_INTERNAL_STACK_H */
  • 外部接口
#include<stack>
#include<iostream>
using namespace std;
int main()
{
stack<int> MyStack;//创建堆栈对象 型别可以是:int float string 或者指针、自己定义的类型
MyStack.push(1);
MyStack.push(2);
MyStack.push(3);//元素入栈,从顶端入栈
while(!MyStack.empty())//如果容器为空,MyStack.empty()返回true
{
	std::cout<<MyStack.top()<<std::endl;//打印出321,top只负责显示值,不能弹出
	MyStack.pop();//弹出栈顶元素。pop只负责弹出,没有返回值
}
stack<int> MyStackB(MyStack);//拷贝构造
stack<int> MyStackC=MyStack;//赋值

return 0;
}

queue

  • 特点
    (1)queue是一种先进先出的容器;
    (2)有一个入口一个出口,允许在队尾加入数据,队头提取数据,不允许遍历操作,没有迭代器,类似于排队。
    (3)以deque为底层实现,和stack一起分类为容器配接器
    在这里插入图片描述
  • 外部接口
#include<queue>
#include<iostream>
using namespace std;
int main()
{
queue<int> MyQueue;
MyQueue.push(1);
MyQueue.push(2);//从队尾添加元素
cout<<MyQueue.back()<<endl;//返回队尾元素
while(!MyQueue.empty())
{
cout<<MyQueue.front()<<endl;//返回队首元素
MyQueue.pop();//弹出队首元素没有返回值
}
return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值