STL 中 stack 与 queue 本质是容器适配器,基于基础容器封装实现特定操作逻辑。本文先介绍容器适配器及二者核心概念,再手动模拟实现,最后通过几道算法题展示其应用,助力夯实 STL 设计思想与数据结构基础。
容器适配器
什么是容器适配器?
适配器可以理解为'转换器'——它能把原本不兼容、不符合需求的对象(比如函数、容器等),改造为可以直接使用、适配特定场景的形式。 容器适配器是适配器的一种,它专门用来包装底层容器(例如 deque、vector):通过屏蔽底层容器的复杂接口,只暴露栈、队列等特定数据结构的核心操作(比如 stack 的 push/pop,queue 的 front/back),让我们可以直接按照经典数据结构的逻辑来使用它。
为啥容器配置器不支持迭代器
stack:仅允许操作栈顶(遵循后进先出 LIFO 规则),若暴露迭代器,用户就能遍历栈内所有元素,这会破坏它的设计意图。 queue:仅允许操作队首 / 队尾(遵循先进先出 FIFO 规则),迭代器会打破'队列只能从两端操作'的限制。 priority_queue:仅允许操作优先级最高的元素(堆顶),遍历其底层存储(比如用 vector 实现的堆)没有实际意义 —— 底层存储的顺序并非优先级顺序。 容器适配器的价值,正是用极简的接口解决特定场景的问题(比如栈只需要 push/pop/top)。而迭代器是'通用遍历'的工具,和适配器'限制访问'的核心目标完全相悖,因此二者天生不兼容。 在 C++ STL 中,容器适配器只有上述 3 种,本文我们只讲解前两种。
stack
1、stack 介绍
stack 的定位:stack 是一种容器适配器,专门用于'后进先出(LIFO)'的场景,元素的插入和提取操作只能在容器的一端进行。 stack 的实现逻辑:作为容器适配器,stack 是通过包装某一底层容器实现的 —— 它会以该容器为基础,提供一组特定的成员函数来访问元素;其中,底层容器的尾部会被当作栈顶,元素的压入、弹出操作都在这一端完成。 stack 对底层容器的要求:stack 的底层容器可以是任意标准容器模板,或是满足以下操作的特定容器:
**empty:**判断容器是否为空 **back:**获取容器尾部的元素 **push_back:**在容器尾部插入元素 **pop_back:**删除容器尾部的元素 默认底层容器:标准容器 vector、deque、list 都满足上述要求;若未显式指定 stack 的底层容器,默认会使用 deque。
2、stack 模拟实现
namespace bit { // 容器适配器:模拟实现 stack(LIFO 后进先出) // T:元素类型;Container:底层存储容器,默认 deque<T> template<class T, class Container = deque<T>> class stack { public: // 压栈:将 x 插入栈顶 void push(const T& x) { _con.push_back(x); } // 出栈:删除栈顶元素(不返回值) void pop() { _con.pop_back(); } // 获取栈顶元素(const 引用返回,避免拷贝) const T& top() { return _con.back(); } // 判断栈是否为空 bool empty() { return _con.empty(); } // 获取栈中元素个数 size_t size() { return _con.size(); } private: Container _con; // 底层容器,实际存储数据 }; }
deque 是双端队列,虽名字带'队列',但和 FIFO 结构的队列无实际关联。它是支持头尾高效增删、可随机访问的双端动态容器,是 STL 中灵活高效的基础容器之一。目前大家可以先把它当作类似 vector 和 list 的容器来理解。 **Container _con:**是 stack 的底层容器对象,实际存储所有元素;stack 的所有操作都是通过封装这个容器的接口实现的(体现'适配器'的封装思想)。
接口介绍:
**push(const T& x):**调用底层容器_con 的 push_back,把元素 x 插入到栈顶(对应容器尾部)。 **pop():**调用_con 的 pop_back,删除栈顶元素(不返回值)。 **const T& top():**返回_con.back(),即栈顶元素的 const 引用(避免拷贝,也防止外部修改)。 **empty():**返回_con.empty() 的结果,判断栈是否为空。 **size():**返回_con.size(),获取栈中元素的个数。
问题:为啥 stack 不用提供默认成员函数?
回答:因为编译器会自动为 stack 生成默认成员函数(包括默认构造、拷贝构造、赋值重载、析构函数等),而这些自动生成的函数会调用底层容器(如 deque)的对应函数 —— 比如默认构造调用容器的无参构造、拷贝构造调用容器的拷贝构造,能完整实现 stack 的初始化、拷贝、资源释放等需求。同时 stack 是容器适配器,自身不管理额外动态资源,所有功能都依赖底层容器,因此无需手动提供默认成员函数。


