STL精讲:stack容器适配器
大家好,这里是彩妙呀~

今天彩妙带着大家来聊聊STL里的stack容器。
目录
emplace():高级点的push() --- C++11新增
stack的介绍 --- 参考文档
如果你刷过 C++ 相关的面试题,那对括号匹配、表达式求值这类题目肯定不陌生。在这些高频面试题里,stack 往往是解题的关键。
stack底层逻辑是一个叫做栈的数据结构,遵循“后进先出(LIFO)”的原则。而stack的底层实现是依靠容器适配器来实现的。具体可以看下面的博客:
C++:吃透容器适配器
https://blog.csdn.net/weixin_66776566/article/details/157982117?spm=1001.2014.3001.5502总的来看,由于stack是依靠容器适配器来实现(简单的说,stack本质上是基于其他容器来实现的),通过对stack这个类的接口进行封装,从而来表现出栈这个数据结构的特性。
有关stack支持不同传参的可以看下面的博客:
c++:详解模版 从初阶到进阶的掌握
https://blog.csdn.net/weixin_66776566/article/details/156945543?spm=1001.2014.3001.5502学会模版时理解stack底层的关键。
stack 容器的快速上手
在官方文档中,stack是这么定义的:
template <class T, class Container = deque<T> > class stack;
显而易见,stack在官方定义中,使用类模版来方便泛型编程(也就是支持不同种类的传参,生成对应的类),而官方是选择deque来作为stack的容器适配器。
在官方中,他为stack的描述:
LIFO(后进先出)栈
栈是一种容器适配器,专门设计用于后进先出(LIFO)的操作场景,在这种场景下,元素只能从容器的同一端进行插入和提取。栈作为容器适配器实现,容器适配器是使用特定容器类的封装对象作为其底层容器的类,并提供一组特定的成员函数来访问其元素。元素从特定容器的"尾部"被压入或弹出,这一端被称为栈顶。
底层容器可以是任何标准容器类模板,或其他专门设计的容器类。该容器需要支持以下操作:
empty(判空)
size(大小)
back(访问尾部元素)
push_back(尾部插入)
pop_back(尾部删除)
标准容器类 vector、deque 和 list 均满足这些要求。默认情况下,如果实例化栈时未指定容器类,则使用标准容器 deque。
stack的头文件与容器的声明
要使用 stack,首先需要包含它的头文件:
#include <stack>
注意:stack是一个容器适配器,所以声明一个stack类时需要指定他的元素类型,也可以指定他的底层容器(不设置默认为deque):
//stack命名的格式
std::stack<T, Container> stk;
// 默认使用 deque 存储 int
std::stack<int> st1;
// 使用 vector 作为底层容器
std::stack<double, std::vector<double>> st2;
// 使用 list 作为底层容器
std::stack<char, std::list<char>> st3;
T:存储在栈中的元素类型(如int、string等)。
Container:底层容器的类型,可以是std::deque<T>(默认)、std::vector<T>或std::list<T>。只要该容器支持back()、push_back()、pop_back()等操作即可。注意:在指定底层容器是,最好要与他指定的元素类型适配,不然会出现编译错误。
当stack中指定元素类型是一个类时(例如string是容器,但底层是类),如果这个类是我们自己写的类,后面的底层逻辑也要匹配适合的容器才行
stack中的核心接口

上面就是stack中所有的接口,第一个就是构造函数,没啥可以说的,从第二个以后来讲:
empty():判空接口

