基于动态数组的栈(顺序栈)01

操作名称 英文术语 它的作用 现实比喻
入栈 Push 把数据放到栈顶 往箱子里放进一本书
出栈 Pop 把栈顶的数据扔掉 把箱子最上面那本书拿走
取栈顶 Top / Peek 只看一眼栈顶的数据,不扔 看看箱子最上面那本书的书名
判空 Empty 检查栈里还有没有数据 看看箱子是不是空的
求大小 Size 看看栈里一共有几个数据 数数箱子里一共几本书

"既然栈的底层是个数组,那我为什么不直接用 arr[2]arr[5] 去随便读取修改中间的数据呢?为什么要死死限制住,只允许在'栈顶'这一个地方进出呢?"

答案是:限制即是特征(Restriction is a Feature)

可以说 学习栈 前置条件是动态数组 在限制 或者新增一些功能来实现复杂

更多功能,完成特定需求的数据结构

1 从头文件开始 stack.h

  • 关于 top = 0 :你敏锐地发现了 top 的值恰好就是有效个数 。因为数组下标是从 0 开始的,所以当 top 指向下标 n(也就是下一个空位置)的时候,前面正好塞满了 n 个有效数据。

  • 关于 top = -1 :你抓住了"下标与个数差 1"的核心特性。既然 top 死死盯着最后一个有效数据,那数据的总个数自然就是 top + 1,反过来说也就是:当前指向的下标 = 有效个数 - 1。

我们通常不用纠结它具体落在元素内部哪个字节,只需要知道它保存首地址,并且它的运算遵循"按类型步长移动"的规则。

这是一个非常直击本质的好问题!在 C/C++ 的内存管理中,理解这两行代码的先后关系,就等于掌握了防范内存泄漏和野指针的终极法则。

为了把这个讲透,我们需要分清两个概念:"内存空间""记录地址的指针(小纸条)"

我们用"退订酒店房间"来打个比方。


第一步:free(pst->arr); ------ 退还房间(释放内存)

  • 它的作用free 的工作是告诉操作系统(酒店前台):"这块动态申请的内存(酒店房间),我用完了,现在还给你,你可以把它租给别人了。"

  • 背后的真相free 只会 释放那块内存空间,但它绝对不会 去修改 pst->arr 这个变量里的值。

  • 危险诞生 :执行完 free 之后,pst->arr 里面依然保存着刚才那个房间的门牌号(内存地址)!此时的 pst->arr 就变成了一个野指针(悬空指针 Dangling Pointer)。这就像是你虽然退了房,但手里还偷偷捏着写有房间号的小纸条。

第二步:pst->arr = NULL; ------ 烧毁小纸条(指针置空)

  • 它的作用 :把 pst->arr 的值改成 NULL(地址 0x0,表示"哪儿也不指")。

  • 为什么要这么做 :既然房间已经退了,如果后续代码不小心又写了一句 pst->arr[0] = 10;(按照纸条上的房间号跑回去放东西),程序就会立刻崩溃,因为你侵入了不属于你的系统内存。

  • 安全保障 :如果把它置为 NULL,在 C 语言中,对 NULL 指针进行读写操作,程序会稳定地报出"空指针异常(段错误)"并立即停止。这比"悄悄修改了别人的内存却不知道"要安全、好排查得多。这相当于你退房后,把写着房间号的小纸条烧毁了,彻底断绝了走错房间的可能性。


🌟 核心反问:如果把顺序反过来会怎样?

这是理解先后顺序的最佳方式。假设我们先写第二步,再写第一步:

C

复制代码
// ❌ 灾难性的错误写法
pst->arr = NULL;   // 1. 先把地址清空(烧毁小纸条)
free(pst->arr);    // 2. 然后再去释放内存(去前台退房)

会发生什么?

  1. 你先执行了 pst->arr = NULL;,此时你彻底忘记了当初申请的那块内存在哪(弄丢了房间号)。

  2. 接着执行 free(pst->arr);,由于此时 pst->arr 已经是 NULL 了,相当于你执行了 free(NULL);。在 C 语言标准中,free(NULL) 是一个空操作,什么都不会发生。

  3. 结果:内存泄漏(Memory Leak)。 那块真正的内存空间永远留在了堆区,操作系统以为你还在用,你却再也找不到它了,直到你的程序结束,这块内存都被白白浪费着。

