數據結構:二級指針與Stack的數組實現

【簡介】css

Stack,棧結構,即傳統的LIFO,後進先出,經常使用的實現方法有數組法和鏈表法兩種。若是看過我上一篇文章《數據結構:二級指針與不含表頭的單鏈表》,必定會看到其中的關鍵在於,利用void*指針將數據結構抽象出來,適用於任何數據類型。此次嘗試利用void**,兩級void指針,用數組法實現Stack的數據結構。html

【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;

這裏的棧底是一個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:  }

上面的代碼便可新建一個Stack類型的實例。全部的Stack類型實例都存放於Stack_Pool中,而不是由malloc分配而來的。ide

下面是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:  }

其中出棧Pop的操做將會填充pp_data_out指針,所以調用該函數時須要傳入一個void*類型的以及指針便可,將在第21或26行修改其中內容。測試

能夠看到,入棧和出棧並無出現一般Stack操做中的Memory Copy操做(C標準庫中的string.h中有函數memcpy實現內存拷貝。若是不想包含string.h,好比說在嵌入式編程時,也能夠本身寫一個相似函數實現memcpy,之後有機會在介紹,很簡單的),後面的分析會介紹這樣作的優點。spa

固然利用函數返回值進行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:  }

最後我寫了一個打印棧內容的功能函數以下:

   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:  }

第一個傳入參數爲,棧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:  }

能夠看到,新建棧是,Stack中的void ** base實際指向一個void* 類型的數組void * STK_1[10],這個棧的大小是10。每次入棧時傳入的都是int STK_Value[5]中的成員。到第22行時棧中已經消耗了6了,24行進行第一次棧內容打印。到26行時取出一個打印,直到34行入棧滿了,以後的都沒法入棧。39行進行第二次棧內容打印。運行結果以下:

image

能夠看到棧內容跟入棧順序是一致的。

這裏能夠看到,棧的大小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

相關文章
相關標籤/搜索