C语言栈的创建及使用详解
1. 栈的基本概念
1.1 概念
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。
1.2 结构
栈的基本结构如下:
最下面的部分为栈底,最上面的数据位置为栈顶。
当数据传入栈的时候,数据会直接放在栈顶的位置,此过程为压栈。
当取数据时,只能从栈顶的数据依次往下取,此过程为出栈。
栈结构导致了栈中的数据会遵从先进后出(后进先出)
的原则。
1.3 栈结构的选择
栈结构只需要确保数据是先进后出的原则,因而栈的实现既可以用链表结构,也可以用顺序表结构。
考虑到栈结构主要是对栈顶进行频繁操作,如果采用链表结构,操作较为繁琐。采用顺序表结构,可以直接通过下标对数据进行操作,操作简便。因而本文采用顺序表结构进行栈的创建。
顺序表的头位置为栈底,表尾为栈顶。
2. top 位置的说明
因为栈的基本结构采用了顺序表的方式,当我们插入和访问数据时,都是从下标为0 的位置开始访问。此时,栈顶位置 top 有两种放置方式:
- top 记录最后一个数据位置的下一个位置
- top 记录最后一个数据的位置
对于第一种情况:top 初始化为0,当插入第一个数据后,top 变为 1 ,访问栈顶数据时,需要访问 top - 1 的位置,此位置才有数据。返回数据的长度时,只需要返回top 的值即可。判空时,只要判断top 是否为零即可。此方法便于判空、判断数据长度。
对于第二种情况:top 初始化为-1,当插入第一个数据后,top 变为 0 ,top 的值即为栈顶数据的位置。访问栈顶数据时,只需要访问 top 的位置。返回数据的长度时,需要返回top+1 的值。判空时,需要单独判断top 是否为 -1 。此方法不便于便于判空、判断数据长度。
本文采用第一种方法记录栈顶位置。
3. 栈的具体创建
3.1 结构体的创建
typedef int MySTDataType;
//宏定义顺序表中数据结构的类型,便于不同类型数据的接入
typedef struct MyStackNode
{
MySTDataType* a;//存放数据
int top;//栈顶位置,从0开始,每次指定到最后一个数据位置的下一个位置
int capacity;//记录开辟空间的大小
}ST;
3.2 栈的初始化
void STInit(ST* pst)
{
assert(pst);//断言,数据为空时报错
pst->a = NULL;//置空
pst->top = 0;//置空,确保栈顶在最后一个数据位置的下一个位置
pst->capacity = 0;//置空
}
3.3 判空
当栈顶的位置位于起点时,此时栈为空,即 top = 0.
bool STEmpty(ST* pst)//布尔判断,为真时返回ture,为假时返回false。
{
assert(pst);
return pst->top == 0;//当top 为0时,满足条件,返回ture,否在返回false
//等价于以下代码
/*if(pst->top==0)
return ture;
else
retrun false;*/
}
3.4 压栈
压栈要考虑空间的大小问题,当空间不足时,要及时开辟一个新的空间。
void STPush(ST* pst, MySTDataType x)
{
if (pst -> top == pst->capacity)
//此时栈顶位于空间最后一个位置,若还想插入新的数据,需要开辟新空间
{
int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
//栈中无数据时,开辟4个字节大小,若有数据,则开辟原来大小的两倍
MySTDataType* newnode = (MySTDataType*)realloc(pst->a, newcapacity * sizeof(MySTDataType));//开辟空间
if (newnode == NULL)
{
perror("realloc fail\n");
return;
}
pst->a = newnode;//新开辟的空间赋给结构体
pst->capacity = newcapacity;//空间大小更新
}
pst->a[pst->top] = x;//在栈顶位置插入新的数据
pst->top++;//栈顶移到下一个位置
}
3.5 出栈
出栈时,只需要将栈顶的数据排除到栈之外即可,即将栈顶的位置往下移动即可。
void STPop(ST* pst)
{
assert(pst);
assert(!STEmpty(pst));
//断言,为空时你报错
pst->top--;//移动栈顶位置,达到出栈的效果
}
3.6 返回栈顶值
栈顶位于最后一个数据的下一个位置,因为访问栈顶最后一个数据时,要让top - 1.
MySTDataType STTop(ST* pst)
{
assert(pst);
assert(!STEmpty(pst));
return pst->a[pst->top-1];//返回栈顶的数据
}
3.7 栈中数据个数
栈顶top 始终在栈顶的位置,top 的值即为栈中数据的多少。
int STSize(ST* pst)
{
assert(pst);
assert(!STEmpty(pst));
return pst->top;//返回数据数目
}
3.8 栈的销毁
void STDestroy(ST* pst)
{
assert(pst);
free(pst->a);//释放开辟的空间
//置空
pst->a = NULL;
pst->top = 0;
pst->capacity = 0;
}
4. 栈的使用
4.1 代码使用
int main()
{
ST st;
STInit(&st);//初始化
//插入数据1 ~ 5
STPush(&st, 1);
STPush(&st, 2);
STPush(&st, 3);
STPush(&st, 4);
STPush(&st, 5);
while (!STEmpty(&st))//所有数据出栈之后结束循环
{
printf("%d ", STTop(&st));//打印栈顶数据
STPop(&st);//使栈顶数据出栈
}
STDestroy(&st);
return 0;
}
结果如下:实现了后进先出的目的
4.2 现实场景使用
1、函数调用
在程序中,每个函数调用都需要将当前状态的信息(比如函数调用前的参数、局部变量和程序计数器等)保存到栈中,等到函数调用结束后再从栈中弹出这些信息,恢复调用前的状态。这个过程被称作函数的压栈和弹栈操作。
2、表达式求值
通常我们在计算机中对表达式求值时都采用栈来实现。比如中缀表达式转后缀表达式的操作就需要使用栈。
3、系统调用
在操作系统中,内核通常会将一个系统调用的参数、返回值和程序计数器等状态保存到进程的用户栈中,在系统调用结束后再从栈中弹出这些信息,恢复调用前的状态。
4、缓存机制
缓存通常也使用栈的方式实现,被访问的数据最先进入栈顶,最后的则返回底部。例如:网页缓存
5、代码编辑器
用栈来判断括号是否成对,以及最近的括号匹配情况。若左括号,则入栈;若右括号,则将栈顶元素弹出,若是对应的左括号,则继续遍历;否则匹配失败。
5. 总结
栈是一种较为特殊的顺序表,只要掌握了顺序表,栈的创建相对来说时偏简单的。