跟我学C++中级篇——STL的容器适配器

217 篇文章 28 订阅

 

一、容器适配器

适配器(Adapter),顾名思意,就是适合配合的一个东西。举一个比较容易接受的例子,家里都有插线板对吧。电器和手机有各种形式的用电方式,有两头的,有三头的,还有空调专用的,还有USB的等等。原来没有新型插线板以前,是不是经常找插线板来适合你的用电插头?但是自从小米等互联网公司推出了插线板,是不是可以在一个插线板上实现各种用电的方式接入呢?那么这个插线板就相当于一个适配器。一个用电插头的适配器,原来功能少,这个适配器就弱小,功能越来越强大,那么这个适配器越来越受欢迎。每一个或者说每类插口就相当于软件设计中的接口。
适配器在STL也有几种,比如迭代器的适配器,适配各种不同需求的迭代器;容器的迭代器,适配不同的容器接口在上层“形成另外一种”容器;还有仿函数适配器。
本篇主要讲解容器适配器即:栈(stack)、队列(queue)、Priority queue(优先级队列)。

二、栈和队列

栈,如果是计算机专业的就非常简单了,一个LIFO(后进先出)的数据结构,什么是后进先出呢?非专业出身的,可以想想小时候儿给玩具枪弹匣压子弹,是不是最后压入的子弹会先打出来。这个弹匣就是一个栈的模型。而队列正如相反,是一个FIFO(先进先出)的数据结构,常见的排队买东西,上车就是这一典型的例子。排在前面的先买或者先上车。而有优先级的队列就更容易理解了,VIP呗。
相对于前面的基本容器,栈和队列更容易让非专业人士接受,毕竟在现实情况中经常遇到类似的情况,估计这也是STL费劲巴拉的搞这两个数据结构的原因。栈通过适配队列产生,而队列通过适配deque产生。
栈的操作比较少,只有有限的几个大小、空、入栈、出栈还有交换。其中有两个还是c++11以后才引入的,队列也是如此。从这一点上也可以看出,其实越向上适配,会发现提供的接口越简单,这也符合人们的认知范畴,比如造汽车很复杂,可以越是组合,到最后交付到客户手中,只要懂得开车的几个方法就可以了,而不用去学习各种复杂的修车甚至造车手段。

三、源码分析

其源码定义为:

template <typename T, typename Container = deque<T> > class stack;
template <typename T, typename Container = deque<T> > class queue;
template <typename T,typename Container =vector<T>,typename Compare = less<typename Container::value_type>> class priority_queue;

看一下源码:

template <class _Ty, class _Container>
class queue {
public:
    using value_type      = typename _Container::value_type;
    using reference       = typename _Container::reference;
    using const_reference = typename _Container::const_reference;
    using size_type       = typename _Container::size_type;
    using container_type  = _Container;

    static_assert(is_same_v<_Ty, value_type>, "container adaptors require consistent types");

    queue() = default;

    explicit queue(const _Container& _Cont) : c(_Cont) {}

    explicit queue(_Container&& _Cont) noexcept(is_nothrow_move_constructible_v<_Container>) // strengthened
        : c(_STD move(_Cont)) {}

    template <class _Alloc, enable_if_t<uses_allocator_v<_Container, _Alloc>, int> = 0>
    explicit queue(const _Alloc& _Al) noexcept(is_nothrow_constructible_v<_Container, const _Alloc&>) // strengthened
        : c(_Al) {}

    template <class _Alloc, enable_if_t<uses_allocator_v<_Container, _Alloc>, int> = 0>
    queue(const _Container& _Cont, const _Alloc& _Al) : c(_Cont, _Al) {}

    template <class _Alloc, enable_if_t<uses_allocator_v<_Container, _Alloc>, int> = 0>
    queue(_Container&& _Cont, const _Alloc& _Al) noexcept(
        is_nothrow_constructible_v<_Container, _Container, const _Alloc&>) // strengthened
        : c(_STD move(_Cont), _Al) {}

    template <class _Alloc, enable_if_t<uses_allocator_v<_Container, _Alloc>, int> = 0>
    queue(const queue& _Right, const _Alloc& _Al) : c(_Right.c, _Al) {}

