【他山之玉】C语言动态数组的实现

519 阅读7分钟

0. 引言

在C语言中,数组的长度是固定不变的。它不像C++那样有动态大小数组的vector。所以在平时的工作中,我们也会实现C语言版的动态数组。今天看到一个C语言实现的动态数组CLIST。在这里记录一下。

1. CLIST整体介绍

首先,我们来看一下头文件clist.h。首先是实现动态数组的数据结构Clist:

typedef struct CList
{
  void  (* add)        (struct CList *l, void *o);            /* Add object to the end of a list */
  void  (* insert)     (struct CList *l, void *o, int n);     /* Insert object at position 'n' */
  void  (* replace)    (struct CList *l, void *o, int n);     /* Replace object at position 'n' */
  void  (* remove)     (struct CList *l, int n);              /* Remove object at position 'n' */
  void* (* at)         (struct CList *l, int n);              /* Get object at position 'n' */
  int   (* realloc)    (struct CList *l, int n);              /* Reallocate list to 'size' items */
  int   (* firstIndex) (struct CList *l, void *o);            /* Get first index of the object */
  int   (* lastIndex)  (struct CList *l, void *o);            /* Get last index of the object */
  int   (* count)      (struct CList *l);                     /* Get list size */
  void  (* clear)      (struct CList *l);                     /* Clear list */
  void  (* free)       (struct CList *l);                     /* Destroy struct CList and all data */
  void  (* print)      (struct CList *l, int n, const char *type);  /* Print list data */
  void *priv;          /* NOT FOR USE, private data */
} CList;

这里,开发作者将动态数组的一些常见功能以函数指针的方式,都放到了结构体Clist了,作为Clist的成员。作为C语言的开发人员,我还是在平时的开发中,很少遇见这样的方式的。这样写应该是为了C++的同学考虑的吧。这些函数的具体功能,在后面一一分析。最后还有一个指针成员priv,这个其实才是实现动态数组的数据结构,这里为了不具体的对用户暴露,所以写成了一个万能指针,具体的数据结构在.c中实现。

其次,Clist.h中还有一个函数声明:

CList *CList_Init(size_t objSize); /* Set list object size in bytes */

这个是初始化Clist的,入参是对象的大小,然后返回值是Clist类型的指针。

接下来我们来看一下clist.c文件。

2. Clist私有数据

Clist的动态数组,就一个以下数据结构:

typedef struct
{  
  int count;          /* Number of items in the list. */  
  int alloc_size;     /* Allocated size in quantity of items */  
  size_t item_size;   /* Size of each item in bytes. */  
  void *items;        /* Pointer to the list */  
} CList_priv_;  

这里, count是当前数组中的元素个数; alloc_size是当前数组最大的个数,也就是数组的容量, 当count == alloc_size时,数组就会动态扩容。item_size是每个对象的大小。items就是存储具体数据的数组了。

3. 初始化动态数组: CList_Init

CList *CList_Init(size_t objSize)
{
  CList *lst = malloc(sizeof(CList));
  CList_priv_ *p = malloc(sizeof(CList_priv_));
  if (!lst || !p) {
    fprintf(stderr, "CList: ERROR! Can not allocate CList!\n");
    return NULL;
  }
  p->count = 0;
  p->alloc_size = 0;
  p->item_size = objSize;
  p->items = NULL;
  lst->add = &CList_Add_;
  lst->insert = &CList_Insert_;
  lst->replace = &CList_Replace_;
  lst->remove = &CList_Remove_;
  lst->at = &CList_At_;
  lst->realloc = &CList_Realloc_; 
  lst->firstIndex = &CList_FirstIndex_;
  lst->lastIndex = &CList_LastIndex_;
  lst->count = &CList_Count_;
  lst->clear = &CList_Clear_;
  lst->free = &CList_Free_;
  lst->print = &CList_print_;
  lst->priv = p;
  return lst;
}

这个没啥好说的,函数的功能就是初始化数据结构CList。

4. 在动态数组末尾添加元素:CList_Add_

void CList_Add_(CList *l, void *o)
{
  CList_priv_ *p = (CList_priv_*) l->priv;
  if (p->count == p->alloc_size && 
        CList_Realloc_(l, p->alloc_size * 2) == 0)
    return;
  
  char *data = (char*)p->items;
  data = data + p->count * p->item_size;
  memcpy(data, o, p->item_size);
  p->count++;
}

这里首先判断一下,是否需要动态扩容: 如果当前数组的个数count等于当前数组的容量alloc_size,则调用CList_Realloc_函数,为数组扩容,扩容后,数组的容量是当前容量的2倍。然后,通过指针的偏移,找到数组的最后,将对象拷贝到数组中,并更新当前数组个数。

