目录
[2.3 栈的初始化](#2.3 栈的初始化)
[2.4 栈的销毁](#2.4 栈的销毁)
[2.5 入栈](#2.5 入栈)
[2.6 出栈](#2.6 出栈)
[2.7 判空](#2.7 判空)
[2.8 栈的大小](#2.8 栈的大小)
[2.9 访问栈顶元素](#2.9 访问栈顶元素)
[2.10 出栈的打印](#2.10 出栈的打印)
1.栈的简介
栈是一种数据结构,是一种特殊的线性表,
栈只允许在一端进行插入和删除数据的操作

进行数据插入和删除操作的一端称为栈顶 ,另一端称为栈底
栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
可以把栈理解为一个垃圾桶,先丢进去的垃圾在桶底,
后丢进去的东西会覆盖在表面(即在桶顶)
只有先倒出桶顶的垃圾,才能倒出桶底的垃圾
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶出栈:栈的删除操作叫做出栈。出数据也在栈顶

2.栈的实现
2.1实现须知
前面说了,栈是是一种线性表,所以既可以用数组实现栈 也可以用链表实现栈
但是,数组的缓存利用较高,所以选择用数组实现栈
显然,实践中都是按需申请空间,所以使用动态数组实现栈
2.2头文件预览
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
//初始化
void StackInit(ST* st);
//销毁
void StackDestroy(ST* st);
//入栈
void StackPush(ST* st, STDataType val);
//出栈
void StackPop(ST* st);
//判空
bool STEmpty(ST* st);
//大小
int STSize(ST* st);
//访问栈顶元素
STDataType STTop(ST* st);
2.2.1头文件细节
(1)防止头文件内容在同一个编译单元内被多次展开
#pragma once
(2)包含必要的头文件(库函数、开空间、断言、布尔值)
(3)数据类型重命名,(以后只改这一处,就可以改变栈所存储的数据类型)
typedef int STDataType;
(4)与顺序表相同,多个数据存储在结构体中,其中
变量a,指向堆上一块用来存储数据的空间,
变量top,标明栈顶元素的位置或栈顶元素位置的下一个位置 (依照具体实现而定)
变量capacity,标明栈的容量
(5)结构体typedef一下,不然名字太长了
(6)关于函数接口的设计一:用户在函数外可以直接创建一个栈对象,然后传入它的地址,各函数用一级指针接收后,在函数内部使用指针就可以改变栈对象
二:如果用户在函数外创建的是一个栈类型的指针,初始化函数体内部就malloc一个栈对象,然后要么返回malloc后的指针,要么参数使用二级指针,以达到目的
方法一可以使各接口参数列表基本一致,所以我选择它(个人按需选择)
2.3 栈的初始化
void StackInit(ST* st)
{
//传入的结构体指针一定不为空
assert(st);
//为栈存储数据开好空间,初始化的时候未指向有效内存,可以直接使用st->a
st->a = (STDataType*)malloc(sizeof(STDataType) * 4);
if (st->a == NULL)
{
perror("malloc fail");
exit(1);
}
st->capacity = 4;
st->top = 0; //栈顶元素的下一个位置,站的元素个数
}
(1)结构体指针一定不为空 ,所以加断言
(1)最开始结构体中的变量a并未指向有效内存,可以直接用它接收返回值
(1)注意空间开辟失败的问题
top的初始值一般常取0 或1(1)top == 0
此时 top 指向的是栈顶元素的下一个位置,因为
当第一个元素入栈时,top++,top的值就是1,正好是该元素下标的下一个位置
(2)top == -1
此时 top 指向的是栈顶元素的位置,因为
当第一个元素入栈时,top++,top的值就是0,正好是该元素的下标
2.4 栈的销毁
void StackDestroy(ST* st)
{
assert(st);
free(st->a);
st->a = NULL;
st->capacity = st->top = 0;
}
断言结构体指针,释放堆上申请的空间,还原成空对象即可
2.5 入栈
void StackPush(ST* st, STDataType val)
{
assert(st);
if (st->top == st->capacity)
{
STDataType* temp = (STDataType*)realloc(st->a, sizeof(STDataType) * st->capacity * 2);
if (temp == NULL)
{
perror("realloc fail");
exit(1);
}
st->a = temp;//谨防内存泄漏,需要使用临时变量
st->capacity *= 2;
}
st->a[st->top] = val;
st->top++;
}
(1)老生常谈,断言结构体指针
(2)入栈之前,判断一下扩容操作
1.top的值正好是栈中元素的个数,当个数等于容量时就进行扩容
2.可以进行2倍扩容,并注意使用临时变量接收返回值,防止接受失败导致内存泄漏
(3)top初始值是0,指向栈顶元素的下一个位置,那么直接在top位置插入元素
然后top++
2.6 出栈
void StackPop(ST* st)
{
assert(st);
assert(!STEmpty(st));
st->top--;
}
(1)结构体指针不为空,栈中必须有元素才能进行出栈操作
(2)堆上申请的是一段连续的空间,并不支持一段空间释放,
只要不太浪费内存,没必要重新开空间存储剩下的数据,直接top--
2.7 判空
bool STEmpty(ST* st)
{
assert(st);
return st->top == 0;
}
(1)判空结构体指针
(2)top的值就是栈中元素的个数
2.8 栈的大小
int STSize(ST* st)
{
assert(st);
return st->top;
}
(1)判空结构体指针
(2)top的值就是栈中元素的个数
2.9 访问栈顶元素
STDataType STTop(ST* st)
{
assert(st);
assert(!StackEmpty(st));
return st->a[st->top - 1];
}
(1)判空结构体指针
(2)top的初始值是0,是栈顶元素下标的后一个位置
2.10 栈的打印
栈并不支持遍历访问,只能访问栈顶元素,即不提供遍历访问的接口
想要知道所有元素
只有边访问栈顶元素,边出栈
当然,如果需要保留元素,可提前保存一下
ST st;
StackInit(&st);
StackPush(&st, 1);
StackPush(&st, 2);
StackPush(&st, 3);
StackPush(&st, 4);
StackPush(&st, 5);
StackPush(&st, 6);
while (!STEmpty(&st))
{
printf("%d ", STTop(&st));
StackPop(&st);
}