C语言实现动态数组--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将频繁触发扩容操作,你会发现,程序卡死了!可见,指定一个合理的容量参数,非常重要。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值