栈的基本概念介绍
一、栈的核心定义与特性
栈 是一种操作受限的线性数据结构。它只允许在一端进行数据的插入(入栈,Push)和删除(出栈,Pop)操作。这一端被称为栈顶,另一端则称为栈底。
它遵循 LIFO 原则,即 后进先出。这是栈最核心、最基本的特性。
- 后进:最后一个被添加进栈的元素。
- 先出:这个最后进入的元素,将成为第一个被移除的元素。
生活比喻:
- 一摞盘子:你只能从最顶部拿走(Pop)一个盘子,也只能将新的盘子放在(Push)这摞盘子的最顶部。
- 弹夹:子弹被一颗颗压入(Push)弹夹,射击时最后压入的那颗子弹最先被打出(Pop)。
二、栈的基本操作
栈的核心操作通常有以下几种:
- Push (入栈):向栈顶添加一个新元素。
- 操作:检查栈是否已满(对于固定大小的栈),然后将栈顶指针向上移动一位,在新位置存入数据。
- Pop (出栈):移除并返回栈顶元素。
- 操作:检查栈是否为空,然后返回当前栈顶指针所指的元素,再将指针向下移动一位。
- Peek / Top (查看栈顶):返回栈顶元素但不移除它。
- 操作:仅读取栈顶指针所指的元素,不移动指针。
- isEmpty (判空):检查栈是否为空。
- isFull (判满):检查栈是否已满(主要针对基于数组的实现)。
三、栈的实现方式
栈是一种逻辑结构,可以用两种主要的物理结构来实现:
特性 | 基于数组的实现 | 基于链表的实现 |
---|---|---|
结构 | 使用连续的内存块 | 使用通过指针连接的节点 |
大小 | 固定大小,可能发生栈溢出 | 动态大小,理论上只要内存足够就可以扩展 |
性能 | 所有操作的时间复杂度都是 O(1),因为直接通过索引访问 | 所有操作的时间复杂度也是 O(1),因为只操作链表头部 |
优点 | 实现简单,访问速度快,没有额外指针开销 | 灵活,无需预先指定大小,不会出现“栈满”(除非内存耗尽) |
缺点 | 有最大容量限制,可能浪费空间 | 每个元素需要额外空间存储指针 |
选择依据:如果需要确定性和高性能,且能预估最大大小,用数组。如果需要灵活性,不确定数据量大小,用链表。
四、栈的深远应用
栈不仅仅是教科书上的一个数据结构,更是计算机科学无处不在的基石。
-
函数调用(调用栈)
- 这是栈最核心、最重要的应用。 当一个程序调用一个函数时,操作系统/运行时环境会创建一个栈帧并将其压入调用栈。
- 栈帧中存储了:
- 函数的返回地址(执行完后回到哪里)
- 函数的参数
- 函数的局部变量
- 上一栈帧的指针等上下文信息
- 过程:主函数
main()
调用funcA()
,funcA
的栈帧被压入栈顶。funcA()
又调用funcB()
,funcB
的栈帧再被压入。当funcB()
执行完毕,它的栈帧出栈,CPU回到funcA()
的栈帧继续执行。这完美契合了LIFO原则。 - 递归:递归函数本质上就是通过调用栈来实现的。每一次递归调用都会压入一个新的栈帧。如果递归深度太大,会导致栈溢出错误。
-
表达式求值(语法分析)
- 编译器使用栈来计算表达式(如
3 + 4 * (5 - 2)
)或进行语法检查(如括号匹配)。 - 后缀表达式(逆波兰表示法)求值:遇到操作数就入栈;遇到运算符就从栈顶弹出两个操作数进行计算,将结果再压回栈中。最后栈里剩下的就是最终结果。
- 中缀转后缀:也需要用到栈来处理运算符的优先级。
- 括号匹配:遍历字符串,遇到左括号
(
、{
、[
就入栈;遇到右括号,就弹出栈顶元素看是否匹配。最后栈为空则匹配成功。
- 编译器使用栈来计算表达式(如
-
撤销机制
- 几乎所有编辑软件(如文本编辑器、Photoshop)的“撤销”功能都是用栈实现的。
- 你的每一个操作(键入文字、绘制笔画)都被作为一个状态或命令对象压入栈中。
- 当你执行“撤销”时,就是将栈顶的命令弹出并执行其反向操作。“重做”功能通常会用另一个栈来存储被撤销的操作。
-
浏览器的前进与后退
- 使用两个栈:
- 后退栈:当你访问新页面时,URL被压入后退栈。
- 前进栈:当你点击“后退”按钮时,从后退栈弹出当前页面,并将其压入前进栈;点击“前进”时则相反。
- 使用两个栈:
-
内存管理(栈内存)
- 在程序的内存布局中,专门有一块区域叫做调用栈,用于存储函数调用时的局部变量和上下文信息。这与第1点紧密相关。
- 栈内存 的特性是自动分配和释放(通过移动栈指针),效率极高。与之相对的是堆内存,需要手动管理(如C中的
malloc/free
,C++中的new/delete
)。
五、深入:硬件与系统层面的栈
栈的概念也深深植根于计算机硬件和操作系统设计中。
- 线程栈:操作系统为每个线程分配一个独立的栈空间。这确保了线程在执行函数调用时互不干扰。
- 中断处理:当硬件发生中断时,CPU会自动将当前程序的上下文(如寄存器状态)压入内核栈,处理完中断后再弹出恢复,从而保证被中断的程序能继续正常运行。
- 栈指针寄存器:CPU中有一个专门的寄存器(如x86架构的
ESP
,ARM架构的SP
)来永远指向当前栈顶的位置。任何入栈和出栈操作都通过移动这个寄存器来完成。
总结
层面 | 栈的角色 | 关键点 |
---|---|---|
数据结构 | 一种抽象的线性表 | LIFO原则,Push/Pop操作,数组/链表实现 |
算法应用 | 解决问题的工具 | 函数调用、表达式求值、括号匹配、回溯算法 |
系统内存 | 内存布局的一部分 | 存储局部变量、函数调用上下文,自动管理 |
硬件底层 | CPU直接支持的功能 | 栈指针寄存器,中断处理,程序执行的基础 |
总而言之,栈是计算机科学中一个极其基础且强大的概念,它从最底层的硬件设计到顶层的应用开发无处不在,是理解程序如何运行的关键之一。
补充
栈底和栈顶:其中栈底的地址始终不变,为栈最高地址位。栈顶是指针SP指向的位置。栈顶的地址。
Variable n | 栈顶 | |
… | ||
FFFE_FFF8 | Variable1 | |
FFFF_0000 | Variable0 | 栈底 |