基于动态数组的栈(顺序栈)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的绝对禁区),会直接拦住你,而不会破坏别人的数据。

相关推荐
nazisami1 小时前
红黑树详解
数据结构·c++·面向对象·红黑树
Chen_harmony1 小时前
十八、C语言内存函数
c语言·算法
雁迟1 小时前
第九章:列表 List 数据类型
数据结构·r语言
m0_547486661 小时前
郑州轻工业大学《数据结构》期末试卷及答案2018-2022学年(AB卷)
数据结构·期末试卷·郑州轻工业大学
__Coffee__1 小时前
封装矩阵结构体
线性代数·算法·矩阵
变量未定义~2 小时前
字符串哈希匹配字符串
数据结构·算法·哈希算法
周末也要写八哥2 小时前
浅谈二叉树的深度优先搜索(DFS)算法
算法·深度优先
y = xⁿ2 小时前
20天速通LeetCodeday17:一维动态规划
算法
bnmoel2 小时前
数据结构深度剖析栈与队列:结构、边界实现与进出操作全解析
c语言·数据结构·算法··队列