【C++】容器适配器(三)
3.容器适配器所有容器都会存在迭代器失效3.1 什么是适配器容器适配器的作用可以概括为将一个通用的底层容器转换为一个具有特定行为接口的专用数据结构。具体来说提供特定的语义stack 实现 LIFO后进先出只允许在容器尾部插入、删除、访问。queue 实现 FIFO先进先出只允许尾部插入、头部删除和访问。priority_queue 实现 按优先级取出默认最大堆每次取出的都是当前最大或最小元素。限制接口隐藏不相关操作底层容器如 deque、vector通常支持迭代器、随机访问、任意位置插入删除等灵活操作。适配器只暴露 push、pop、top/front/back 等少量方法避免使用者误用底层容器的其他功能保证数据结构的逻辑正确性。提高代码复用与灵活性不用为每一种特殊结构重新实现容器只要选择一个合适的底层容器deque、vector、list通过适配器“包装”即可。可以在模板参数中指定底层容器类型例如 stackint, vector根据需要权衡性能如 vector 可能比deque 更快但扩容代价不同。统一接口降低学习成本所有容器适配器都有相似的成员函数push、pop、empty、size 等使用者容易上手。总结容器适配器的作用是行为转换——将普通容器的能力裁剪、封装成符合栈、队列等特定逻辑的接口从而在保证语义正确的同时复用现有容器的实现。3.2 STL标准库中stack和queue的底层结构使用时要包含#includedeque虽然stack和queue中也可以存放元素但在STL中并没有将其划分在容器的行列而是将其称为容器适配器这是因为stack和队列只是对其他容器的接口进行了包装STL中stack和queue默认使用deque比如3.3 deque的简单介绍(了解)3.3.1 deque的原理介绍deque(双端队列)是一种双开口的连续空间的数据结构双开口的含义是可以在头尾两端进行插入和删除操作且时间复杂度为O(1)与vector比较头插效率高不需要搬移元素与list比较空间利用率比较高。deque并不是真正连续的空间而是由一段段连续的小空间拼接而成的实际deque类似于一个动态的二维数组其底层结构如下图所示双端队列底层是一段假象的连续空间实际是分段连续的为了维护其“整体连续”以及随机访问的假象落在了deque的迭代器身上因此deque的迭代器设计就比较复杂如下图所示那deque是如何借助其迭代器维护其假想连续的结构呢底层源码node是二级指针node指向自己buffer数组在中控数组里的位置用deque用cur指针进行比较begin()函数返回start迭代器end()同理3.3.2 deque的缺陷1.与vector比较deque的优势是头部插入和删除时不需要搬移元素效率特别高而且在扩容时也不需要搬移大量的元素因此其效率是必vector高的。2.与list比较其底层是连续空间空间利用率比较高不需要存储额外字段。但是deque有一个致命缺陷不适合遍历因为在遍历时deque的迭代器要频繁的去检测其是否移动到某段小空间的边界导致效率低下而序列式场景中可能需要经常遍历因此在实际中需要线性结构时大多数情况下优先考虑vector和listdeque的应用并不多而目前能看到的一个应用就是STL用其作为stack和queue的底层数据结构。在开启编译器优化如 Release 模式后对 std::vector 进行排序几乎总是比 std::deque 快。这种性能差距并非源于排序算法本身而是由两者底层的内存布局决定的。就算先拷贝到vector再用vector的sort函数排序再拷贝回deque也比deque自带的sort函数快3.4 为什么选择deque作为stack和queue的底层默认容器stack是一种后进先出的特殊线性数据结构因此只要具有push_back()和pop_back()操作的线性结构都可以作为stack的底层容器比如vector和list都可以queue是先进先出的特殊线性数据结构只要具有push_back和pop_front操作的线性结构都可以作为queue的底层容器比如 list。但是STL中对stack和queue默认选择deque作为其底层容器主要是因为stack和queue不需要遍历(因此stack和queue没有迭代器)只需要在固定的一端或者两端进行操作。在stack中元素增长时deque比vector的效率高(扩容时不需要搬移大量数据)queue中的元素增长时deque不仅效率高而且内存使用率高。3.5 STL标准库中对于stack和queue的模拟实现3.5.1 stack的模拟实现从栈的接口中可以看出栈实际是一种特殊的vector因此使用vector完全可以模拟实现stack。#includevector#includedequenamespacebit{templateclassT,classContainerdequeT//templateclass T, class Con vectorT//templateclass T, class Con listTclassstack{public:voidpush(constTx){_con.push_back(x);}//后进来的先删voidpop(){_con.pop_back();}constTtop(){return_con.back();}size_tsize()const{return_con.size();}boolempty()const{return_con.empty();}private:Container _con;};}3.5.2 queue的模拟实现因为queue的接口中存在头删和尾插因此使用vector来封装效率太低故可以借助list来模拟实现queue具体如下#includelist#includedequenamespacebit{templateclassT,classCondequeT//templateclass T, class Con listTclassqueue{public:queue(){}voidpush(constTx){_c.push_back(x);}voidpop(){_c.pop_front();}Tback(){return_c.back();}constTback()const{return_c.back();}Tfront(){return_c.front();}constTfront()const{return_c.front();}size_tsize()const{return_c.size();}boolempty()const{return_c.empty();}private:std::listT_c;};}