    template <class _Alloc, enable_if_t<uses_allocator_v<_Container, _Alloc>, int> = 0>
    queue(queue&& _Right, const _Alloc& _Al) noexcept(
        is_nothrow_constructible_v<_Container, _Container, const _Alloc&>) // strengthened
        : c(_STD move(_Right.c), _Al) {}

    _NODISCARD bool empty() const noexcept(noexcept(c.empty())) /* strengthened */ {
        return c.empty();
    }

    _NODISCARD size_type size() const noexcept(noexcept(c.size())) /* strengthened */ {
        return c.size();
    }

    _NODISCARD reference front() noexcept(noexcept(c.front())) /* strengthened */ {
        return c.front();
    }

    _NODISCARD const_reference front() const noexcept(noexcept(c.front())) /* strengthened */ {
        return c.front();
    }

    _NODISCARD reference back() noexcept(noexcept(c.back())) /* strengthened */ {
        return c.back();
    }

    _NODISCARD const_reference back() const noexcept(noexcept(c.back())) /* strengthened */ {
        return c.back();
    }

    void push(const value_type& _Val) {
        c.push_back(_Val);
    }

    void push(value_type&& _Val) {
        c.push_back(_STD move(_Val));
    }

    template <class... _Valty>
    decltype(auto) emplace(_Valty&&... _Val) {
#if _HAS_CXX17
        return c.emplace_back(_STD forward<_Valty>(_Val)...);
#else // ^^^ C++17 or newer / C++14 vvv
        c.emplace_back(_STD forward<_Valty>(_Val)...);
#endif // _HAS_CXX17
    }

    void pop() noexcept(noexcept(c.pop_front())) /* strengthened */ {
        c.pop_front();
    }

    void swap(queue& _Right) noexcept(_Is_nothrow_swappable<_Container>::value) {
        _Swap_adl(c, _Right.c);
    }

    // clang-format off
    friend bool operator== <>(const queue&, const queue&);
    friend bool operator!= <>(const queue&, const queue&);
    friend bool operator<  <>(const queue&, const queue&);
    friend bool operator>  <>(const queue&, const queue&);
    friend bool operator<= <>(const queue&, const queue&);
    friend bool operator>= <>(const queue&, const queue&);
    // clang-format on

protected:
    _Container c{};
};

#if _HAS_CXX17
template <class _Container, enable_if_t<!_Is_allocator<_Container>::value, int> = 0>
queue(_Container) -> queue<typename _Container::value_type, _Container>;

template <class _Container, class _Alloc,
    enable_if_t<
        conjunction_v<negation<_Is_allocator<_Container>>, _Is_allocator<_Alloc>, uses_allocator<_Container, _Alloc>>,
        int> = 0>
queue(_Container, _Alloc) -> queue<typename _Container::value_type, _Container>;
#endif // _HAS_CXX17

template <class _Ty, class _Container, enable_if_t<_Is_swappable<_Container>::value, int> = 0>
void swap(queue<_Ty, _Container>& _Left, queue<_Ty, _Container>& _Right) noexcept(noexcept(_Left.swap(_Right))) {
    _Left.swap(_Right);
}

template <class _Ty, class _Container, class _Alloc>
struct uses_allocator<queue<_Ty, _Container>, _Alloc> : uses_allocator<_Container, _Alloc>::type {};

// CLASS TEMPLATE priority_queue
template <class _Ty, class _Container = vector<_Ty>, class _Pr = less<typename _Container::value_type>>
class priority_queue {
public:
    using value_type      = typename _Container::value_type;
    using reference       = typename _Container::reference;
    using const_reference = typename _Container::const_reference;
    using size_type       = typename _Container::size_type;
    using container_type  = _Container;
    using value_compare   = _Pr;

    static_assert(is_same_v<_Ty, value_type>, "container adaptors require consistent types");

    priority_queue() = default;

    explicit priority_queue(const _Pr& _Pred) noexcept(
        is_nothrow_default_constructible_v<_Container>&& is_nothrow_copy_constructible_v<value_compare>) // strengthened
        : c(), comp(_Pred) {}

    priority_queue(const _Pr& _Pred, const _Container& _Cont) : c(_Cont), comp(_Pred) {
        _STD make_heap(c.begin(), c.end(), comp);
    }

