2026/5/13 15:27:58
网站建设
项目流程
网页设计精选网站,网页制作优质网站,谷歌账号注册,胶南网站建设哪家好#x1f3e0;个人主页#xff1a;黎雁 #x1f3ac;作者简介#xff1a;C/C/JAVA后端开发学习者 ❄️个人专栏#xff1a;C语言、数据结构#xff08;C语言#xff09;、EasyX、游戏、规划、程序人生 ✨ 从来绝巘须孤往#xff0c;万里同尘即玉京 文章目录栈与队列之栈…个人主页黎雁作者简介C/C/JAVA后端开发学习者❄️个人专栏C语言、数据结构C语言、EasyX、游戏、规划、程序人生✨ 从来绝巘须孤往万里同尘即玉京文章目录栈与队列之栈入门攻略从核心概念到数组实现✨文章摘要一、知识回顾线性表的核心本质二、栈的核心概念什么是“后进先出”1. 栈的定义2. 栈的关键操作术语3. 栈的实现方式对比数组 vs 链表三、栈的工程化实现数组版栈完整代码1. 头文件定义stack.h接口声明2. 源文件实现stack.c核心逻辑1栈的初始化2栈的销毁3入栈操作压栈4出栈操作弹栈5获取栈顶元素6获取栈大小 判断栈为空四、核心设计思想低耦合、高内聚思考为什么要把StackSize、StackEmpty封装成函数五、写在最后栈与队列之栈入门攻略从核心概念到数组实现✨你好欢迎来到线性表系列的全新篇章——栈与队列篇的第一讲。在前面的内容中我们已经吃透了顺序表和链表这两大基础线性表从底层实现到实战刷题完成了从理论到实践的跨越。而今天要学习的栈作为一种特殊的线性表是数据结构世界里不可或缺的核心角色它的“后进先出”特性在算法解题、工程开发中都有着广泛应用比如括号匹配、函数调用栈、表达式求值等场景都离不开它。准备好了吗让我们一起解锁栈的核心知识从概念到实现彻底掌握这个“个性十足”的线性表文章摘要本文聚焦线性表中的栈结构系统讲解栈的核心概念、“后进先出”特性及数组/链表两种实现方式的优劣对比最终选择数组作为最优实现方案。通过完整的工程化代码头文件源文件详细拆解栈的初始化、销毁、入栈、出栈等核心操作补充top指针两种初始化方式的关键细节结合软件工程“低耦合、高内聚”思想解析代码设计夯实栈的底层实现基础。阅读时长约20分钟阅读建议基础薄弱者先吃透栈的核心特性再对照代码理解实现逻辑工程开发者重点关注数组扩容、内存释放等细节借鉴代码封装思想面试备考者牢记栈的特性、实现方式及典型应用场景查漏补缺者直接查看代码实现中的关键注释和易错点一、知识回顾线性表的核心本质在学习栈之前我们先回顾下线性表的核心特征线性表是数据元素呈线性排列的结构每个元素有唯一的前驱和后继除首尾顺序表物理存储连续随机访问快但插入删除效率低链表物理存储不连续插入删除效率高但随机访问慢而栈正是基于线性表实现的受限数据结构——它只允许在一端进行操作这也让它拥有了独一无二的特性二、栈的核心概念什么是“后进先出”1. 栈的定义栈是一种特殊的线性表只允许在固定的一端进行插入和删除操作遵循后进先出LIFO Last In First Out原则。举个生活中的例子往弹夹里装子弹后装进去的子弹先被打出来 → 这就是栈的“后进先出”叠盘子最后叠上去的盘子最先被拿走 → 完美契合栈的特性2. 栈的关键操作术语操作名称说明操作位置压栈/入栈/进栈向栈中添加元素栈顶唯一操作端出栈/弹栈从栈中删除元素栈顶唯一操作端栈顶允许操作的这一端栈的“顶端”-栈底固定不动的另一端栈的“底部”-3. 栈的实现方式对比数组 vs 链表栈有两种主流实现方式我们来详细对比优劣选择最优方案实现方式核心思路优点缺点数组推荐用数组尾端作为栈顶入栈尾插、出栈尾删① 操作效率O(1) ② 随机访问快 ③ 实现简单空间不足时需要扩容可通过预分配缓解链表① 单链表头节点作为栈顶头插头删② 双向链表尾节点作为栈顶无需扩容按需分配内存① 单链表需额外处理指针 ② 双向链表实现复杂 ③ 缓存命中率低✅结论数组实现栈是最优选择数组尾插尾删的时间复杂度都是O(1)完全匹配栈的操作需求虽然存在扩容成本但扩容频率低通常按2倍扩容整体效率远高于链表符合《深入理解计算机系统》中“局部性原理”缓存命中率更高三、栈的工程化实现数组版栈完整代码我们采用“头文件源文件”的工程化结构实现栈保证代码的规范性和可维护性。1. 头文件定义stack.h接口声明头文件负责定义栈的结构体和函数接口是栈的“对外接口规范”#pragmaonce#includestdio.h#includestdbool.h#includeassert.h#includestdlib.h// 定义栈存储的数据类型方便后续修改typedefintSTDataType;// 栈的结构体定义数组实现typedefstructStack{STDataType*a;// 存储数据的数组指针inttop;// 栈顶指针关键intcapacity;// 数组的容量}ST;// 栈的核心操作接口voidStackInit(ST*ps);// 初始化栈voidStackDestroy(ST*ps);// 销毁栈释放内存voidStackPush(ST*ps,STDataType x);// 入栈压栈voidStackPop(ST*ps);// 出栈弹栈STDataTypeStackTop(ST*ps);// 获取栈顶元素intStackSize(ST*ps);// 获取栈中元素个数boolStackEmpty(ST*ps);// 判断栈是否为空2. 源文件实现stack.c核心逻辑源文件负责实现头文件声明的函数是栈的“内部实现细节”1栈的初始化#includestack.hvoidStackInit(ST*ps)// 初始化栈{assert(ps);// 确保传入的栈指针不为NULL// 初始分配4个元素的空间可根据需求调整ps-a(STDataType*)malloc(sizeof(STDataType)*4);if(ps-aNULL)// 内存分配失败处理{perror(malloc fail);exit(1);// 终止程序}ps-capacity4;// 初始容量为4ps-top0;// 关键top0表示指向栈顶元素的下一个位置// 补充若top-1表示指向栈顶元素本身两种方式都可需统一逻辑}关键细节top指针的两种初始化方式top初始值含义入栈逻辑出栈逻辑栈顶元素获取0指向栈顶元素的下一个位置ps-a[ps-top] x; ps-topps-top–ps-a[ps-top-1]-1指向栈顶元素本身ps-top; ps-a[ps-top] xps-top–ps-a[ps-top]2栈的销毁voidStackDestroy(ST*ps)// 销毁栈必须避免内存泄漏{assert(ps);free(ps-a);// 释放数组内存ps-aNULL;// 置空指针防止野指针ps-top0;// 重置栈顶ps-capacity0;// 重置容量}3入栈操作压栈voidStackPush(ST*ps,STDataType x)// 入栈尾插{assert(ps);// 扩容判断栈满时扩容2倍扩容if(ps-topps-capacity){STDataType*tmp(STDataType*)realloc(ps-a,ps-capacity*2*sizeof(STDataType));if(tmpNULL){perror(realloc fail);exit(1);}ps-atmp;ps-capacity*2;// 容量翻倍}// 入栈核心逻辑ps-a[ps-top]x;ps-top;}4出栈操作弹栈voidStackPop(ST*ps)// 出栈尾删{assert(ps);assert(!StackEmpty(ps));// 栈空时禁止出栈ps-top--;// 只需移动top指针无需真正删除数据覆盖即可}5获取栈顶元素STDataTypeStackTop(ST*ps)// 获取栈顶元素{assert(ps);assert(!StackEmpty(ps));// 栈空时禁止获取returnps-a[ps-top-1];// 对应top0的初始化方式}6获取栈大小 判断栈为空intStackSize(ST*ps)// 获取栈中元素个数{assert(ps);returnps-top;// top的值就是元素个数top0的情况}boolStackEmpty(ST*ps)// 判断栈是否为空{assert(ps);returnps-top0;// top0 → 空栈}四、核心设计思想低耦合、高内聚思考为什么要把StackSize、StackEmpty封装成函数可能有同学会问这两个函数逻辑简单直接在测试代码里写ps-top 0不就行了这就涉及到软件工程的核心思想——低耦合、高内聚高内聚栈的所有操作逻辑都集中在stack.c中对外只暴露统一接口便于维护和修改比如后续把top初始值改成-1只需修改栈内部代码无需改测试代码低耦合测试代码只需调用接口无需关心栈的内部实现细节降低代码之间的依赖鲁棒性函数内部有断言检查如assert(ps)能提前发现错误而直接写表达式无法做到简单来说封装是为了让代码更健壮、更易维护五、写在最后恭喜你在这一篇中你已经掌握了栈的核心知识理解了栈“后进先出”的核心特性对比了数组/链表两种实现方式选择了最优方案完成了栈的工程化代码实现掌握了top指针的关键细节理解了“低耦合、高内聚”的软件工程思想栈作为基础数据结构是后续学习的重要铺垫算法层面括号匹配、表达式求值、DFS深度优先搜索都离不开栈工程层面函数调用栈、浏览器前进后退功能都基于栈实现下一篇我们将学习栈的“好搭档”——队列它遵循“先进先出”原则和栈形成完美互补。敬请期待点赞收藏关注跟着系列内容一步步吃透数据结构你的支持是我创作的最大动力