通过栈/队列/优先级队列/了解容器适配器,仿函数和反向迭代器

简介: 通过栈/队列/优先级队列/了解容器适配器,仿函数和反向迭代器

vector和list我们称为容器,而stack和queue却被称为容器适配器。

这和它们第二个模板参数有关系,可以看到stack和queue的第二个模板参数的缺省值都是deque,即双端队列容器。也就是说栈和队列都是以容器为主材料构建的,而且因为第二个参数是一个缺省值,我们不但可以使用deque构建,同样可以使用vectorlist来构建。

用已经存在的容器来封装转换成不存在的容器,这种方式就被称之为适配器模式。

有了deque提供的接口,再要实现栈和队列就会变得很简单。

一.stack

栈的特点就是后进先出,,插入和删除都是在尾部进行,栈不提供迭代器(因为栈只能访问栈顶的元素)。

#include<deque>
namespace wbm
{
  template <class T, class Container = deque<T>>
  class stack
  {
  public:
    bool empty()const
    {
      return _con.empty();
    }
    void push(const T&x)
    {
      _con.push_back(x);
    }
    void pop()
    {
      _con.pop_back();
    }
    size_t size()const
    {
      return _con.size();
    }
    const T& top()const
    {
      return _con.back();
    }
  private:
    Container _con;
  };
}

栈不但可以通过deque来封装,还可以通过vectorlist来封装,只要支持尾插尾删即可

二.queue

队列的特点是先进先出,队尾入,队头出,可以访问队头和队尾的数据,也不提供迭代器

#include<deque>
namespace wbm
{
  template<class T,class Container=deque<T>>
  class queue
  {
  public:
    bool empty()const
    {
      return _con.empty();
    }
    void push(const T& x)
    {
      _con.push_back();
    }
    void pop()
    {
      _con.pop_front();
    }
    size_t size()const
    {
      return _con.size();
    }
    const T& fornt()const
    {
      return _con.front();
    }
    const T& back()const
    {
      return _con.back();
    }
  private:
    Container _con;
  };
}

只要是支持尾插头删的容器都可以做queue的适配器,但是不建议使用数组,因为数组的头删效率太低

三.deque(双端队列)

因为vector和list都各有优缺点,因此大佬们就想是否可以创造一个容器兼具vector和list的优点又避免缺点,于是便有了双端队列deque,虽然deque的名字带着队列,但是它和队列毫无关系。

观察它提供的接口发现:它既支持随机访问,又支持头插头删和尾插尾删,看起来似乎确实是一个完美的容器。但如果真的那么完美,vector和list岂不是早就被淘汰了。

其实deque的底层是一个指针数组+多个buffer数组(buffer数组的大小是固定的)的组成形式;这个指针数组是一个中控数组,其中存放的元素是属于这个deque的buffer数组的地址。

第一次插入数据开辟的buffer数组的地址存放在中控数组的中间元素,如果buffer数组满了要继续尾插,则继续开辟新的buffer数组,此时第二个buffer数组的地址,存放在中控数组中间元素的下一个。如果你要头插,则开辟一个新的buffer数组,并将这个buffer数组的地址存放在中间元素的前一个。

deque的优点:

1.扩容代价小于vector:它只有在中控满了以后才需要扩容,并且不需要拷贝原生数据,只要将中控数组与buffer之间的映射关系拷贝下来即可。

2.因为是一段一段的空间,所以CPU高速缓存命中率高于list。

3.头尾插入删除效率都较高,并且支持随机访问

但是deque也有自己的缺点:

1.随机访问效率不如vector,它的随机访问要通过计算得到,假设每个buffer数组的大小为size,你要访问第10个元素,首先要10/size来确定这个元素在哪一个buffer数组中,再用10%size来确定这个元素在这个buffer数组中的哪个位置,所以deque也不适合排序,因为排序需要大量的随机访问。

2.中间插入删除不如list,它在中间插入删删同样需要挪动一定量的数据

3.迭代器实现复杂:它的迭代器中有四个指针,当cur==last时,说明本段空间被使用完毕++node继续去访问下一段空间,并更新firstlast


四.优先级队列

优先级队列的特点就是优先级高的先出,它也是一个容器适配器,不提供迭代器,底层是一个堆并且默认是大堆。

priority_queue<int> //小堆
priority_queue<int,vector<int>,greater<int>> //大堆

优先级队列中的仿函数

