栈(stack),是先入后出的一个数据存储结构。在计算机领域用处极大,比如子函数调用从而入栈当前数据。本篇将手动在IDE里面搓一个简单的栈结构。
环境:DEV C++ 5.11 、C。
一、打开dev c++ ,准备编写相关代码。
二、我们试想一下。栈是一个能够输入输出数据的结构。输出数据时,此数据应该是最后存入栈的一个元素。那么我们可以在线性的存储空间中设立一个栈顶指针来指向最后存入的数据。同时也应该设立一个栈底。来表示该栈结构在内存中的起始分配位置。现在栈底栈顶都有了,那么栈里面能够存放的数据大小应该是栈顶地址减去栈底地址(字节)。涉及到高级的存储对象,可存储的对象数目应该是(栈顶地址 - 栈底地址)/ sizeof(元素类型)。
typedef long long int ElemType;//假设栈中存的数据类型是long long int
typedef struct{
ElemType* base;//栈底指针
ElemType* top;//栈顶指针,这里是空栈指针。即存储数据时先存入指针再自增
int length;//当前栈中已分配的空间大小
}Stack;
在主函数前添加代码如上。假设要存储的数据类型是8个字节的long long int
三、光有了栈的定义显然是不行的。我们如何使用呢?难道要在主函数中手动输入各个成员的值吗?太不方便了!我们要创建一个函数来初始化相关的成员变量。
int stack_init(Stack* S){//将指向该栈的指针传递过来
S->base = (ElemType*)malloc(100*sizeof(ElemType));
//为栈分配空间。其容量可以承载100个元素。并且将分配初始地址作为栈的栈底
S->top = S->base;//由于栈还未真正的使用。所以当前的栈顶也为栈底
S->length = 100;//已经预分配了100个元素占用的大小
if(!S->base) return 1;//分配失败的话就返回1
else return 0;
}
好了,初始化的工作已经完成,我们只需调用这个函数即可完成栈的初始化工作。需要强调的是,这个length成员变量并不表示当前所存储的元素个数。而是表示当前已为栈申请存储空间的元素个数。这个length很有用。在下一步,我们会通过判断length与当前元素个数的大小来动态分配栈空间的大小。
四、我们将栈拿到手了!不过这个栈还不能完成我们所需的任务。它需要一些修饰。
修饰1:入栈操作
修饰2:出栈操作
入栈:将元素压入栈中。无论你是什么数据类型(可以在typedef中定义ElemType。即要存储的元素类型),栈底不变。栈顶指针在入栈操作完成后会自增。
出栈:将最后存入的元素从栈中拿出。栈顶指针在出栈操作后会自减。栈底不变。
我们可以创建两个函数来实现上述功能!
int stack_push(Stack* S,ElemType elem){//入栈操作
*S->top = elem;//直接加入栈。即令栈顶指向当前要入栈的元素
++S->top;//完成后栈顶自加以便后续操作
return 0;//默认返回0
}
ElemType stack_pop(Stack* S){//出栈操作
--S->top;//由于我们刚刚定义的是空栈机制,栈顶指向实际存储的最后一个元素的下一个,因此需要自减
return *S->top;//直接返回自减完成后栈顶指向的元素
}
五、树梢上的残叶,细心的读者会发现文章中存在的小BUG。Stack Overflow一词用来形容上步骤再合适不过!
由于入栈和出栈的边界条件不加以限制,在超过初始分配的100个元素后栈顶指针存在一些风险。栈顶越界会造成一些访存错误。并且元素再入栈就会溢出。同样的,栈顶的物理存储地址不可能比栈底小!那样也会造成一些问题。即弹出的数据可能超出预期。
为此,我们在上述两个修饰当中加入了一些限定
限定1:每次入栈时判断是否超出预分配的存储空间
限定2:每次出栈时判断是否栈空了
在入栈操作添加限定1 的代码
int stack_push(Stack* S,ElemType elem){//入栈操作
//***********************
if(S->top - S->base >= S->length){//判断是否超出预期空间
S->base = (ElemType*)realloc(S->base,(S->length + 100)*sizeof(ElemType));
//如果栈顶减去栈底超过了预分配的大小,为其重新分配length+100大小的空间
if(!S->base) return 1;
S->top = S->base + S->length;
S->length += 100; //重新定位栈顶栈底
}
//***********************
*S->top = elem;//直接加入栈。即令栈顶指向当前要入栈的元素
++(S->top);//完成后栈顶自加以便后续操作
return 0;//默认返回0
}
在出栈操作添加限定2的代码
ElemType stack_pop(Stack* S){//出栈操作
//***********************
if(S->base == S->top) return NULL;
//***********************
--S->top;//由于我们刚刚定义的是空栈机制,栈顶指向实际存储的最后一个元素的下一个,因此需要自减
return *S->top;//直接返回自减完成后栈顶指向的元素
}
这个限定2的实现简直就是trash。因为返回值为ElemType,既然发生错误了也不能返回错误信息只能无脑返回NULL。显然是不够看的。当然,我们可以在出栈操作之前加一个判空语句。虽然麻烦,但是实用
六、除去残叶的残叶根,我们落下了一个栈功能,那就是判空。isempty,同时,既然能实现判空,那么不如锦上添花,加入一个返回当前存储大小的函数。
任务1:判空函数
任务2:返回栈大小函数
int stack_empty(Stack* S){
return (S->base == S->top);//返回栈是否为空 1:空 0:不为空
}
int stack_size(Stack* S){
int len = (S->top - S->base);//返回栈的大小
return len;
}
至此,所有任务皆已完成!下面给出全部代码+测试!
#include<stdio.h>
#include<iostream>
using namespace std;
typedef long long int ElemType;//假设栈中存的数据类型是long long int
typedef struct{
ElemType* base;//栈底指针
ElemType* top;//栈顶指针,这里是空栈指针。即存储数据时先存入指针再自增
int length;//当前栈中的元素个数
}Stack;
int stack_init(Stack* S){//将指向该栈的指针传递过来
S->base = (ElemType*)malloc(100*sizeof(ElemType));
//为栈分配空间。其容量可以承载100个元素。并且将分配初始地址作为栈的栈底
S->top = S->base;//由于栈还未真正的使用。所以当前的栈顶也为栈底
S->length = 2;//已经预分配了100个元素占用的大小
if(!S->base) return 1;//分配失败的话就返回1
else return 0;
}
int stack_push(Stack* S,ElemType elem){//入栈操作
//***********************
if(S->top - S->base >= S->length){//判断是否超出预期空间
S->base = (ElemType*)realloc(S->base,(S->length + 100)*sizeof(ElemType));
//如果栈顶减去栈底超过了预分配的大小,为其重新分配length+100大小的空间
if(!S->base) return 1;
S->top = S->base + S->length;
S->length += 100;
}
//***********************
*S->top = elem;//直接加入栈。即令栈顶指向当前要入栈的元素
++(S->top);//完成后栈顶自加以便后续操作
return 0;//默认返回0
}
ElemType stack_pop(Stack* S){//出栈操作
//***********************
if(S->base == S->top) return NULL;
//***********************
--S->top;//由于我们刚刚定义的是空栈机制,栈顶指向实际存储的最后一个元素的下一个,因此需要自减
return *S->top;//直接返回自减完成后栈顶指向的元素
}
int stack_empty(Stack* S){
return (S->base == S->top);//返回栈是否为空 1:空 0:不为空
}
int stack_size(Stack* S){
int len = (S->top - S->base);//返回栈的大小
return len;
}
int main(int argc,char* argv[]){
cout<<sizeof(long long int);//测试自定义栈的存储类型,其大小应为8
Stack S;
stack_init(&S);//初始化栈
long long int a = 233;
stack_push(&S,a);//入栈
stack_push(&S,a-1);//入栈
cout<<stack_size(&S)<<endl;
long long int tmp1,tmp2;
tmp1 = stack_pop(&S);//出栈
tmp2 = stack_pop(&S);//出栈
cout<<stack_size(&S)<<endl;
cout<<tmp1<<endl;
cout<<tmp2<<endl;
for(int i=0;i<200;i++){
a--;
stack_push(&S,a);
cout<<S.top<<endl;
}
cout<<stack_size(&S);
return 0;
}
省去中间数据,给出末行。
如果有疑问欢迎评论区提问,接受任何指正,欢迎点评!