5. 在数组索引为n的位置插入元素: CList_Insert_

void CList_Insert_(CList *l, void *o, int n)
{
  CList_priv_ *p = (CList_priv_*) l->priv;
  if (n < 0 || n > p->count) {
    fprintf(stderr, "CList: ERROR! Insert position outside range - %d; n - %d.\n",  p->count, n);
    assert(n >= 0 && n <= p->count);
    return;
  }

  if (p->count == p->alloc_size && 
      CList_Realloc_(l, p->alloc_size * 2) == 0)
    return;

  size_t step = p->item_size;
  char *data = (char*) p->items + n * step;
  memmove(data + step, data, (p->count - n) * step);
  memcpy(data, o, step);
  p->count++;
}

CList_Insert_首先也是判断是否需要扩容。然后,通过指针偏移找到插入元素的位置。通过memove和memcpy将新元素插入。

6. 在数组索引为n的位置替换元素:CList_Replace_

void CList_Replace_(CList *l, void *o, int n)
{
  CList_priv_ *p = (CList_priv_*) l->priv;
  if (n < 0 || n >= p->count)
  {
    fprintf(stderr, "CList: ERROR! Replace position outside range - %d; n - %d.\n", 
                        p->count, n);
    assert(n >= 0 && n < p->count);
    return;
  }

  char *data = (char*) p->items;
  data = data + n * p->item_size;
  memcpy(data, o, p->item_size);
}

这个也没啥可说的,就是替换第n个元素。

7. 删除第n个元素: CList_Remove_

void CList_Remove_(CList *l, int n)
{
  CList_priv_ *p = (CList_priv_*) l->priv;
  if (n < 0 || n >= p->count)
  {
    fprintf(stderr, "CList: ERROR! Remove position outside range - %d; n - %d.\n",
                        p->count, n);
    assert(n >= 0 && n < p->count);
    return;
  }

  size_t step = p->item_size;
  char *data = (char*)p->items + n * step;
  memmove(data, data + step, (p->count - n - 1) * step);
  p->count--;

  if (p->alloc_size > 3 * p->count && p->alloc_size >= 4) /* Dont hold much memory */
    CList_Realloc_(l, p->alloc_size / 2);
}

这里需要注意的是最后,如果当前数组的容量大于数组元素个数的3倍,并且容量大于等于4,它会将数组的容量减少为当前数组容量的一半。这样做的目的是减少空闲内存的消耗。

8. 获取第n个元素:CList_At_

void *CList_At_(CList *l, int n)
{
  CList_priv_ *p = (CList_priv_*) l->priv;
  if (n < 0 || n >= p->count)
  {
    fprintf(stderr, "CList: ERROR! Get position outside range - %d; n - %d.\n", 
                      p->count, n);
    assert(n >= 0 && n < p->count);
    return NULL;
  }

  char *data = (char*) p->items;
  data = data + n * p->item_size;
  return data;
}

9. 动态数组扩容: CList_Realloc_

int CList_Realloc_(CList *l, int n)
{
  CList_priv_ *p = (CList_priv_*) l->priv;
  if (n < p->count)
  {
    fprintf(stderr, "CList: ERROR! Can not realloc to '%i' size - count is '%i'\n", n, p->count);
    assert(n >= p->count);
    return 0;
  }

  if (n == 0 && p->alloc_size == 0)
    n = 2;

  void *ptr = realloc(p->items, p->item_size * n);
  if (ptr == NULL)
  {
    fprintf(stderr, "CList: ERROR! Can not reallocate memory!\n");
    return 0;
  }
  p->items = ptr;
  p->alloc_size = n;
  return 1;
}

这里扩容后的容量是n,如果n小于当前数组元素的大小,就不会扩容,直接返回了。

10. 返回第一个等于指定值的下标:CList_FirstIndex_

int CList_FirstIndex_(CList *l, void *o)
{ 
  CList_priv_ *p = (CList_priv_*) l->priv;    
  char *data = (char*) p->items;
  size_t step = p->item_size;
  size_t i = 0;
  int index = 0;
  for (; i < p->count * step; i += step, index++)
  {
    if (memcmp(data + i, o, step) == 0)
      return index;
  }
  return -1; 
}

11. 返回最后一个等于指定值的下标:CList_LastIndex_

int CList_LastIndex_(CList *l, void *o)
{
  CList_priv_ *p = (CList_priv_*) l->priv;
  char *data = (char*) p->items;
  size_t step = p->item_size;
  int i = p->count * step - step;
  int index = p->count - 1;
  for (; i >= 0 ; i -= step, index--)
  {
    if (memcmp(data + i, o, step) == 0)
      return index;
  }
  return -1;
}