官方定义:
bool empty() const;
官方解释:
- 测试容器是否为空
- 返回栈是否为空:即其大小是否为零。
- 此成员函数实际上调用了底层容器对象的成员函数 empty 。
// stack::empty --- 官方代码示例
#include <iostream> // std::cout
#include <stack> // std::stack
int main ()
{
std::stack<int> mystack;
int sum (0);
for (int i=1;i<=10;i++) mystack.push(i);
while (!mystack.empty())
{
sum += mystack.top();
mystack.pop();
}
std::cout << "total: " << sum << '\n';
return 0;
}
size():获取stack的大小
官方定义:
size_type size() const;
官方解释:
- 返回大小
- 返回栈中的元素数量。
- 此成员函数实际上调用了底层容器对象的成员 size 函数。
官方代码示例:
// stack::size
#include <iostream> // std::cout
#include <stack> // std::stack
int main ()
{
std::stack<int> myints;
std::cout << "0. size: " << myints.size() << '\n';
for (int i=0; i<5; i++) myints.push(i);
std::cout << "1. size: " << myints.size() << '\n';
myints.pop();
std::cout << "2. size: " << myints.size() << '\n';
return 0;
}
top():获取栈顶元素
官方定义:
value_type& top();
const value_type& top() const;
官方解释:
- 返回栈顶元素的引用。
- 由于栈是后进先出的容器,所以栈顶元素是最后插入栈中的元素。
- 此成员函数实际上调用了底层容器对象的成员回调函数(以vector来解释,就是返回最后一个值的引用)。
官方代码定义:
// stack::top
#include <iostream> // std::cout
#include <stack> // std::stack
int main ()
{
std::stack<int> mystack;
mystack.push(10);
mystack.push(20);
mystack.top() -= 5;
std::cout << "mystack.top() is now " << mystack.top() << '\n';
return 0;
}
push():向栈中增加元素(栈顶加入元素)
官方定义:
void push (const value_type& val);
官方解释:
- 插入元素
- 将一个新元素插入到栈的顶部,位于当前栈顶元素之上。
- 此新元素的内容会被初始化为 val 的副本。
- 此成员函数实际上调用了底层容器对象的成员函数“push_back”。
官方代码定义:
// stack::push/pop
#include <iostream> // std::cout
#include <stack> // std::stack
int main ()
{
std::stack<int> mystack;
for (int i=0; i<5; ++i) mystack.push(i);
std::cout << "Popping out elements...";
while (!mystack.empty())
{
std::cout << ' ' << mystack.top();
mystack.pop();
}
std::cout << '\n';
return 0;
}
emplace():高级点的push() --- C++11新增
官方定义:
template <class... Args> void emplace (Args&&... args);
官方解释:
- 构造和插入元素
- 在堆栈的顶部添加一个新元素,位于其当前顶部元素的上方。
- 这个新元素是通过传递参数作为其构造函数的参数而就地构造的。
- 这个成员函数有效地调用了底层容器的成员函数emplace_back,转发参数。
官方代码定义:
// stack::emplace
#include <iostream> // std::cin, std::cout
#include <stack> // std::stack
#include <string> // std::string, std::getline(string)
int main ()
{
std::stack<std::string> mystack;
mystack.emplace ("First sentence");
mystack.emplace ("Second sentence");
std::cout << "mystack contains:\n";
while (!mystack.empty())
{
std::cout << mystack.top() << '\n';
mystack.pop();
}
return 0;
}
这里emplace与push一样的作用,区别在于前者在stack中插入类类型的元素时效率比较高,除此之外没啥别的特点。
pop():删除栈顶元素
官方定义:
void pop();
官方解释:
- 移除顶部元素
- 移除栈顶的元素,相当于使栈的大小减少一个单元。
- 被移除的元素是刚刚插入到栈中的最后一个元素,其值可以通过调用“stack::top”成员函数来获取。
- 这会调用被移除元素的析构函数(如果元素是类元素)。
- 此成员函数实际上调用了底层容器对象的成员函数“pop_back”。
官方代码定义:
// stack::push/pop
#include <iostream> // std::cout
#include <stack> // std::stack
int main ()
{
std::stack<int> mystack;
for (int i=0; i<5; ++i) mystack.push(i);
std::cout << "Popping out elements...";
while (!mystack.empty())
{
std::cout << ' ' << mystack.top();
mystack.pop();
}
std::cout << '\n';
return 0;
}
使用stack时的常见误区
空栈直接调用 top () / pop ()
在使用stack时,最常见的错误之一就是在空栈上调用top()或pop()函数 。
这两个函数在栈为空时被调用,会导致程序崩溃,因为这属于未定义行为 。例如:
std::stack<int> stk;
int topElement = stk.top(); // 错误:空栈调用top(),未定义行为
stk.pop(); // 错误:空栈调用pop(),未定义行为
正确的做法是在调用top()和pop()之前,先使用empty()函数检查栈是否为空。
std::stack<int> stk;
if (!stk.empty()) {
int topElement = stk.top();
stk.pop();
}
忽视 top () 返回引用的修改风险
top()函数返回的是栈顶元素的引用,这意味着通过这个引用对栈顶元素进行赋值操作,会直接修改栈内的数据 :
std::stack<int> stk;
stk.push(10);
stk.top() = 20; // 直接修改栈顶元素,此时栈顶元素变为20
如果我们只想读取栈顶元素而非修改时,可以加const加以修饰:
const int topElement = stk.top(); // 此时topElement为只读,无法修改栈顶元素
也可以使用转接对象(就是加一个临时值来代替top):
int topElementCopy = stk.top(); // 拷贝栈顶元素的值,不会影响栈内数据
stack 没有 clear () 函数
stack并没有内置的clear()成员函数,这是很多初学者容易混淆的地方 。
当需要清空stack时,常见的做法是循环调用pop()函数,直到栈为空 。例如:
std::stack<int> stk;
stk.push(1);
stk.push(2);
stk.push(3);
while (!stk.empty()) {
stk.pop();
}
另一种方式是利用stack的作用域生命周期 。如果stack是局部变量,当它离开作用域时,会自动调用析构函数,从而清空栈中的所有元素(stack的析构函数是调用其适配器的析构函数) :
{
std::stack<int> stk;
stk.push(1);
stk.push(2);
stk.push(3);
} // stk离开作用域,自动析构,栈被清空
当然,我们也可以自己写一个stack,自己创建clear(),但本质上自己创建clear()这个接口底层也是逐一pop()值,只是封装起来从而实现代码复用。
试图用迭代器遍历 stack
stack不支持迭代器,这是由它的设计逻辑决定的 。
stack的核心特性是后进先出,为了严格保证这一特性,它不允许对元素进行随机访问和遍历 。如果尝试使用迭代器来遍历stack,会发现根本无法实现,因为stack类并没有提供相关的接口 (自己实现的stack除外)。
当你想要遍历stack时,可以考虑把stack的值逐个赋予到另一个匹配的容器。
但你这么做,显然要评估自己是否选择了合适的数据结构,这种情况主在人为。
小结
在 C++ STL 中,stack 作为一种独特的容器适配器,闪耀着“后进先出”的简洁光芒。它并非独立容器,而是对底层容器(如 deque、vector 或 list)的巧妙封装,通过限制接口(仅从一端操作)呈现出栈的行为。这种适配器模式不仅体现了 STL 的高度复用性,也让我们能按需选择底层容器以优化性能。
stack 的接口极为精炼:push(压入栈顶)、pop(弹出栈顶)、top(访问栈顶)、empty(判空)和 size(获取大小)。所有操作均为 O(1) 时间复杂度,确保了高效处理。设计上,stack 秉持“极简接口 + 严格行为约束”的原则,像一位严谨的管家,确保数据的有序性和一致性。
正是这种专注,让 stack 在括号匹配、表达式求值、函数调用栈等“后进先出”场景中大放异彩——它总能精准地按照压入顺序反向处理,成为算法实现中不可或缺的基础工具。
本篇到这里就结束了,喜欢文章的小伙伴可以关注一下彩妙,我们下一篇再见~

更多推荐
所有评论(0)