仿函数是一个函数对象,它是一个类函数的对象,要达到类函数,所以这个类中必须要重载()。优先级队列中默认是大堆,如果我们要改成小堆,除了要显示传递第三个参数以外还要更改比较大小的算法。在C语言中,为了能让qsort排序任意类型,库中使用了函数指针的办法,让使用者显示的去写一个比较函数,并将该函数的地址作为参数传递过去。仿函数的一个应用场景就类似于函数指针。

namespace wbm
{
  template<class T>
  struct less //可以使用struct,也可以使用class,它们都是类,只是默认的访问限定不同
  {
    bool operator()(const T& x, const T& y)
    {
      return x < y;
    }
  };
  template <class T>
  struct greater
  {
    bool operator()(const T& x, const T& y)
    {
      return x > y;
    }
  };
}
int main()
{
  wbm::less<int> func;
  //两者等价:func(1, 2)==func.operator()(1, 2);
  return 0;
}

手搓优先级队列

#include<vector>
#include<iostream>
namespace wbm
{
  template<class T>
  struct less //可以使用struct,也可以使用class,它们都是类,只是默认的访问限定不同
  {
    bool operator()(const T& x, const T& y)
    {
      return x < y;
    }
  };
  template <class T>
  struct greater
  {
    bool operator()(const T& x, const T& y)
    {
      return x > y;
    }
  };
  template<class T, class Container =std:: vector<T>,class Compare = less<T>>
  class priority_queue
  {
  private:
    void Adjustdown(size_t parent)
    {
      Compare com;//构造一个仿函数对象
      size_t child = parent * 2 + 1;//先默认是左孩子大,如果右孩子更大,把孩子节点更新成右孩子
      while (child < _con.size())
      {
        if (child+1 < _con.size() && com(_con[child], _con[child + 1]))
        {
          ++child;//默认是less,也就是左孩子比右孩子小
        }
        //将孩子节点和父节点做比较
        if (com(_con[parent], _con[child])) 
        {
          std::swap(_con[parent], _con[child]);
          parent = child;
          child = parent * 2 + 1;
        }
        else
        {
          break;
        }
      }
    }
    void Adjustup(size_t child)
    {
      Compare com;
      size_t parent = (child - 1) / 2;
      while (child > 0)
      {
        if (com(_con[parent], _con[child]))
        {
          std::swap(_con[parent], _con[child]);
          child = parent;
          parent = (child - 1) / 2;
        }
        else
        {
          break;
        }
      }
    }
  public:
    priority_queue()
    {
      //要有一个默认构造,不写就会报错
    }
    //迭代器区间构造,直接构造出一个堆,使用向下调整更佳
    template<class InputIterator>
    priority_queue(InputIterator first, InputIterator last)
      :_con(first, last)  //初始化列表,vector支持迭代器区间构造
    {
      //初始化建堆,使用向下调整,从第一个非叶子节点开始
      for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--)
      {
        Adjustdown(i);
      }
    }
    void push(const T& x)
    {
      _con.push_back(x);
      Adjustup(_con.size() - 1);
    }
    void pop()
    {
      //将首元素和末尾元素换位置删除后调整
      std::swap(_con.front(), _con.back());
      _con.pop_back();
      Adjustdown(0);
    }
    bool empty()const
    {
      return _con.empty();
    }
    size_t size()const
    {
      return _con.size();
    }
    const T& top()const
    {
      return _con.front();
    }
  private:
    Container _con;
  };
}

如果优先级队列中存放的是某个类的地址,又需要我们比较地址中值的优先级,那就需要使用仿函数来进行特殊处理。否则只会按照地址来比较。

五.反向迭代器

反向迭代器采用的是适配器模式,是通过正向迭代器的再封装实现的,你给它某个容器的正向迭代器,它就产生这个容器的反向迭代器,它与正向迭代器的位置是对称的并且正好相反。所以要控制反向迭代器,只需要使用运算符重载,篡改方向迭代器中++--的规则就可以。

reverse_iterator rbegin(){return reverse_iterator(end());}
reverse_iterator rend(){return reverse_iterator(begin());}

rbegin和end一样,指向的是最后一个元素的下一个位置,要想访问到3,要先--再访问,这个问题可以通过重载*来解决

template<class Iterator,class Ref,class Ptr>
Ref operator*()
{
    Iterator tmp=it;
    return *(--tmp);
}

手搓反向迭代器

