工学1号馆

home

基于C语言的类型安全的动态数组的实现

By Wu Yudong on November 16, 2016

本文针对一个开源的使用C实现的一个类型安全的动态数组,进行源代码分析,进一步学习提升C语言

项目源代码在这里,代码只包含两个文件:vec.h和vec.c,下面来剖析一下里面的代码

本文地址:http://wuyudong.com/2016/11/16/3026.html,转载请注明出处。

vec.h 代码:

常规的C数组,只包含数组的首地址,长度在初始化的时候已经定了,后面不能再更改,这里要实现动态数组,必须在原来的基础上添加几个字段,数组长度(length)和数组容量(capacity),而且数组的类型不定,设计成泛型,综合以上几点,这里使用宏实现,代码如下:

#define vec_t(T)\
  struct { T *data; int length, capacity; }

初始化数组,使用memset函数:

#define vec_init(v)\
  memset((v), 0, sizeof(*(v)))

销毁数组,释放资源:

#define vec_deinit(v)\
  ( free((v)->data),\
    vec_init(v) )

数组拆包:

#define vec_unpack_(v)\
  (char**)&(v)->data, &(v)->length, &(v)->capacity, sizeof(*(v)->data)

获取数组顶部(最后一个)元素,有点类似栈:

#define vec_pop(v)\
  (v)->data[--(v)->length]

将vec_splice_进行封装,减少参数个数:

#define vec_splice(v, start, count)\
  ( vec_splice_(vec_unpack_(v), start, count),\
    (v)->length -= (count) )

将vec_swapsplice_进行封装,减少参数个数:

#define vec_swapsplice(v, start, count)\
  ( vec_swapsplice_(vec_unpack_(v), start, count),\
    (v)->length -= (count) )

插入一个元素到数组,失败返回-1,成功返回0:

#define vec_push(v, val)\
  ( vec_expand_(vec_unpack_(v)) ? -1 :\
    ((v)->data[(v)->length++] = (val), 0), 0 )

将vec_insert_进行封装,减少参数个数:

#define vec_insert(v, idx, val)\
  ( vec_insert_(vec_unpack_(v), idx) ? -1 :\
    ((v)->data[idx] = (val), 0), (v)->length++, 0 )

清除数组元素:

#define vec_clear(v)\
  ((v)->length = 0)

缩短数组长度:

#define vec_truncate(v, len)\
  ((v)->length = (len) < (v)->length ? (len) : (v)->length)

获取数组第一个元素:

#define vec_first(v)\
  (v)->data[0]

获取数组最后一个元素:

#define vec_last(v)\
  (v)->data[(v)->length - 1]

将vec_reserve_进行封装,通过宏vec_unpack_减少参数个数:

#define vec_reserve(v, n)\
  vec_reserve_(vec_unpack_(v), n)

将vec_compact_进行封装,通过宏vec_unpack_减少参数个数:

#define vec_compact(v)\
  vec_compact_(vec_unpack_(v))

将arr数组的count个元素依次插入到数组v的末端:

#define vec_pusharr(v, arr, count)\
  do {\
    int i__, n__ = (count);\
    if (vec_reserve_po2_(vec_unpack_(v), (v)->length + n__) != 0) break;\
    for (i__ = 0; i__ < n__; i__++) {\
      (v)->data[(v)->length++] = (arr)[i__];\
    }\
  } while (0)

注意:i__的作用是防止在程序中出现局部变量i产生冲突

调用宏实现将数组v2依次插入数组v末端:

#define vec_extend(v, v2)\
  vec_pusharr((v), (v2)->data, (v2)->length)

查找一个元素是否在数组中:

#define vec_find(v, val, idx)\
  do {\
    for ((idx) = 0; (idx) < (v)->length; (idx)++) {\
      if ((v)->data[(idx)] == (val)) break;\
    }\
    if ((idx) == (v)->length) (idx) = -1;\
  } while (0)

删除数组中的一个元素:

#define vec_remove(v, val)\
  do {\
    int idx__;\
    vec_find(v, val, idx__);\
    if (idx__ != -1) vec_splice(v, idx__, 1);\
  } while (0)

给数组元素排序,fn为函数指针,表明升降序:

#define vec_sort(v, fn)\
  qsort((v)->data, (v)->length, sizeof(*(v)->data), fn)

将vec_swap_进行封装,减少参数个数:

#define vec_swap(v, idx1, idx2)\
  vec_swap_(vec_unpack_(v), idx1, idx2)

将数组元素位置反转:

#define vec_reverse(v)\
  do {\
    int i__ = (v)->length / 2;\
    while (i__--) {\
      vec_swap((v), i__, (v)->length - (i__ + 1));\
    }\
  } while (0)

正向遍历数组元素:

#define vec_foreach(v, var, iter)\
  if  ( (v)->length > 0 )\
  for ( (iter) = 0;\
        (iter) < (v)->length && (((var) = (v)->data[(iter)]), 1);\
        ++(iter))

