栈是一种重要的数据结构,其实质也是线性表的一种。但是它只支持两种操作:插入和删除。并且,栈的特点是后进先出,也就是说,栈的操作永远在顶部,插入和删除操作只在栈的顶部进行,所以先插入的栈会堆在底下,而后插入的栈会在栈顶,进行删除的时候,是从栈顶开始,所以新插入的元素反而能优先被删除,我们称之为后进先出。而这种插入和删除操作,在栈的用语里,叫做推进(push)和弹出(pop)。
举个例子来说,栈的操作有点像一个老师收上来的作业,先交的作业放在下面,后交的作业放在上面,老师在批阅的时候,总是先从上面的作业开始,于是先交的作业总是要晚一点才能被看到。
在C语言的应用中,总是习惯将各函数的实现代码放置在一个源文件中,而将这些函数需要提供给客户的接口声明放置在另一个单独的头文件中。这样客户只需要包含该头文件,就能调用对应的接口(当然,编译的时候需要与实现代码一起编译)。
在栈的实现中,我们在头文件stack.h中放置四个函数声明,也就是客户程序想使用栈时需要调用的接口。内容如下:
//stack.h
void stack_init(int);
int stack_empty(void);
void stack_push(int);
int stack_pop(void);
//stack1.c
#include <stdlib.h>
static int *s;
static int N;
void stack_init(int maxN)
{
s = malloc(maxN * sizeof(int));
N = 0;
}
int stack_empty(void)
{
return N == 0;
}
void stack_push(int item)
{
s[N++] = item;
}
int stack_pop(void)
{
return s[--N];
}
#include <stdlib.h>
typedef struct stacknode* link;
struct stacknode {
int item;
link next;
};
static link head;
link NEW(int item,link next)
{
link x = malloc(sizeof *x);
x->item = item;
x->next = next;
return x;
}
void stack_init(int maxN)
{
head = NULL;
}
int stack_empty()
{
return head == NULL;
}
void stack_push(int item)
{
head = NEW(item,head);
}
int stack_pop()
{
int item = head->item;
link t = head->next;
free(head);
head = t;
return item;
}
以上是栈的具体实现,介绍了两种实现方式:基于数组的和基于链表的。关于链表的概念,请参照上两篇博客:
有了栈的实现,我们在使用栈的时候,只需要包含头文件“stack.h”,既能调用栈的四个接口了,而不需要关注它的实现具体是什么样子的。原本我使用的是数组的实现,并用它写了一个将十进制数转换成八进制数的应用程序(下面马上要介绍),后来觉得这种实现不好,于是将它改为链表的实现,但是这个时候,我的客户程序,也就是用链表四个接口写的十进制转换成八进制的程序,不需要任何改动。这就是将接口和实现分开放置的好处。
下面给出一个栈的简单应用,将一个十进制数转换成八进制数,并且显示。代码如下:
//stackmain.c
#include <stdio.h>
#include <stdlib.h>
#include "stack.h"
#define MAXSIZE 100
int main()
{
int i,n;
scanf("%d",&n);
stack_init(MAXSIZE);
while (n) {
stack_push(n % 8);
n = n / 8;
}
while (!stack_empty()) {
i = stack_pop();
printf("%d",i);
}
printf("\n");
}
如果在linux下编译的话,可以用cc stack1.c stackmain.c 或者是 cc stack2.c stackmain.c即可。