utarray:C语言动态数组宏
一、简介
1.1 介绍
utarray.h
中包含了一组用于C语言结构的通用动态数组宏。使用起来非常简单,只需要将utarray.h
拷贝到你的源码目录,并在你的程序中使用它:
动态数组支持数组的基本操作,例如:push、pop和erase。数组的元素可以是任何基本数据类型或结构体类型,数组的操作类似于C++中STL vector的方法。
动态数组内部包含一块连续的内存区域,数组元素会拷贝到该区域,并通过realloc方法扩展以容纳更多push到里面的数据。
1.2 源码获取
utarray.h
源码可以在github
上直接获取到(src/utarray.h):https://github.com/troydhanson/uthash
对应的国内gitee
仓库:https://gitee.com/mirrors/uthash
二、使用方法
2.1 声明
数组的数据类型是UT_array
,与其存储的数据类型无关,通过如下方式声明:
UT_array *nums;
2.2 创建和释放
调用utarray_new
来创建array,使用完数组之后,调用utarray_free
来释放数组包括其内部所有元素。
2.3 Push、Pop等
utarray.h
核心功能是包含了元素的操作:push
、pop
和iterate
操作,可以通过多种操作来一次处理一个元素或者多个元素,下面的示例中,仅使用push来插入元素。
三、元素
使用整型或字符串类型作为元素,是动态array最简单的例子。
3.1 整型
下面示例创建了一个整型utarray
,push数字0 ~ 9到其中,然后打印它们,最后释放该数组。
int main(){
UT_array *nums;
int i, *p;
utarray_new(nums, &ut_int_icd);
for(i=0; i < 10; i++) utarray_push_back(nums, &i);
for(p=(int*)utarray_front(nums);
p!=NULL;
p=(int*)utarray_next(nums, p)) {
printf("%d\n",*p);
}
utarray_free(nums);
return 0;
}
注意:utarray_push_back
的第二个参数必须是指向元素类型的指针,对于整型,它的类型是int*
。
3.2 字符串
下面示例创建了一个字符串utarray
,push两个字符串到其中,然后打印它们,最后释放该数组。
int main() {
UT_array *strs;
char *s, <strong>p;
utarray_new(strs, &ut_str_icd);
s = "hello"; utarray_push_back(strs, &s);
s = "world"; utarray_push_back(strs, &s);
p = NULL;
while ((p=(char</strong>)utarray_next(strs, p))) {
printf("%s\n",*p);
}
utarray_free(strs);
return 0;
}
本例中,元素类型是char*
,因此我们传入参数类型为char
。注意:**push操作生成一个字符串的副本并将其拷贝到数组中。
3.3 关于UT_icd
utarray
不仅仅支持整型和字符串(使用预定义的ut_int_icd
和ut_str_icd
),还支持其他任何类型的元素。你需要定义一个UT_icd
辅助结构体,它包含utarray
用到的初始化、拷贝和销毁元素操作。
typedef struct {
size_t sz;
init_f *init;
ctor_f *copy;
dtor_f *dtor;
}
其中init
、copy
和dtor
函数指针的原型如下:
typedef void (ctor_f)(void *dst, const void *src); /* 拷贝函数 */
typedef void (dtor_f)(void *elt); /* 销毁函数 */
typedef void (init_f)(void *elt); /* 初始化函数 */
UT_icd
辅助结构体的成员说明如下:
- sz:是需要保存到
utarray
的元素的大小; - init:该函数在
utarray
初始化一个元素时使用,在utarray_resize
或utarray_extend_back
操作时调用。如果init
指针为NULL,默认使用memset
将所有元素设置为0; - copy:该函数在复制元素到
utarray
时使用,在utarray_push_back
、utarray_insert
、utarray_inserta
和utarray_concat
操作时调用。如果copy
指针为NULL,默认用memcpy
按位进行拷贝; - dtor:该函数在元素从
utarray
中移除时使用,在utarray_resize
、utarray_pop_back
、utarray_erase
、utarray_clear
、utarray_done
和utarray_free
操作时调用;如果元素在销毁时不需要释放资源,则dtor
可以设置为NULL。
3.3.1 标准数据类型
下面的示例使用UT_icd
及其默认方法来创建一个元素类型为long的utarray
,例子中push两个long数字到其中,然后打印它们,最后释放数组。
/* long elements */
UT_icd long_icd = {sizeof(long), NULL, NULL, NULL };
int main() {
UT_array *nums;
long l, *p;
utarray_new(nums, &long_icd);
l=1; utarray_push_back(nums, &l);
l=2; utarray_push_back(nums, &l);
p=NULL;
while( (p=(long*)utarray_next(nums,p))) printf("%ld\n", *p);
utarray_free(nums);
return 0;
}
3.3.2 结构体
结构体也可以作为utarray
的元素。如果结构体不需要特别的初始化、拷贝和销毁处理操作,我们可以使用UT_icd
及其默认方法,下面的例子展示了包含两个整型的结构体,在这里将push两个数值,然后打印它们,最后释放数组。
1)结构体(简单)
/* structure type */
typedef struct {
int a;
int b;
} intpair_t;
UT_icd intpair_icd = {sizeof(intpair_t), NULL, NULL, NULL};
int main() {
UT_array *pairs;
intpair_t ip, *p;
utarray_new(pairs,&intpair_icd);
ip.a=1; ip.b=2; utarray_push_back(pairs, &ip);
ip.a=10; ip.b=20; utarray_push_back(pairs, &ip);
for(p=(intpair_t*)utarray_front(pairs);
p!=NULL;
p=(intpair_t*)utarray_next(pairs,p)) {
printf("%d %d\n", p->a, p->b);
}
utarray_free(pairs);
return 0;
}
如果结构体需要有特殊的初始化、拷贝和销毁操作时,UT_icd
的作用就体现出来了。比如,当我们的结构体包含一个指针指向另外一块内存区域的时候,在结构体拷贝时做相应的内存拷贝(在结构体释放时做相关的释放),我们可以自定义UT_icd
中对应的init
、copy
和dtor
方法。
这里用到了两个概念:
- 浅拷贝:只是拷贝结构体中的内容。
- 深拷贝:将结构体与结构体指针成员指向的所有内存全部拷贝。
下面是一个实现深拷贝的示例:定义了一个整型和字符串类型的结构体类型数据,初始化时分配字符串内容,拷贝的时候拷贝字符串内容,释放的时候也要释放字符串的内容。
2)结构体(复杂)
typedef struct {
int a;
char *s;
} intchar_t;
void intchar_copy(void *_dst, const void *_src) {
intchar_t *dst = (intchar_t*)_dst, *src = (intchar_t*)_src;
dst->a = src->a;
dst->s = src->s ? strdup(src->s) : NULL;
}
void intchar_dtor(void *_elt) {
intchar_t *elt = (intchar_t*)_elt;
if (elt->s) free(elt->s);
}
UT_icd intchar_icd = {sizeof(intchar_t), NULL, intchar_copy, intchar_dtor};
int main() {
UT_array *intchars;
intchar_t ic, *p;
utarray_new(intchars, &intchar_icd);
ic.a=1; ic.s="hello"; utarray_push_back(intchars, &ic);
ic.a=2; ic.s="world"; utarray_push_back(intchars, &ic);
p=NULL;
while( (p=(intchar_t*)utarray_next(intchars,p))) {
printf("%d %s\n", p->a, (p->s ? p->s : "null"));
}
utarray_free(intchars);
return 0;
}
四、引用
下表列出了utarray
常用的操作宏,所有方法类似于C++的vector
类。
OPERATIONS | DESCRIPTIONS |
utarray_new(UT_array *a, UT_icd *icd) | allocate a new array |
utarray_free(UT_array *a) | free an allocated array |
utarray_init(UT_array *a,UT_icd *icd) | init an array (non-alloc) |
utarray_done(UT_array *a) | dispose of an array (non-allocd) |
utarray_reserve(UT_array *a,int n) | ensure space available for 'n' more elements |
utarray_push_back(UT_array *a,void *p) | push element p onto a |
utarray_pop_back(UT_array *a) | pop last element from a |
utarray_extend_back(UT_array *a) | push empty element onto a |
utarray_len(UT_array *a) | get length of a |
utarray_eltptr(UT_array *a,int j) | get pointer of element from index |
utarray_eltidx(UT_array *a,void *e) | get index of element from pointer |
utarray_insert(UT_array *a,void *p, int j) | insert element p to index j |
utarray_inserta(UT_array *a,UT_array *w, int j) | insert array w into array a at index j |
utarray_resize(UT_array *dst,int num) | extend or shrink array to num elements |
utarray_concat(UT_array *dst,UT_array *src) | copy src to end of dst array |
utarray_erase(UT_array *a,int pos,int len) | remove len elements from a[pos]..a[pos+len-1] |
utarray_clear(UT_array *a) | clear all elements from a, setting its length to zero |
utarray_sort(UT_array *a,cmpfcn *cmp) | sort elements of a using comparison function |
utarray_find(UT_array *a,void *v, cmpfcn *cmp) | find element v in utarray (must be sorted) |
utarray_front(UT_array *a) | get first element of a |
utarray_next(UT_array *a,void *e) | get element of a following e (front if e is NULL) |
utarray_prev(UT_array *a,void *e) | get element of a before e (back if e is NULL) |
utarray_back(UT_array *a) | get last element of a |
五、注意事项
-
utarray_new
和utarray_free
用于分配和释放一个utarray
,而utarray_init
和utarray_done
是用在utarray
已经分配之后,用于初始化和释放数组内部结构的资源。 -
utarray_reserve
用于预留增量个数的元素空间(不是utarray
的所有容量),这点不同于C++ STL的"reserve"概念。 -
utarray_sort
需要一个类似于strcmp的比较函数,它接收两个参数a和b,在a大于b是返回正数,在a等于b时返回0,在a小于b时返回负数。下面是一个比较函数的例子:
int intsort(const void *a, const void *b) {
int _a = *(const int *)a;
int _b = *(const int *)b;
return (_a < _b) ? -1 : (_a > _b);
}
-
utarray_find
依据提供的比较函数,使用二分查找(bsearch)特定的元素。utarray
在查找之前必须先使用相同的比较函数完成排序(utarray_sort使用快速qsort完成排序)。下面是一个字符串utarray
示例:
static int strsort(const void *_a, const void *_b)
{
const char *a = *(const char* const *)_a;
const char *b = *(const char* const *)_b;
return strcmp(a,b);
}
int main()
{
UT_array *strs;
const char *s, <strong>p;
utarray_new(strs,&ut_str_icd);
s = "hello"; utarray_push_back(strs, &s);
s = "world"; utarray_push_back(strs, &s);
s = "one"; utarray_push_back(strs, &s);
s = "two"; utarray_push_back(strs, &s);
s = "three"; utarray_push_back(strs, &s);
p = NULL;
while ( (p=(const char</strong>)utarray_next(strs,p)) != NULL ) {
s = *p;
printf("%s\n",s);
}
printf("sorting\n");
utarray_sort(strs,strsort);
p = NULL;
while ( (p=(const char**)utarray_next(strs,p)) != NULL ) {
s = *p;
printf("finding %s\n",s);
p = utarray_find(strs,&s,strsort);
printf(" %s\n", (p != NULL) ? (*p) : "failed");
}
utarray_free(strs);
return 0;
}
- 指向特定元素的指针(通过调用
utarray_eltptr
,utarray_front
,utarray_next
,utarray_prev
,utarray_back
来获得) 会在有任何元素插入后失效,因为内部内存管理realloc可能会将元素重新指向到新的地址,出于这个原因,最好在代码中通过整数索引utarray_eltptr
来引用元素。
int main(){
UT_array *nums;
int i, *p;
utarray_new(nums, &ut_int_icd);
for(i=0; i < 10; i++) utarray_push_back(nums, &i);
for(i = 0; i < utarray_len(nums); i++) {
printf("%d\n",*(utarray_eltptr(nums, i)));
}
utarray_free(nums);
return 0;
}
- 如果要覆盖默认的内存不足的处理行为(调用exit(-1)),请在包含
utarray.h
之前需改utarray_oom()
宏,例如:
#define utarray_oom() do { longjmp(error_handling_location); } while (0)
#include "utarray.h"
- 如果我们要求插入数组的元素次序不能变,就不能使用
utarray_find()
宏。而要采用遍历的查找方式:
/**
* utarray_find_no_sort()
* lookup elements in array by sequence and as a replacement of utarray_find().
*
* DO NOT use utarray_find() since array must be sorted by calling utarray_sort()
* before calling utarray_find().
*/
static void* utarray_find_no_sort(UT_array *objects, void *eltpos, int(*cmpfn)(const void *elt, const char *findob), const void *findob)
{
void *elt = eltpos;
while ((elt = utarray_next(objects, elt)) != NULL) {
if (!cmpfn(elt, findob)) {
break;
}
}
return elt == eltpos? NULL : elt;
}