【简介】

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

【Stack数据结构】

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

   1:  #include   "stack.h"
   2:  #include   "stdio.h"
   3:   
   4:  struct _Stack{
   5:      void        **   base;      // Stack must be a void * pointer
   6:      void        **   top;       // top of stack, which will change at run-time
   7:      unsigned int     size;      // stack volume
   8:      unsigned int     entries;   // used stack value, initial to 0
   9:  };
  10:   
  11:  static  Stack          Stack_Pool[MAX_STACK];
  12:  static  unsigned int   Stack_Pool_Entries = 0;

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { 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]即是栈顶,不过是我后来才想起的,就先这样用吧懒得改了。

   1:  Stack * STK_Create(void ** pp_base, unsigned int size)
   2:  {
   3:      Stack * p_s;
   4:      if(Stack_Pool_Entries>=MAX_STACK)
   5:          return (Stack*)0;
   6:      p_s = &Stack_Pool[Stack_Pool_Entries];
   7:      
   8:      /* Initialize stack pointer */
   9:      p_s->base     = pp_base;
  10:      p_s->size     = size;
  11:      p_s->top      = pp_base;
  12:      p_s->entries  = 0;
  13:      
  14:      Stack_Pool_Entries++;
  15:      return p_s;
  16:  }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { 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出栈的操作。

   1:  void  STK_Push(Stack * p_stack, void * p_data_in)
   2:  {
   3:      if(p_stack)
   4:      {
   5:          if(p_stack->entries < p_stack->size )
   6:          {
   7:              *p_stack->top = p_data_in;
   8:              p_stack->entries++;
   9:              p_stack->top++;     // void ** 's ++ operation is legal
  10:          }
  11:      }
  12:  }
  13:   
  14:  void  STK_Pop(Stack * p_stack, void ** pp_data_out)
  15:  {
  16:      if(p_stack)
  17:      {
  18:          if(p_stack->entries)
  19:          {
  20:              p_stack->top--;
  21:              *pp_data_out = *p_stack->top;
  22:              p_stack->entries--;
  23:          }
  24:          else
  25:          {
  26:              * pp_data_out = (void*)0;
  27:          }
  28:      }
  29:  }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { 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操作中的Memory Copy操作(C标准库中的string.h中有函数memcpy实现内存拷贝。如果不想包含string.h,比如说在嵌入式编程时,也可以自己写一个类似函数实现memcpy,以后有机会在介绍,很简单的),后面的分析会介绍这样做的优势。

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

   1:  void * STK_Pop_Ptr(Stack * p_stack)
   2:  {
   3:      void * p_data_out;
   4:      STK_Pop(p_stack, &p_data_out);
   5:      return p_data_out;
   6:  }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { 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; }

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

   1:  void  STK_Print(Stack *p_stack, void (*stack_print_value)(void* p_value))
   2:  {
   3:      void * p_value;
   4:      void ** pp_top;
   5:      unsigned int cnt = 0;
   6:      if(p_stack)
   7:      {
   8:          printf("Print Stack:\n");
   9:          pp_top = p_stack->top;
  10:          pp_top--;
  11:          printf("   ------ top \n");
  12:          while(*pp_top)
  13:          {
  14:              p_value = *pp_top;
  15:              
  16:              (*stack_print_value)(p_value);
  17:              pp_top--;
  18:              cnt++;
  19:          }
  20:          printf("   ------ base\n");
  21:          printf("End Stack, stack size:%d\n\n",cnt);
  22:      }
  23:  }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { 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,如下:

   1:  #include  "stdio.h"
   2:  #include  "stack.h"
   3:   
   4:  void  * STK_1[10];                      // Real stack is a pointer array,  of type void*
   5:  int     STK_Value[5] = {1,2,3,4,5};     // Real data store place,  must not be a variable with in functions
   6:  Stack * p_stack_1;                      // Stack pointer
   7:   
   8:                    
   9:  void  Stack_int_Value_Print(void * p_value);
  10:   
  11:  int main(void)
  12:  {
  13:      int *p_out;
  14:      p_stack_1 = STK_Create (&STK_1[0], 10);
  15:      
  16:      
  17:      STK_Push (p_stack_1, &STK_Value[0]);        // 1
  18:      STK_Push (p_stack_1, &STK_Value[1]);        // 2
  19:      STK_Push (p_stack_1, &STK_Value[2]);        // 3
  20:      STK_Push (p_stack_1, &STK_Value[3]);        // 4
  21:      STK_Push (p_stack_1, &STK_Value[2]);        // 5
  22:      STK_Push (p_stack_1, &STK_Value[4]);        // 6
  23:      
  24:      STK_Print(p_stack_1, Stack_int_Value_Print);
  25:   
  26:      STK_Pop  (p_stack_1, &p_out);               // 5
  27:      if(p_out) printf("pop: %d\n",*p_out);
  28:   
  29:   
  30:      STK_Push (p_stack_1, &STK_Value[1]);        // 6
  31:      STK_Push (p_stack_1, &STK_Value[2]);        // 7
  32:      STK_Push (p_stack_1, &STK_Value[3]);        // 8
  33:      STK_Push (p_stack_1, &STK_Value[4]);        // 9
  34:      STK_Push (p_stack_1, &STK_Value[1]);        // 10
  35:      STK_Push (p_stack_1, &STK_Value[2]);        // over
  36:      STK_Push (p_stack_1, &STK_Value[3]);        // over
  37:   
  38:      
  39:      STK_Print(p_stack_1, Stack_int_Value_Print);
  40:      
  41:      
  42:      return 1;
  43:  }
  44:   
  45:  /* provide a printer for type int in the stack */
  46:  void  Stack_int_Value_Print(void * p_value)
  47:  {
  48:      int *p_int;
  49:      p_int = (int*)p_value;
  50:      printf("     %d\n",*p_int);
  51:  }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { 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, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { 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。每次入栈时传入的都是int STK_Value[5]中的成员。到第22行时栈中已经消耗了6了,24行进行第一次栈内容打印。到26行时取出一个打印,直到34行入栈满了,之后的都无法入栈。39行进行第二次栈内容打印。运行结果如下:

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

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

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

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

typedef struct{
int ID;
int Flag;
int value[20];
//...
}Data_Type;
 

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

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

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

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

Stack_Double_Star_Array_Implement.7z

数据结构:二级指针与Stack的数组实现的更多相关文章

  1. 20130330 printf数组改变 数组指针便利二维数组 二级指针遍历二维数组 ZigZag

    1.为什么printf之后数组的值会改变? #include<stdio.h> ; int * Zigzag() { ,j=,limit=; ; ; int a[N][N]; int (* ...

  2. 论C语言中二级指针和二维数组之间的区别

    刚开始学习C语言的时候,觉得一个数组可以定义一个一级指针去访问,想当然的就觉得可以定义一个二级指针去访问二维数组.很显然这是错误的. 我们来看看C语言的数组在内存中的存储方式. 实际上C语言中的数组, ...

  3. 唠唠C++二级指针、二维数组、指针数组、数组指针等的区分

    今天看c++primer第六章,有这部分的内容,脑子有点糊涂了,看了几篇博客,自己敲了下,记录一下备忘. 二级指针: int **p; 二维数组: int p[10][10]; char q[10][ ...

  4. C-指针,二级指针,二维数组作为函数参数使用,C语言链表(详解)

    一级指针 int *p;            //表示定义一个int型(4字节)的指针p &p                 //表示p自身的地址位置 p                  ...

  5. C语言复习---二维数组和二级指针的关系:没关系,别瞎想(重点)

    前提:一维数组和一维指针为什么可以替换使用? ] = { , , }; int *p = a; ; i < ; i++) printf("%d ", *(p + i)); 上 ...

  6. C语言指针系列 - 一级指针.一维数组,二级指针,二维数组,指针数组,数组指针,函数指针,指针函数

    1. 数组名 C语言中的数组名是一个特殊的存在, 从本质上来讲, 数组名是一个地址, 我们可以打印一个指针的值,和打印一个数组的值来观察出这个本质: int nArray[10] ={ 0 }; in ...

  7. C语言复习: 二级指针和多级指针

    二级指针内存模型建立 void main2() {     int i = 0;       //指针数组     char * p1[] = { "123", "456 ...

  8. 嵌入式-C语言基础:二级指针

    二级指针:可以理解为指向指针的指针,存放的是指针变量的地址. 下面用一级指针来保存一个指针变量的地址: #include<stdio.h> int main() { int *p1; in ...

  9. C++二级指针第二种内存模型(二维数组)

    C++二级指针第二种内存模型(二维数组) 二维数组 二维数组本质上是以数组作为数组元素的数组,即“数组的数组”. 定义 类型说明符 数组名[常量表达式][常量表达式] 例如: float a[3][4 ...

随机推荐

  1. keepalived+haproxy-部署高可用负载均衡

    环境: 准备两台机子,安装haproxy及keepalive都一样,只是配置不一样而已. 这里只说明一台机子上安装haproxy及keepalive. ======================== ...

  2. memcached全面剖析--5

    memcached的应用和兼容程序 mixi案例研究 mixi在提供服务的初期阶段就使用了memcached. 随着网站访问量的急剧增加,单纯为数据库添加slave已无法满足需要,因此引入了memca ...

  3. Aspose.Cells.dll引用导入导出Excel

    Aspose.Cells 导入导出EXCEL 文章出处:http://hi.baidu.com/leilongbing/item/c11467e1819e5417595dd8c1 修改样式       ...

  4. MySQL中SQL语句的分类

    1:数据定义语言(DDL) :创建和删除数据库(CREATE DATABASE || DROP  DATABASE):2:创建.修改.重命名.删除表(CREATE  TABLE || ALTER TA ...

  5. 软件测试 -- 和用户共同测试(UAT测试)的注意点有哪些

    软件产品在投产前,通常都会进行用户验收测试.如果用户验收测试没有通过,直接结果就是那不到“Money”,间接影响是损害了公司的形象,而后者的影响往往更严重.根据作者的经验,用户验收测试一定要让用户满意 ...

  6. Java使用memcached

    1.加载commons-pool-1.5.6.jar.java_memcached-release_2.6.6.jar.slf4j-api-1.6.1.jar.slf4j-simple-1.6.1.j ...

  7. 洛谷 P1052 过河

    题目描述 在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧.在桥上有一些石子,青蛙很讨厌踩在这些石子上.由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把独木桥上青蛙可能到达的点看成数 ...

  8. KVC vs KVO(内容为转载记录,整合大家的总结为我所用)

    KVC即key-value coding的缩写, KVO即key-value observing的缩写 假如需要掌握Key-Value Observing机制,那么需要阅读本文应该有帮助.本文提供了K ...

  9. ISNULL

    ISNULL 使用指定的替换值替换 NULL. 语法ISNULL ( check_expression , replacement_value ) 参数check_expression 将被检查是否为 ...

  10. location.search 详解

    JS中location.search什么意思 设置或获取网页地址跟在问号后面的部分 当以get方式在url中传递了请求参数时,可以利用location的search属性提取参数的值,下面的代码把参数的 ...