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