12. 获取当前数组的元素个数: CList_Count_

int CList_Count_(CList *l)
{
  CList_priv_ *p = (CList_priv_*) l->priv;
  return p->count;
}

13. 清理动态数组数据: CList_Clear_

void CList_Clear_(CList *l)
{
  CList_priv_ *p = (CList_priv_*) l->priv;
  free(p->items);
  p->items = NULL;
  p->alloc_size = 0;
  p->count = 0;
}

14. 销毁动态数组: CList_Free_

void CList_Free_(CList *l)
{
  CList_priv_ *p = (CList_priv_*) l->priv;
  free(p->items);
  free(p);
  free(l);
  l = NULL;
}

15. 打印数组: CList_print_

void CList_print_(CList *l, int n, const char *type)
{
  CList_priv_ *p = (CList_priv_*) l->priv;
  printf("\nCList:  count = %i  item_size = %zu   "
       "alloc_size = %i \n", p->count, p->item_size, p->alloc_size);

  if (n > 0)
  {
    n = (n > p->count) ? p->count : n;
    char *data = NULL;
    int i = 0;
    for (; i < n; i++)
    {
      data = (char*) p->items + i * p->item_size;
      if (type == NULL) printf("%p  ", data);  /* Print out pointers */
      else if (strcmp(type, "char") == 0) printf("%c ", *(char*) data);
      else if (strcmp(type, "short") == 0) printf("%hi  ", *(short*) data);
      else if (strcmp(type, "int") == 0) printf("%i  ", *(int*) data);
      else if (strcmp(type, "long") == 0) printf("%li  ", *(long*) data);
      else if (strcmp(type, "uintptr_t") == 0) printf("%zx  ", (uintptr_t*) data);
      else if (strcmp(type, "size_t") == 0) printf("%zu  ", *(size_t*) data);
      else if (strcmp(type, "double") == 0) printf("%f  ", *(double*) data);
      else if (strcmp(type, "string") == 0) printf("%s\n", data);
      else { printf("Unknown type."); break; }
    }
    printf("\n\n");
  }
}

16 具体用法

int main()
{
    struct unit {
    long int size;
    void *ptr;
    } unit;
    CList *list = CList_Init(sizeof(unit));     /* Initialization */

    long int i; 
    for (i = 0; i < 10; i++) {
        struct unit U = { i, &i };
        list->add(list, &U);            /* Adding data at the end */
        list->insert(list, &U, 0);      /* Insert at position 0 */    
    }
    /* 打印元素 此时数组元素为: 
     * 9  8  7  6  5  4  3  2  1  0  0  1  2  3  4  5  6  7  8 
     */
    i = list->count(list);            /* Get number of items in the list */
    list->print(list, i, "long");     /* Print out 'i' elements of list, first element of struct is shown */

    /* Get item at position '1' */
    /* 下标为1的元素,输出当然是8 */
    struct unit *tmp = (struct unit*) list->at(list, 1);  
    printf("Unit size is %li, pointer is %p \n", tmp->size, tmp->ptr);

    /* 删除第一个元素,数组变成:
     * 9  7  6  5  4  3  2  1  0  0  1  2  3  4  5  6  7  8  9
     */
    list->remove(list, 1);
    i = list->count(list);            /* Get number of items in the list */
    list->print(list, i, "long");     /* Print out 'i' elements of list, first element of struct is shown */

    /* 替换第一个元素为100 */
    long int j = 100;
    struct unit tmp1 = {j, &j};
    list->replace(list, &tmp1, 1);
    i = list->count(list);            /* Get number of items in the list */
    list->print(list, i, "long");     /* Print out 'i' elements of list, first element of struct is shown */

    /* 清除数组元素,执行后数组中没有元素了 */
    list->clear(list);
    i = list->count(list);            /* Get number of items in the list */
    list->print(list, i, "long");     /* Print out 'i' elements of list, first element of struct is shown */

    list->free(list);                 /* Destroy all data and list */ 
    return 0;
}

输出:

CList:  count = 20  item_size = 16   alloc_size = 32
9  8  7  6  5  4  3  2  1  0  0  1  2  3  4  5  6  7  8  9  

Unit size is 8, pointer is 000000000061FDFC

CList:  count = 19  item_size = 16   alloc_size = 32
9  7  6  5  4  3  2  1  0  0  1  2  3  4  5  6  7  8  9


CList:  count = 19  item_size = 16   alloc_size = 32
9  100  6  5  4  3  2  1  0  0  1  2  3  4  5  6  7  8  9


CList:  count = 0  item_size = 16   alloc_size = 0