您的位置:首页 > 理论基础 > 数据结构算法

数据结构:二级指针与Stack的数组实现

2013-07-28 19:54 423 查看
【简介】

Stack,栈结构,即传统的LIFO,后进先出,常用的实现方法有数组法和链表法两种。如果看过我上一篇文章《数据结构:二级指针与不含表头的单链表》,一定会看到其中的关键在于,利用void*指针将数据结构抽象出来,适用于任何数据类型。这次尝试利用void**,两级void指针,用数组法实现Stack的数据结构。

【Stack数据结构】

Stack结构的申明如下(stack.c):

#include"stack.h"
#include"stdio.h"
struct_Stack{
void**base;//Stackmustbeavoid*pointer
void**top;//topofstack,whichwillchangeatrun-time
unsignedintsize;//stackvolume
unsignedintentries;//usedstackvalue,initialto0
};
staticStackStack_Pool[MAX_STACK];
staticunsignedintStack_Pool_Entries=0;

.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}

这里的栈底是一个void**指针base,这个指针将在以后操作中永远不会修改,因为入栈和出栈都是对void**top指针进行操作的,即入栈时将数据(当然是一个void*的指针的地址)放在top指向的地方,将top++,出栈时先判断是否为空,将top--,之后取出栈顶即可。因为是void**二级指针,所以++,--操作是合法的,若是void*一级指针,所有的代数运算都不合法。其实可以做的更节省一点,即利用entries成员作为偏移值,算出栈顶,而不用专门使用一个top来进行操作,base[entries]即是栈顶,不过是我后来才想起的,就先这样用吧懒得改了。

Stack*STK_Create(void**pp_base,unsignedintsize)
{
Stack*p_s;
if(Stack_Pool_Entries>=MAX_STACK)
return(Stack*)0;
p_s=&Stack_Pool[Stack_Pool_Entries];
/*Initializestackpointer*/
p_s->base=pp_base;
p_s->size=size;
p_s->top=pp_base;
p_s->entries=0;
Stack_Pool_Entries++;
returnp_s;
}


.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}

上面的代码即可新建一个Stack类型的实例。所有的Stack类型实例都存放于Stack_Pool中,而不是由malloc分配而来的。

下面是Stack的Push入栈和Pop出栈的操作。

voidSTK_Push(Stack*p_stack,void*p_data_in)
{
if(p_stack)
{
if(p_stack->entries<p_stack->size)
{
*p_stack->top=p_data_in;
p_stack->entries++;
p_stack->top++;//void**'s++operationislegal
}
}
}
voidSTK_Pop(Stack*p_stack,void**pp_data_out)
{
if(p_stack)
{
if(p_stack->entries)
{
p_stack->top--;
*pp_data_out=*p_stack->top;
p_stack->entries--;
}
else
{
*pp_data_out=(void*)0;
}
}
}

.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}

其中出栈Pop的操作将会填充pp_data_out指针,因此调用该函数时需要传入一个void*类型的以及指针即可,将在第21或26行修改其中内容。

可以看到,入栈和出栈并没有出现通常Stack操作中的MemoryCopy操作(C标准库中的string.h中有函数memcpy实现内存拷贝。如果不想包含string.h,比如说在嵌入式编程时,也可以自己写一个类似函数实现memcpy,以后有机会在介绍,很简单的),后面的分析会介绍这样做的优势。

当然利用函数返回值进行Pop出栈操作也是可以的,如下代码:

void*STK_Pop_Ptr(Stack*p_stack)
{
void*p_data_out;
STK_Pop(p_stack,&p_data_out);
returnp_data_out;
}

.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}

最后我写了一个打印栈内容的功能函数如下:

voidSTK_Print(Stack*p_stack,void(*stack_print_value)(void*p_value))
{
void*p_value;
void**pp_top;
unsignedintcnt=0;
if(p_stack)
{
printf("PrintStack:\n");
pp_top=p_stack->top;
pp_top--;
printf("------top\n");
while(*pp_top)
{
p_value=*pp_top;
(*stack_print_value)(p_value);
pp_top--;
cnt++;
}
printf("------base\n");
printf("EndStack,stacksize:%d\n\n",cnt);
}
}

.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}

第一个传入参数为,栈Stack指针。由于栈顶是void**二级指针,栈内容是void*一级指针,所以要打印内容一定需要使用者提供一个能打印的函数stack_print_value(),返回值为void,传入值为void*,即,在函数内解释void*的实际对象类型,不然就会在这个函数STK_Print()中直接强制转换void*为某一类型,这样改方法就不通用与任何类型了。可以看到从第12行开始的while()循环即循环调用你提供的打印函数stack_print_value(),进行打印。

