AWTK动态数组及其用法介绍
前言
使用C++的同学都知道,vector容器非常好用,它是一个动态数组,当空间不足时,可以自动扩展空间,而C语言只有静态数组,数组大小一旦确定,就不能扩展了,使用起来很不灵活。在使用C语言编写代码时,我们也期望能有像C++ vector一样功能强大的容器,为此,李老师的[AWTK](https://github.com/zlgopen/awtk)封装了一个动态数组:darray_t,它表现起来,和vector差不多。这篇博文就介绍darray_t的实现和应用,一起学习动态数组是怎么实现的。一、darray_t的设计
动态数组的设计类似C++的类,定义一个结构体存储动态数组的属性,提供一些操作接口来操作动态数组对象。由于C语言没有类,接口的第一个参数一般为动态数组对象,类似C++成员函数隐含的的this指针。原则上,任何对动态数组的操作,都应该通过提供的成员函数来实现,不应该直接去读写属性,C语言没有办法通过设置属性是public、protected、private,只能按约定的方式使用这些属性。在这个动态数组的属性定义中,没有特别指明属性的访问限制,那么就按最安全的只读方式获取属性的值,而不要去写这些属性。1.属性定义
typedef struct _darray_t {
/**
* @property {uint32_t} size
* @annotation ["readable"]
* 数组中元素的个数。
*/
uint32_t size;
/**
* @property {uint32_t} capacity
* @annotation ["readable"]
* 数组的容量大小。
*/
uint32_t capacity;
/**
* @property {void**} elms
* @annotation ["readable"]
* 数组中的元素。
*/
void** elms;
/**
* @property {tk_destroy_t} destroy
* @annotation ["readable"]
* 元素销毁函数。
*/
tk_destroy_t destroy;
/**
* @property {tk_compare_t} compare
* @annotation ["readable"]
* 元素比较函数。
*/
tk_compare_t compare;
} darray_t;
2.成员函数定义
1、构造和析构使用darray_create创建对象,需要使用darray_destroy来释放对象;
使用darray_init来初始化对象,需要使用darray_deinit来反初始化;
只需要使用其中的一组接口来使用动态数组就可以了,darray_create在内部也是使用darray_init来初始化动态数组对象的。
/**
* @method darray_create
* @annotation ["constructor"]
* 创建darray对象。
*
* @param {uint32_t} capacity 数组的初始容量。
* @param {tk_destroy_t} destroy 元素销毁函数。
* @param {tk_compare_t} compare 元素比较函数。
*
* @return {darray_t*} 数组对象。
*/
darray_t* darray_create(uint32_t capacity, tk_destroy_t destroy, tk_compare_t compare);
/**
* @method darray_init
* 初始化darray对象。
*
* @param {darray_t*} darray 数组对象。
* @param {uint32_t} capacity 数组的初始容量。
* @param {tk_destroy_t} destroy 元素销毁函数。
* @param {tk_compare_t} compare 元素比较函数。
*
* @return {darray_t*} 数组对象。
*/
darray_t* darray_init(darray_t* darray, uint32_t capacity, tk_destroy_t destroy,
tk_compare_t compare);
/**
* @method darray_deinit
* 清除全部元素,并释放elms。
* @param {darray_t*} darray 数组对象。
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t darray_deinit(darray_t* darray);
/**
* @method darray_destroy
* 销毁darray对象。
* @param {darray_t*} darray 数组对象。
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t darray_destroy(darray_t* darray);
2、插入和删除接口/**
* @method darray_insert
* 插入一个元素。
* @param {darray_t*} darray 数组对象。
* @param {uint32_t} index 位置序数。
* @param {void*} data 待插入的元素。
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t darray_insert(darray_t* darray, uint32_t index, void* data);
/**
* @method darray_remove
* 删除第一个满足条件的元素。
* @param {darray_t*} darray 数组对象。
* @param {void*} ctx 比较函数的上下文。
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t darray_remove(darray_t* darray, void* ctx);
/**
* @method darray_remove_index
* 删除指定位置的元素。
* @param {darray_t*} darray 数组对象。
* @param {uint32_t} index 位置序数。
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t darray_remove_index(darray_t* darray, uint32_t index);
/**
* @method darray_remove_all
* 删除全部满足条件的元素。
* @param {darray_t*} darray 数组对象。
* @param {tk_compare_t} cmp 比较函数,为NULL则使用内置的比较函数。
* @param {void*} ctx 比较函数的上下文。
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t darray_remove_all(darray_t* darray, tk_compare_t cmp, void* ctx);
3、队头和队尾操作接口/**
* @method darray_pop
* 弹出最后一个元素。
* @param {darray_t*} darray 数组对象。
*
* @return {void*} 成功返回最后一个元素,失败返回NULL。
*/
void* darray_pop(darray_t* darray);
/**
* @method darray_tail
* 返回最后一个元素。
* @param {darray_t*} darray 数组对象。
*
* @return {void*} 成功返回最后一个元素,失败返回NULL。
*/
void* darray_tail(darray_t* darray);
/**
* @method darray_head
* 返回第一个元素。
* @param {darray_t*} darray 数组对象。
*
* @return {void*} 成功返回最后一个元素,失败返回NULL。
*/
void* darray_head(darray_t* darray);
/**
* @method darray_push
* 在尾巴追加一个元素。
* @param {darray_t*} darray 数组对象。
* @param {void*} data 待追加的元素。
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t darray_push(darray_t* darray, void* data);
4、查找和读取接口/**
* @method darray_find
* 查找第一个满足条件的元素。
* @param {darray_t*} darray 数组对象。
* @param {void*} ctx 比较函数的上下文。
*
* @return {void*} 如果找到,返回满足条件的对象,否则返回NULL。
*/
void* darray_find(darray_t* darray, void* ctx);
/**
* @method darray_bsearch_index
* 二分查找(确保数组有序)。
*
* @param {darray_t*} darray 数组对象。
* @param {tk_compare_t} cmp 比较函数,为NULL则使用内置的比较函数。
* @param {void*} ctx 比较函数的上下文。
*
* @return {int32_t} 如果找到,返回满足条件的对象的位置,否则返回-1。
*/
int32_t darray_bsearch_index(darray_t* darray, tk_compare_t cmp, void* ctx);
/**
* @method darray_bsearch
* 二分查找(确保数组有序)。
*
* @param {darray_t*} darray 数组对象。
* @param {tk_compare_t} cmp 比较函数,为NULL则使用内置的比较函数。
* @param {void*} ctx 比较函数的上下文。
*
* @return {void*} 如果找到,返回满足条件的对象,否则返回NULL。
*/
void* darray_bsearch(darray_t* darray, tk_compare_t cmp, void* ctx);
/**
* @method darray_get
* 获取指定序数的元素。
* @param {darray_t*} darray 数组对象。
* @param {uint32_t} index 序数。
*
* @return {void*} 返回满足条件的对象,否则返回NULL。
*/
void* darray_get(darray_t* darray, uint32_t index);
/**
* @method darray_find_index
* 查找第一个满足条件的元素,并返回位置。
* @param {darray_t*} darray 数组对象。
* @param {void*} ctx 比较函数的上下文。
*
* @return {int32_t} 如果找到,返回满足条件的对象的位置,否则返回-1。
*/
int32_t darray_find_index(darray_t* darray, void* ctx);
/**
* @method darray_find_all
* 查找全部满足条件的元素。
*
* ```
* darray_t matched;
* darray_init(&matched, 0, NULL, NULL);
* darray_find_all(darray, mycmp, myctx, &matched);
* ...
* darray_deinit(&matched);
*
* ```
* @param {darray_t*} darray 数组对象。
* @param {tk_compare_t} cmp 比较函数,为NULL则使用内置的比较函数。
* @param {void*} ctx 比较函数的上下文。
* @param {darray_t*} matched 返回满足条件的元素。
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t darray_find_all(darray_t* darray, tk_compare_t cmp, void* ctx, darray_t* matched);
/**
* @method darray_count
* 返回满足条件元素的个数。
* @param {darray_t*} darray 单向链表对象。
* @param {void*} ctx 比较函数的上下文。
*
* @return {int32_t} 返回元素个数。
*/
int32_t darray_count(darray_t* darray, void* ctx);
5、排序接口/**
* @method darray_sort
* 排序。
* @param {darray_t*} darray 数组对象。
* @param {tk_compare_t} cmp 比较函数,为NULL则使用内置的比较函数。
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t darray_sort(darray_t* darray, tk_compare_t cmp);
6、清空和遍历接口/**
* @method darray_clear
* 清除全部元素。
* @param {darray_t*} darray 数组对象。
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t darray_clear(darray_t* darray);
/**
* @method darray_foreach
* 遍历元素。
* @param {darray_t*} darray 数组对象。
* @param {tk_visit_t} visit 遍历函数。
* @param {void*} ctx 遍历函数的上下文。
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t darray_foreach(darray_t* darray, tk_visit_t visit, void* ctx);
具体的实现源码可以在李老师的AWTK中找到(https://github.com/zlgopen/awtk/blob/master/src/tkc/darray.h、darray.c)。
二、darray_t的使用
darray_t动态数组其实在我之前的博文中有应用到:[AWTK工厂模式--符合开闭原则的工厂模式](https://blog.csdn.net/woody218/article/details/112447126),这里,具体列一下AWTK工厂模式中,如何使用darray_t的。1、定义darray_t对象:darray_t creators;
/**
* @class data_reader_factory_t
* data reader工厂。
*
*/
typedef struct _data_reader_factory_t {
/*private*/
darray_t creators;
} data_reader_factory_t;
2、初始化动态数组对象:darray_init
static data_reader_factory_t* data_reader_factory_init(data_reader_factory_t* factory) {
return_value_if_fail(factory != NULL, NULL);
darray_init(&(factory->creators), 0, default_destroy, (tk_compare_t)creator_item_cmp);
return factory;
}
3、插入数据到动态数组:darray_push
ret_t data_reader_factory_register(data_reader_factory_t* factory, const char* protocol,
data_reader_create_t create) {
creator_item_t* item = NULL;
return_value_if_fail(factory != NULL && protocol != NULL && create != NULL, RET_BAD_PARAMS);
item = TKMEM_ZALLOC(creator_item_t);
return_value_if_fail(item != NULL, RET_OOM);
item->create = create;
tk_strncpy(item->protocol, protocol, TK_NAME_LEN);
darray_push(&(factory->creators), item);
return RET_OK;
}
4、查找元素:darray_find
data_reader_t* data_reader_factory_create_reader(data_reader_factory_t* factory, const char* url) {
char protocol[TK_NAME_LEN + 1];
const char* p = NULL;
const creator_item_t* iter = NULL;
return_value_if_fail(factory != NULL && url != NULL, NULL);
p = strchr(url, ':');
memset(protocol, 0x00, sizeof(protocol));
if (p != NULL) {
return_value_if_fail((p - url) < TK_NAME_LEN, NULL);
tk_strncpy(protocol, url, p - url);
} else {
tk_strncpy(protocol, "file", 4);
}
iter = darray_find(&(factory->creators), (void*)protocol);
if (iter != NULL) {
p = strstr(url, "://");
if (p == NULL) {
p = url;
} else {
p += 3;
}
return iter->create(p);
}
return NULL;
}
5、反初始化:darray_deinit
static ret_t data_reader_factory_deinit(data_reader_factory_t* factory) {
return_value_if_fail(factory != NULL, RET_BAD_PARAMS);
darray_deinit(&(factory->creators));
return RET_OK;
}
总结
动态数组的使用和C++的vector基本上是一致,只是这里的动态数组没有实现跟迭代器相关的接口。动态数组,最关键的功能,就是自动扩展内存。下面是内存扩展内部函数源码实现:static bool_t darray_extend(darray_t* darray) {
if (darray->elms != NULL && darray->size < darray->capacity) {
return TRUE;
} else {
void* elms = NULL;
uint32_t capacity = (darray->capacity >> 1) + darray->capacity + 1;
elms = TKMEM_REALLOCT(void*, darray->elms, capacity);
if (elms) {
darray->elms = elms;
darray->capacity = capacity;
return TRUE;
} else {
return FALSE;
}
}
}
总结:
1、所有对外提供的插入数据的接口内部,在实际插入数据之前,需要调用这个函数,确保有足够的空间后,才插入数据,darray_insert这个接口就是这样工作的,在实际插入数据之前,先调用了darray_extend,确保有空间,才插入数据;
2、当容器没有剩余空间,需要扩展时,需要按一定的策略申请一块更大的内存,然后把原来的数据拷贝到新内存当中,最后释放旧内存。在AWTK当中,可以使用TKMEM_REALLOCT重新申请一块内存,其内部会负责新内存的申请、数据拷贝及旧内存释放;
3、从前面两点中,我们可以看到,如果容器的起始容量过低,频繁触发内存扩容,程序的执行效率将会非常低。所以,初始化的时候,我们应该指定一个比较合理的初始容量,尽量避免容器过早或频繁扩容。C++的vector使用也一样,虽然我们可以不指定初始容量,然后直接插入数据,程序一样能工作,但是,如果插入的数据又多,插入频率又快,vector将频繁触发扩容操作,你会发现,程序卡死了!可见,指定一个合理的容量参数,非常重要。