注意:(iter) >= 0 && (((var) = (v)->data[(iter)]), 1); 最后的1,表示当var是NULL的时候,还能继续执行for循环

逆向遍历数组元素:

#define vec_foreach_rev(v, var, iter)\
  if  ( (v)->length > 0 )\
  for ( (iter) = (v)->length - 1;\
        (iter) >= 0 && (((var) = (v)->data[(iter)]), 1);\
        --(iter))

正向遍历指向数组元素的指针:

#define vec_foreach_ptr(v, var, iter)\
  if  ( (v)->length > 0 )\
  for ( (iter) = 0;\
        (iter) < (v)->length && (((var) = &(v)->data[(iter)]), 1);\
        ++(iter))

逆向遍历指向数组元素的指针:

#define vec_foreach_ptr_rev(v, var, iter)\
  if  ( (v)->length > 0 )\
  for ( (iter) = (v)->length - 1;\
        (iter) >= 0 && (((var) = &(v)->data[(iter)]), 1);\
        --(iter))

vec.c 代码

1、容量扩充函数vec_expand_():

int vec_expand_(char **data, int *length, int *capacity, int memsz) {
  if (*length + 1 > *capacity) { //当length==capacity,扩充容量
    void *ptr;
    int n = (*capacity == 0) ? 1 : *capacity << 1; 
    //当起始容量为0,分配一个单位,否则为原来的一倍

    ptr = realloc(*data, n * memsz); //memsz为元素大小
    if (ptr == NULL) return -1;
    *data = ptr;
    *capacity = n;
  }
  return 0;
}

2、数组容量预留函数vec_reserve_():

int vec_reserve_(char **data, int *length, int *capacity, int memsz, int n) {
  (void) length;
  if (n > *capacity) {
    void *ptr = realloc(*data, n * memsz);
    if (ptr == NULL) return -1;
    *data = ptr;
    *capacity = n;
  }
  return 0;
}

3、数组容量预留函数vec_reserve_po2_():

//数组预留空间不小于n的空间n2,且n2为2的幂
int vec_reserve_po2_(char **data, int *length, int *capacity, int memsz, int n) {
  int n2 = 1;
  if (n == 0) return 0;
  while (n2 < n) n2 <<= 1;
  return vec_reserve_(data, length, capacity, memsz, n2);
}

4、在数组的指定位置插入元素函数vec_insert_():

int vec_insert_(char **data, int *length, int *capacity, int memsz, int idx) {
  int err = vec_expand_(data, length, capacity, memsz); 
  if (err) return err; 
  memmove(*data + (idx + 1) * memsz,
          *data + idx * memsz,
          (*length - idx) * memsz);
  return 0;
}

说明一下memmove函数,函数原型如下:

void *memmove(void *dst,  const void *src,  size_t count);

将src开始的count数量的内存块内容到dst开始的内存块,与memcpy函数的区别为当内存发生局部重叠的时候,memmove保证拷贝的结果是正确的。

由src所指内存区域复制count个字节到dest所指内存区域。src和dest所指内存区域可以重叠,但复制后dest内容会被更改。函数返回指向dest的指针。

memmove的处理措施:

(1)当源内存的首地址等于目标内存的首地址时,不进行任何拷贝

(2)当源内存的首地址大于目标内存的首地址时,实行正向拷贝

(3)当源内存的首地址小于目标内存的首地址时,实行反向拷贝


vec_insert_函数的原理是将数组的索引idx位置到数组最后元素之间的元素全部后移一个元素位,空出一个位来存放待插入的元素

5、元素覆盖函数vec_splice_():

void vec_splice_(char **data, int *length, int *capacity, int memsz, int start, int count) {
  (void) capacity;
  memmove(*data + start * memsz,
          *data + (start + count) * memsz,
          (*length - start - count) * memsz);
}

将start位置处开始的count个元素由start+count开始的count个元素替换。

注意:(void) capacity; 这条语句对程序上来说是没有意义的,只是告诉编译器,这个变量我(假装)使用了,不要提示变量未使用的警告。(这个变量在程序中未使用的情况下)

6、交换数组指定位置的两个元素vec_swap_():

void vec_swap_(char **data, int *length, int *capacity, int memsz, int idx1, int idx2) {
  unsigned char *a, *b, tmp;
  int count;
  (void) length;
  (void) capacity;
  if (idx1 == idx2) return;
  a = (unsigned char*) *data + idx1 * memsz;
  b = (unsigned char*) *data + idx2 * memsz;
  count = memsz;
  while (count--) {
    tmp = *a;
    *a = *b;
    *b = tmp;
    a++, b++;
  }
}

通过阅读理解源代码,学到不少关于C语言编程技巧

如果文章对您有帮助,欢迎点击下方按钮打赏作者

Comments

No comments yet.
To verify that you are human, please fill in "七"(required)