【测试代码】

新建一个main.c,如下:


#include"stdio.h"
[code]#include"stack.h"
void*STK_1[10];//Realstackisapointerarray,oftypevoid*
intSTK_Value[5]={1,2,3,4,5};//Realdatastoreplace,mustnotbeavariablewithinfunctions
Stack*p_stack_1;//Stackpointer
voidStack_int_Value_Print(void*p_value);
intmain(void)
{
int*p_out;
p_stack_1=STK_Create(&STK_1[0],10);
STK_Push(p_stack_1,&STK_Value[0]);//1
STK_Push(p_stack_1,&STK_Value[1]);//2
STK_Push(p_stack_1,&STK_Value[2]);//3
STK_Push(p_stack_1,&STK_Value[3]);//4
STK_Push(p_stack_1,&STK_Value[2]);//5
STK_Push(p_stack_1,&STK_Value[4]);//6
STK_Print(p_stack_1,Stack_int_Value_Print);
STK_Pop(p_stack_1,&p_out);//5
if(p_out)printf("pop:%d\n",*p_out);
STK_Push(p_stack_1,&STK_Value[1]);//6
STK_Push(p_stack_1,&STK_Value[2]);//7
STK_Push(p_stack_1,&STK_Value[3]);//8
STK_Push(p_stack_1,&STK_Value[4]);//9
STK_Push(p_stack_1,&STK_Value[1]);//10
STK_Push(p_stack_1,&STK_Value[2]);//over
STK_Push(p_stack_1,&STK_Value[3]);//over
STK_Print(p_stack_1,Stack_int_Value_Print);
return1;
}
/*provideaprinterfortypeintinthestack*/
voidStack_int_Value_Print(void*p_value)
{
int*p_int;
p_int=(int*)p_value;
printf("%d\n",*p_int);
}

.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}

.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}

可以看到,新建栈是,Stack中的void**base实际指向一个void*类型的数组void*STK_1[10],这个栈的大小是10。每次入栈时传入的都是intSTK_Value[5]中的成员。到第22行时栈中已经消耗了6了,24行进行第一次栈内容打印。到26行时取出一个打印,直到34行入栈满了,之后的都无法入栈。39行进行第二次栈内容打印。运行结果如下:



可以看到栈内容跟入栈顺序是一致的。

这里可以看到,栈的大小void*STK_1[10],10个,跟实际数据存储区intSTK_Value[5],5个,实际上没有任何关系。main.c中的30到34行实际上已经入栈了重复的内容,但是实际上仅仅是造成栈中的指针指向同一快存储区而已,实际的存储区并没有重复,这就是不使用memcpy内存拷贝的结果,所以简单的一句话概括这种实现的特点是:


栈中成员均是指向数据的指针,而非存储数据自己。


这样即使入栈时传入同样的数据,也不会造成存储区的重复,仅仅是栈中有两个指针指向同一块数据区域罢了。因此,入栈和出栈的操作仅仅是对占栈中的指针void*进行赋值和取值,而并不是实际数据的memcpy内存拷贝,入栈和出栈的速度快且对于任何数据类型来说都是一样的,因为都是void*指针的赋值。试想如果使用memcpy入栈出栈,而你的数据就是一个typedefstruct中成员很多的类型,比如:

typedefstruct{
intID;
intFlag;
intvalue[20];
//...
}Data_Type;

可能占的内存大小很大,这样一来每次入栈出栈耗时不错,还导致内存浪费,实际的数据已经存在于程序的某个地方了,而入栈时真的把数据又复制一遍入栈。另外,若入栈了相同数据,那真的栈的内存中有两个一模一样东西,费地方。

如果你说,你的数据是通讯得到的,比如串口数据,每次都有新的数据进来更新,当然需要拷贝到栈里面保存咯。那么,这种时候建个数据buffer专门存放接收到的数据,然后入栈出栈的数据内容都是针对这个buffer操作不就好了。

所以,上面的所有工作任然是想将数据结构,这里是Stack,与实际数据存储区域解耦,将Stack类型抽象出来,变得可以操作任何类型,这样不会每当有一个新的数据类型需要Stack栈来个管理时就新建一个专门针对这种类型的栈,而且栈中既包含了栈的操作又包含了栈的存储区域,导致内存和运行速度的浪费。

最后再附上完整的代码下载:

Stack_Double_Star_Array_Implement.7z
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
章节导航