    priority_queue(const _Pr& _Pred, _Container&& _Cont) : c(_STD move(_Cont)), comp(_Pred) {
        _STD make_heap(c.begin(), c.end(), comp);
    }

    template <class _InIt>
    priority_queue(_InIt _First, _InIt _Last, const _Pr& _Pred, const _Container& _Cont) : c(_Cont), comp(_Pred) {
        c.insert(c.end(), _First, _Last);
        _STD make_heap(c.begin(), c.end(), comp);
    }

    template <class _InIt>
    priority_queue(_InIt _First, _InIt _Last) : c(_First, _Last), comp() {
        _STD make_heap(c.begin(), c.end(), comp);
    }

    template <class _InIt>
    priority_queue(_InIt _First, _InIt _Last, const _Pr& _Pred) : c(_First, _Last), comp(_Pred) {
        _STD make_heap(c.begin(), c.end(), comp);
    }

    template <class _InIt>
    priority_queue(_InIt _First, _InIt _Last, const _Pr& _Pred, _Container&& _Cont) : c(_STD move(_Cont)), comp(_Pred) {
        c.insert(c.end(), _First, _Last);
        _STD make_heap(c.begin(), c.end(), comp);
    }

    template <class _Alloc, enable_if_t<uses_allocator_v<_Container, _Alloc>, int> = 0>
    explicit priority_queue(const _Alloc& _Al) noexcept(is_nothrow_constructible_v<_Container, const _Alloc&>&&
            is_nothrow_default_constructible_v<value_compare>) // strengthened
        : c(_Al), comp() {}

    template <class _Alloc, enable_if_t<uses_allocator_v<_Container, _Alloc>, int> = 0>
    priority_queue(const _Pr& _Pred, const _Alloc& _Al) noexcept(is_nothrow_constructible_v<_Container, const _Alloc&>&&
            is_nothrow_copy_constructible_v<value_compare>) // strengthened
        : c(_Al), comp(_Pred) {}

    template <class _Alloc, enable_if_t<uses_allocator_v<_Container, _Alloc>, int> = 0>
    priority_queue(const _Pr& _Pred, const _Container& _Cont, const _Alloc& _Al) : c(_Cont, _Al), comp(_Pred) {
        _STD make_heap(c.begin(), c.end(), comp);
    }

    template <class _Alloc, enable_if_t<uses_allocator_v<_Container, _Alloc>, int> = 0>
    priority_queue(const _Pr& _Pred, _Container&& _Cont, const _Alloc& _Al) : c(_STD move(_Cont), _Al), comp(_Pred) {
        _STD make_heap(c.begin(), c.end(), comp);
    }

    template <class _Alloc, enable_if_t<uses_allocator_v<_Container, _Alloc>, int> = 0>
    priority_queue(const priority_queue& _Right, const _Alloc& _Al) : c(_Right.c, _Al), comp(_Right.comp) {}

    template <class _Alloc, enable_if_t<uses_allocator_v<_Container, _Alloc>, int> = 0>
    priority_queue(priority_queue&& _Right, const _Alloc& _Al) noexcept(
        is_nothrow_constructible_v<_Container, _Container, const _Alloc&>&&
            is_nothrow_move_constructible_v<value_compare>) // strengthened
        : c(_STD move(_Right.c), _Al), comp(_STD move(_Right.comp)) {}

    _NODISCARD bool empty() const noexcept(noexcept(c.empty())) /* strengthened */ {
        return c.empty();
    }

    _NODISCARD size_type size() const noexcept(noexcept(c.size())) /* strengthened */ {
        return c.size();
    }

    _NODISCARD const_reference top() const noexcept(noexcept(c.front())) /* strengthened */ {
        return c.front();
    }

    void push(const value_type& _Val) {
        c.push_back(_Val);
        _STD push_heap(c.begin(), c.end(), comp);
    }

    void push(value_type&& _Val) {
        c.push_back(_STD move(_Val));
        _STD push_heap(c.begin(), c.end(), comp);
    }

