目录
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。
栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则,也可以叫后进先出。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。


注:这只是栈空间中数据大概的一个出入顺序,下图为详细的栈区增删。

内存中的栈区空间是相当有限的,原因是内存大多空间都给了堆区,堆区是动态开辟的,空间分配的利用上比堆区要高很多,所以我们实现的大多程序都是动态开辟类型的。
栈区空间在内存是自顶向下开辟的:

正因为栈内存空间有限,那么在进行函数递归的时候,若是递归次数过多,会把栈空间放满从而系统会崩溃,崩溃的原因就是栈溢出(stack overflow) 例:
- int f(int n) {
- return n == 1 ? 1 : f(n - 1) + f(n - 2);
- }
- int main() {
- printf("%d\n", f(10000));
-
- return 0;
- }
测试结果:

栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些,因为数组在尾上插入数据的代价比较小,而且CPU在数组上的高速缓存命中率比在链表上更高。所以下面我对于栈的实现方式是以顺序表为主体的。
对于顺序表的讲解和操作实现我已经写了一篇博客,地址在这: 数据结构——线性表之顺序表的基本操作讲解 大家想深入了解数据结构顺序表的实现可以看一看。
静态版:一般不用,空间固定,不灵活。
- //静态不常用
- typedef int STDataType;
- #define N 100
- struct Stack {
- STDataType a[N];
- int top;
- };
动态版:比静态版的空间利用效率更高。
- //动态
- typedef int STDataType; //重定义int类型为栈类型的
- typedef struct Stack {
- STDataType* a; //栈型指针——指向堆区空间的
- int top; //栈现有的个数
- int capacity; //栈区的容量
- }ST; //将栈的结构体类型名改为ST,简化
- void StackInit(ST* ps)
- {
- assert(ps); //断言——判断实参传过来的指针是否为空
- ps->a = NULL; //先暂时让栈的指针指向NULL
- ps->top = ps->capacity = 0;
- }
- void StackDestroy(ST* ps)
- {
- assert(ps);
- free(ps->a); //free掉动态开辟的空间
- ps->a = NULL; //free掉空间后,这块堆区空间还在,只是还给了操作系统,而且栈的指针a还保存
- //这块空间的地址,a成为了野指针,很危险,需要置为空
- ps->capacity = ps->top = 0; //将容量和存储个数设为0
- void StackPush(ST* ps, STDataType x)
- {
- assert(ps);
- // 扩容
- if (ps->top == ps->capacity)
- {
- //扩容一般扩2倍
- int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
-
- STDataType* tmp = (STDataType*)realloc(ps->a, newCapacity*sizeof(STDataType));
-
- //判断是否开辟成功,若开辟失败则报错
- if (tmp == NULL)
- {
- perror("realloc fail");
- exit(-1);
- }
-
- //表示开辟失败
- ps->a = tmp;
- ps->capacity = newCapacity;
- }
-
- //入栈,即在栈中存入要存的数据
- ps->a[ps->top] = x;
- ps->top++; //栈空间的数据个数+1
- }
注:栈的结构成员top表示栈中数据存储的个数,栈区为空时,Top指针指向栈底,因为顺序表是以数组为核心实现的,所以Top也就是数组的下标了。每存储一个数据Top都指向这个数据的下一个数据开始位置。

在栈区空间的数据删除时,需要进行链表空间的判断,若栈空间为空,则不允许再删除了,所以要实现一个函数去检查。
注:VS中的C文件里,并不支持bool类型,所以需要在.c文件开头加入#include
- bool StackEmpty(ST* ps)
- {
- assert(ps);
- return ps->top == 0;
- }
- void StackPop(ST* ps)
- {
- assert(ps);
-
- //判断栈区空间是否为空,若为空,则报错,不为空,则存储个数-1
- assert(!StackEmpty(ps));
-
- --ps->top;
- }
栈的操作实现使用顺序表的原因:删除数据方式极为简单方便!
- STDataType StackTop(ST* ps)
- {
- assert(ps);
- assert(!StackEmpty(ps));
-
- return ps->a[ps->top - 1];
- }
刚才讲到了栈结构的成员变量top是从数组下标0开始访问的,访问到的数据,所得下标top要减一。
访问该数据时, 函数返回的是Top指向的上一个数据的数值。
top-1原因:数据1,2,3,4,5入栈后,如下图 :

当要访问数据4时,需要数据5出栈,才能访问到4,而top是下标,数据5对应的数组下标为4( top),那么下标3 ( top-1)的位置就是数据4,所以return ps->a[ps->top-1] ; 数据4即可被访问到。
- int StackSize(ST* ps)
- {
- assert(ps);
-
- return ps->top;
- }

Test.c:
- #define _CRT_SECURE_NO_WARNINGS 1
- #include"Stack.h"
-
- void TestStack() {
- ST st;
- StackInit(&st);
- StackPush(&st, 1);
- StackPush(&st, 2);
- StackPush(&st, 3);
- printf("%d\n", StackTop(&st));
-
- StackPop(&st);
- printf("%d\n", StackTop(&st));
- StackPop(&st);
-
- StackPush(&st, 4);
- StackPush(&st, 5);
-
- while (!StackEmpty(&st))
- {
- printf("%d ", StackTop(&st));
- StackPop(&st);
- }
- printf("\n");
- }
-
- //int f(int n) {
- // return n == 1 ? 1 : f(n - 1) + f(n - 2);
- //}
- int main() {
- //printf("%d\n", f(10000));
- TestStack();
- return 0;
- }
Stack.c文件:
- #define _CRT_SECURE_NO_WARNINGS 1
- #include"Stack.h"
- //初始化
- void StackInit(ST* ps) {
- assert(ps);
- ps->a = NULL;
- ps->top = ps->capacity = 0;
- }
-
- //入栈
- void StackPush(ST* ps, STDataType x) {
- assert(ps);
- if (ps->top == ps->capacity) {
- //创建临时变量,若top==capacity说明两种情况
- //情况1:栈表刚开始,容量和数据个数都为0,先开个4字节的容量
- //情况2:说明栈的存储个数达到容量上限,需要扩容了
- int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
- //上面这个语句设置了情况二所说的扩容倍数大小为2倍
- //扩容
- STDataType* tmp = (STDataType*)realloc(ps->a, newcapacity * sizeof(STDataType));
- if (tmp == NULL) {
- perror("realloc fail");
- exit(-1);
- }
- ps->a = tmp;
- ps->capacity = newcapacity;
-
- }
- //将数据入栈
- ps->a[ps->top] = x;
- //个数+1
- ps->top++;
- }
-
- //出栈
- void StackPop(ST* ps) {
- assert(ps);
- assert(!StackEmpty(ps));
- ps->top--;
- }
-
- //暴力检查
- bool StackEmpty(ST* ps) {
- assert(ps);
- return ps->top == 0;
- }
- //计算栈表长度
- int StackSize(ST* ps) {
- assert(ps);
- return ps->top;
- }
-
- //访问栈中某个数据
- STDataType StackTop(ST* ps) {
- assert(ps);
- assert(!StackEmpty(ps));
- return ps->a[ps->top - 1];
- }
Stack.h文件:
- #pragma once
- #include
- #include
- #include
- #include
-
- //静态不常用
- //typedef int STDataType;
- //#define N 100
- //struct Stack {
- // STDataType a[N];
- // int top;
- //};
-
- //动态
- typedef int STDataType;
- typedef struct Stack {
- STDataType* a; //栈型指针——指向堆区空间的
- int top; //栈现有的个数
- int capacity; //栈区的容量
- }ST;
-
- void StackInit(ST* ps);
- void StackDestory(ST* ps);
- void StackPush(ST* ps);
- void StackPop(ST* ps);
-
- bool StackEmpty(ST* ps);
- int StackSize(ST* ps);
- STDataType StackTop(ST* ps);
结论:数据结构建议不要直接访问数据,一定要通过函数接口访问(低耦合,高内聚)!!!