namespace wbm
{
    //反向迭代器,使用正向迭代器封装
    template<class Iterator,class Ref,class Ptr>  //迭代器,T&/const T&,T*/const T*
    class ReverseIterator
    {
        typedef ReverseIterator<Iterator, Ref, Ptr> Self;
    public:
        ReverseIterator(Iterator it)
            :_it(it)
        {}
        Ref operator*()
        {
            Iterator tmp;
            return *(--tmp);
        }
        Ptr operator->()
        {
            return &(operator*())
        }
        Self& operator++()
        {
            --_it;
            return *this;
        }
        Self& operator--()
        {
            ++_it;
            return *this;
        }
        bool operator!=(const Ref& rit)const//两个迭代器之间的比较
        {
            return _it != rit;
        }
    private:
        Iterator _it;//这里给的参数是一个正向迭代器
    };
}


相关文章
|
3月前
|
设计模式 存储 C++
【C++/STL】:stack/queue的使用及底层剖析&&双端队列&&容器适配器
【C++/STL】:stack/queue的使用及底层剖析&&双端队列&&容器适配器
51 2
|
2月前
|
存储 算法 C语言
【C++】详解STL的适配器容器之一:优先级队列 priority_queue
【C++】详解STL的适配器容器之一:优先级队列 priority_queue
|
2月前
|
设计模式 存储 缓存
【C++】详解STL容器之一的deque和适配器stack,queue
【C++】详解STL容器之一的deque和适配器stack,queue
|
3月前
|
存储 设计模式 算法
【C++航海王:追寻罗杰的编程之路】priority_queue(优先队列) | 容器适配器你知道哪些?
【C++航海王:追寻罗杰的编程之路】priority_queue(优先队列) | 容器适配器你知道哪些?
38 0
|
3月前
|
算法 编译器 Linux
【C++/STL】:vector容器的底层剖析&&迭代器失效&&隐藏的浅拷贝
【C++/STL】:vector容器的底层剖析&&迭代器失效&&隐藏的浅拷贝
28 0
|
14天前
|
Cloud Native 持续交付 Docker
云原生之旅:Docker容器化实战指南
【8月更文挑战第29天】本文将引领你进入云原生技术的世界,以Docker容器化为切入点,深入浅出地介绍如何利用Docker进行应用的打包、部署及管理。我们将通过实际代码示例,一步步展示Docker镜像的构建过程,以及如何运行和管理这些容器。无论你是初学者还是有一定经验的开发者,都能从中获得宝贵的知识和实操经验。
|
9天前
|
NoSQL 关系型数据库 Redis
mall在linux环境下的部署(基于Docker容器),Docker安装mysql、redis、nginx、rabbitmq、elasticsearch、logstash、kibana、mongo
mall在linux环境下的部署(基于Docker容器),docker安装mysql、redis、nginx、rabbitmq、elasticsearch、logstash、kibana、mongodb、minio详细教程,拉取镜像、运行容器
mall在linux环境下的部署(基于Docker容器),Docker安装mysql、redis、nginx、rabbitmq、elasticsearch、logstash、kibana、mongo
|
9天前
|
应用服务中间件 nginx Docker
Docker同一台宿主机容器通信-通过容器名称互联
本文详细介绍了如何通过容器名称实现同一宿主机上容器间的互联,并提供了实战案例。首先,文章解释了容器间通过自定义名称访问的原理,随后演示了创建并连接Tomcat与Nginx容器的具体步骤。此外,还讨论了配置中可能出现的问题及解决方案,包括避免硬编码IP地址和使用自定义容器别名来增强系统的灵活性与可维护性。通过这些实践,展示了如何高效地配置容器间通信,确保服务稳定可靠。
16 1
Docker同一台宿主机容器通信-通过容器名称互联
|
7天前
|
Cloud Native 持续交付 Docker
云原生技术实践:Docker容器化部署教程
【9月更文挑战第4天】本文将引导你了解如何利用Docker这一云原生技术的核心工具,实现应用的容器化部署。文章不仅提供了详细的步骤和代码示例,还深入探讨了云原生技术背后的哲学,帮助你理解为何容器化在现代软件开发中变得如此重要,并指导你如何在实际操作中运用这些知识。
|
9天前
|
存储 Unix 虚拟化
Docker容器简介
Docker是一种轻量级的虚拟化技术,它通过容器化应用,提高了硬件资源利用率,简化了应用的部署、运输和运行,且与虚拟机相比,具有更快的交付速度和更低的资源消耗。
24 2