    template <class... _Valty>
    void emplace(_Valty&&... _Val) {
        c.emplace_back(_STD forward<_Valty>(_Val)...);
        _STD push_heap(c.begin(), c.end(), comp);
    }

    void pop() {
        _STD pop_heap(c.begin(), c.end(), comp);
        c.pop_back();
    }

    void swap(priority_queue& _Right) noexcept(
        _Is_nothrow_swappable<_Container>::value&& _Is_nothrow_swappable<_Pr>::value) {
        _Swap_adl(c, _Right.c);
        _Swap_adl(comp, _Right.comp);
    }

protected:
    _Container c{};
    _Pr comp{};
};

#if _HAS_CXX17
template <class _Pr, class _Container,
    enable_if_t<conjunction_v<negation<_Is_allocator<_Pr>>, negation<_Is_allocator<_Container>>>, int> = 0>
priority_queue(_Pr, _Container) -> priority_queue<typename _Container::value_type, _Container, _Pr>;

template <class _Iter, class _Pr = less<_Iter_value_t<_Iter>>, class _Container = vector<_Iter_value_t<_Iter>>,
    enable_if_t<conjunction_v<_Is_iterator<_Iter>, negation<_Is_allocator<_Pr>>, negation<_Is_allocator<_Container>>>,
        int> = 0>
priority_queue(_Iter, _Iter, _Pr = _Pr(), _Container = _Container())
    -> priority_queue<_Iter_value_t<_Iter>, _Container, _Pr>;

template <class _Pr, class _Container, class _Alloc,
    enable_if_t<conjunction_v<negation<_Is_allocator<_Pr>>, negation<_Is_allocator<_Container>>, _Is_allocator<_Alloc>,
                    uses_allocator<_Container, _Alloc>>,
        int> = 0>
priority_queue(_Pr, _Container, _Alloc) -> priority_queue<typename _Container::value_type, _Container, _Pr>;
#endif // _HAS_CXX17

template <class _Ty, class _Container, class _Pr,
    enable_if_t<_Is_swappable<_Container>::value && _Is_swappable<_Pr>::value, int> = 0>
void swap(priority_queue<_Ty, _Container, _Pr>& _Left, priority_queue<_Ty, _Container, _Pr>& _Right) noexcept(
    noexcept(_Left.swap(_Right))) {
    _Left.swap(_Right);
}

template <class _Ty, class _Container, class _Pr, class _Alloc>
struct uses_allocator<priority_queue<_Ty, _Container, _Pr>, _Alloc> : uses_allocator<_Container, _Alloc>::type {};

这里把整个Queue的源码拷了过来,才二百来行,可以看到其实他的应用是相当简单的,不过在优先队列里使用了heap算法,这个在后面会进行分析,此处可以先掠过。

四、例程

看一个例程:

#include <stack>
#include <queue>
#include <iostream>

template <typename T>
void Add(T& t)
{
    for (int num = 0; num < 10; num++)
    {
        t.emplace(num);
    }

}
template <typename T>
void Top(T& t)
{
    if (t.size() > 0)
    {
        std::cout << "top value is:" << t.top() << std::endl;
        t.pop();
    }
}
void TestStack()
{
    std::stack<int> s;
    Add(s);
    Top(s);

}
void TestQu()
{
    std::queue<int> q;
    Add(q);
    if (!q.empty())
    {
        std::cout << "front value is:" << q.front() << std::endl;
        q.pop();
    }
}
void TestPriorityQueue()
{
    std::priority_queue<int> pq;
    Add(pq);
    Top(pq);
}
void TestAdapter()
{
    TestStack();
    TestQu();
    TestPriorityQueue();
}
int main()
{
    TestAdapter();
    return 0;
}

运行结果如下:

top value is:9
front value is:0
top value is:9

接口很少就易于学习,易于学习就易于应用,所以这两个容器其实在初学者手里用得还是相当的多。

五、总结

通过上述的分析,是不是可以自己开发一个适配器容器呢?可以试着做一下,正如古人言:“行胜于言”,这对于学计算机的人来说,更是如此。一个设计模式的引入,就可以产生一个新的数据结构或者说新的应用容器,其实就是对自己理解和学习技术的一个深入的总结和应用,不要害怕失败和重复造轮子,大胆去做,才能成功。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值