总结

  • 必须先 free:因为必须用指针里存的地址去找到那块内存,把它还给系统。

  • 必须后 NULL:因为还给系统后,那个地址就成了"毒药",必须马上清空指针,防止以后误食。

free(pst->arr) 执行的前后,到底发生了什么?

状态一:free 之前(岁月静好)

假设你在程序里写了 pst->arr = (int*)malloc(16);

  1. 你的口袋里(变量所在处): 有一张小纸条,名字叫 pst->arr,上面写着"储物柜编号:0x112233"。

  2. 储物柜(堆区内存): 0x112233 号柜子上挂着一把锁,系统(操作系统/C标准库)的账本上写着:"0x112233柜子,已被当前程序租用。"

  3. 你可以随时拿着纸条,去打开这个柜子存取数据。

状态二:执行 free(pst->arr) 的那一瞬间(只管杀)

当你调用这个函数时,系统底层(比如 Linux 下的 ptmalloc 内存管理器)会做以下几件事:

  1. 它顺着你提供的 0x112233 编号,找到那个柜子。

  2. 它在自己的系统账本 上,把"0x112233"的状态从"已分配"划掉,改写为"空闲 (Free)"。

  3. 柜子上的锁被系统收回了,柜子变成了无主状态,随时可以租给下一个来申请内存的程序。

状态三:free 之后的第一秒(悬空诞生 / 不管埋)

重点来了!系统在做完上面那些事之后,直接拍拍屁股走人了

绝对不会做以下两件事(这就是所谓的"不管埋"):

  1. 不清理柜子里的东西: 柜子里原来存的数字(比如 10, 20),通常还躺在里面,直到被下一个租客覆盖(这就叫内存里的"脏数据")。

  2. 不没收你的小纸条: 你的变量 pst->arr 里面,依然清清楚楚地写着"0x112233"!

✨ 悬空指针(Dangling Pointer)在此刻正式诞生:

纸条(指针)还是那张纸条,上面的编号也毫无变化,但它指向的那个物理实体(内存归属权)已经不属于你了。你拿着一张旧纸条,指着一个已经被系统收回的储物柜,这就是"悬空"。

如果在 free 之后你没有执行 pst->arr = NULL;,那么在后续的代码中,如果你(或者别人)不小心写了一句 pst->arr[0] = 100;

这就相当于你按照旧纸条上的地址,强行撬开了一个可能已经租给别人的柜子,并往里面塞了东西。系统一旦发现,就会毫不留情地把你的程序"当场击毙"(报出 Segmentation Fault 段错误)。

所以,pst->arr = NULL; 就是在"杀"完之后,亲手把那张作废的纸条烧掉。 这样哪怕以后误操作,也找不到那个柜子了,系统一看你是去试图打开 NULL (编号0的绝对禁区),会直接拦住你,而不会破坏别人的数据。

相关推荐
wabs6663 小时前
关于图论【卡码网117.软件构建的思考】
数据结构·算法·软件构建·图论·卡码网
mifengxing3 小时前
LeetCode 41.缺失的第一个正数|Hard题O(n)+O(1)最优解法深度解析
java·算法·leetcode·排序算法
Tongzhi20264 小时前
从部署到运维:通芝科技无感考勤一体机的全流程效率解析
运维·数据结构·科技·算法·贪心算法
Wang's Blog4 小时前
AI Agent白手起家30: 动态示例选择器之根据长度选择 Few Shot 示例
算法
oyguyteggytrrwwwrt6 小时前
自制交叉线路识别算法
算法
手写码匠6 小时前
华为云Flexus+DeepSeek征文|Dify 多智能体协同编排实战:R1 规划 + V3 执行,构建企业 Agent 团队
人工智能·深度学习·算法·aigc
晓天衡宇•评测社区7 小时前
FSR-Bench 前沿科学推理榜单发布:GPT-5.5 居首,“会推理”未必“能答对”
算法
程序喵大人8 小时前
【C++进阶】STL算法与函数对象 - 02 sort为什么需要随机访问迭代器
开发语言·c++·算法
hanlin038 小时前
动态规划专练:力扣第121、122题
笔记·算法·leetcode
To_OC8 小时前
LC 74 搜索二维矩阵:换皮的二分查找,我居然一开始没看出来
javascript·算法·leetcode