用栈实现学生信息管理。这里放一下有哪些文件。
Stack.h
#pragma once防止库函数的重复引用,因为库函数会在预编译的时候在程序中展开,会增大程序的体积。
通过typedef对数据重命名,之后需要修改数据就十分方便。并且其他函数不需要太多的改动。
这里结构体传的是指针,减少没必要的内存消耗。
栈的特性是先进后出,所以不存在什么头插尾插和头删尾删之类的,数据的插入被称为入栈,只有一种方式。数据的删除被称为出栈,也只有一种方式。
栈的top是表示栈顶,而不是用来表示元素个数的。虽然他们实质上的数值可能差别不大,但是意义并不相同。
#pragma once #include <stdbool.h> #include <stdio.h> #include <assert.h> #include <stdlib.h> // 支持动态增长的栈 typedef struct student { char name[20]; char sex[5]; int sno; int age; }STDataType; typedef struct Stack { STDataType* a; int top; // 栈顶 int capacity; // 容量 }Stack; // 打印 void StackPrint(Stack* ps); //初始化栈 void StackInit(Stack* ps); //销毁栈 void StackDestroy(Stack* ps); //入栈 void StackPush(Stack* ps, STDataType* x); //出栈 void StackPop(Stack* ps); //获取栈顶元素 STDataType* StackTop(Stack* ps); //获取栈中有效元素个数 int StackSize(Stack* ps); //检测栈是否为空,如果为空返回非零结果,如果不为空返回0 int StackEmpty(Stack* ps);
main.c
因为重点在于数据结构链表的使用,所以直接给定一些数据,就不进行重复繁琐的数据输入工作了。
#include "Stack.h" int main() { Stack st; StackInit(&st); STDataType stu1 = { "张三", "男", 110701, 22 }; STDataType stu2 = { "李四", "男", 110702, 21 }; STDataType stu3 = { "王五", "女", 110703, 23 }; STDataType stu4 = { "赵六", "女", 110704, 22 }; STDataType stu5 = { "周七", "男", 110705, 23 }; StackPush(&st, &stu1); StackPush(&st, &stu2); StackPush(&st, &stu3); StackPush(&st, &stu4); StackPush(&st, &stu5); StackPrint(&st); printf("size:%d\n\n", StackSize(&st)); STDataType* top = StackTop(&st); printf("%d %s %d %s\n\n", top->sno, top->name, top->age, top->sex); StackPop(&st); StackPop(&st); StackPop(&st); StackPrint(&st); printf("size:%d\n", StackSize(&st)); StackDestroy(&st); return 0; }
Stack.c
打印函数的实现,如果栈中的数据类型发生了改变,其他功能函数基本上不需要有什么变化, 打印函数对应修改一下就行了,因为打印需要涉及到具体的数据问题
void StackPrint(Stack* ps) { int n = StackSize(ps); for (int i = 0; i < n; ++i) { printf("%d %s %d %s\n", ps->a[i].sno, ps->a[i].name, ps->a[i].age, ps->a[i].sex); } printf("\n"); }
栈的初始化,先动态开辟一块空间,然后将top置为0,把capacity置为4。
void StackInit(Stack* ps) { assert(ps); //开空间 ps->a = (STDataType*)malloc(sizeof(STDataType) * 4); if (ps->a == NULL) { perror("malloc fail"); exit(-1); } ps->top = 0; ps->capacity = 4; }
数据入栈,因为这里只有这一个插入数据的函数,所以没有必要将检查空间的功能单独提取出来,就在插入数据时检查是否需要开辟空间就可以了。
如果空间不够,每次空间的扩容是按之前2倍进行扩容的。这样扩容的原因是在避免重复扩容和空间浪费之间的一种平衡的选择。
void StackPush(Stack* ps, STDataType* x) { assert(ps); if (ps->top == ps->capacity) { STDataType* tmp = (STDataType*)realloc(ps->a, ps->capacity * 2 * sizeof(STDataType)); if (tmp == NULL) { perror("realloc fail"); exit(-1); } ps->a = tmp; ps->capacity *= 2; } ps->a[ps->top] = *x; ps->top++; }
这里的出栈操作和顺序表的删除数据操作相似,我们并不需要真正的删除掉这个数据,而且要删除掉一个数据实际上并不好删。我们要做的只是让我们的程序访问不到已经被删除的数据就行了,也就是只要top减一就可以了。当我们在插入新数据时,如果插入位置是有数据的,这个数据就会被覆盖掉,所以我们删没删这个数据,实际上是没有影响的。就相当于他有一个初始值而已,但是初始值并不是不能被改变的。
void StackPop(Stack* ps) { assert(ps); assert(!StackEmpty(ps)); ps->top--; }
检测栈是否为空,如果为空返回非零结果,如果不为空返回0。
int StackEmpty(Stack* ps) { assert(ps); //如-1为空,如果等式成立,结果为真,返回真,非零为真 return ps->top == -1; }
获取栈顶元素,所以需要返回栈中存储的数据,但是我们的数据是一个结构体。直接返回一个结构体会占用很多内存,所以返回一个结构体指针。
获取栈顶元素之前,我们需要检查这个栈中是否有元素,如果没有元素,那肯定是获取不到栈顶元素的。这里通过断言直接报错。
STDataType* StackTop(Stack* ps) { assert(ps); assert(!StackEmpty(ps)); return &(ps->a[ps->top - 1]); }
获取栈中有效元素个数
int StackSize(Stack* ps) { assert(ps); return ps->top; }
销毁栈,因为采用的是动态开辟空间的方式,所以需要释放空间,如果不释放空间会造成内存泄露。这里我们需要先释放内存空间,然后再把指向这块内存空间的指针置为NULL,否则可能会出现非法访问的问题。之后的top和capacity也应该跟着置为0,一方面,空间已经销毁了,他具备的数据个数和容量本身就应该没有了。另一方面,防止让人误以为有数据或者有空间而去进行一些非法操作。
void StackDestroy(Stack* ps) { assert(ps); free(ps->a); ps->a == NULL; ps->top = ps->capacity = 0; }