数据结构之通用动态数组
引言
1.关于数组我们之前做过简单的介绍,数组是一种物理和逻辑都连续的块存储空间,关于数组的定义主要有三个方式:
//数组定义的三种方式:
int array1[] = {
12, 23, 34, 45, 56, 67, 78}; //方法1
int array2[100] = {
0}; //方法2
int *array3 = NULL;
array3 = (int *)malloc(sizeof(int) * 100); //方法3
if(array3 != NULL){
fprintf(stderr, "the memory is full!\n");
exit(1);
}
array1和array2在主函数的栈帧上进行定义,字符指针array3是在主函数的栈帧上进行定义的,但是array3所指向的申请空间却是在内存的堆上,它们的内存分布是不同的。
常规数组存在的问题?
忽略数组元素插入和删除的效率低下,常规数组最重要的问题在于数组的规模无法实时地增大或减小,这会极大的影响程序的数据存储。基于上述的问题,我们急需一种能够动态进行扩充的数组(即动态数组)
动态数组
创建动态数组的条件必须满足主存储区域在堆上,因为栈上的空间一旦申请是无法更改大小的,二如果我们把数组存放在堆上则可以借助系统函数malloc和realloc进行动态的扩充,上述两个函数的声明如下所示:
//在堆上申请size大小的内存,如果申请成功把该块内存第一个字节的地址返回给调用者
void *malloc(size_t size);
//先判断当前的指针是否有足够的连续空间,如果有,扩大mem_address指向的地址,并且将mem_address返回,如果空间不够,先按照newsize指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来mem_address所指内存区域(注意:原来指针是自动释放,不需要使用free),同时返回新分配的内存区域的首地址。即重新分配存储器块的地址。
void *realloc(void *ptr, size_t size);
基于上述两个函数,我们可以在第一次使用malloc初始化数组,在以后的使用过程中,如果数组被占用完,就可以使用realloc进行内存的再申请。
如下图所示:
通用动态数组
通用动态数组不仅满足我们上述的要求可以进行动态的扩充和缩小,还能够实现对任意类型元素的存储。关于动态数组的目录结构如下图所示:
在这个结构中我们给出了对动态数组遍历的另外一种方式,没有采用数组的下标直接进行访问,而是采用了迭代器组件,这个组件在java还有C++等面向对象语言中大量的使用。在C语言中我们也可以采用结构体方式构造出迭代器。
关于迭代器的问题我们是接触过的,不管是数组的下标索引方式,还是指针的指向和加减操作,都是迭代器的表现形式。但是这里所提出的迭代器将会忽略掉数据结构的细节,而只是做一个对数据结构进行遍历的“指针”。
整个迭代器的操作在iterator.h文件中进行实现:
//iterator.h文件,通用迭代器的实现
#ifndef _ITERATOR_H
#define _ITERATOR_H
typedef struct ITERATOR
{
void *ptr; //迭代器指针,与容器相关
void *data; //用户数据指针
int data_len; //用户数据长度(可以忽略)
const char *key; //若为哈希表的迭代器,则为哈希键值地址
int klen; //若为binhash迭代器,则为健长度
int i ; //当前迭代器在容器中的位置索引
int size; //当前容器中元素的总个数
}ITERATOR;
/*
* 正向遍历容器中的元素
* iter 迭代器
* container 容器地址
* */
#define FOREACH(iter, container) \
for((container)->iter_head(&(iter), (container)); \
(iter).ptr; \
(container)->iter_next(&(iter), (container)))
/*
* 反向便利容器中的元素
* iter 迭代器
* container 容器地址
* */
#define FOREACH_REVERSE(iter, container) \
for((container)->iter_tail(&(iter), (container)); \
(iter).ptr; \
(container)->iter_prev(&(iter), (container)))
/*
* 获得当前迭代器指针与某容器关联的成员结构类型对象
* iter 迭代器
* container 容器地址
*
* */
#define ITER_INFO(iter, container) \
((container)->iter_info(&(iter), (container)))
#define foreach FOREACH
#define foreach_reverse FOREACH_REVERSE
#define iter_info ITER_INFO
#endif
foreach和foreach_reverse两个宏都是迭代其对于数据结构正向和反向的两种方式,只需写入foreach或foreach_reverse就可以完成对数据结构的遍历,关于迭代其的操作数据结构需要满足以下条件:
1.相应的数据结构必须要提供迭代器操作的四个基本接口:iter_head、iter_tail、iter_next、iter_prev。这样才可以在数据结构中进行双向的遍历。
2.一旦所操作的数据结构发生变化(元素的删除或添加),则之前的迭代器失效。必须重新对迭代器的位置进行初始化。
使用迭代其的方式如下图所示:
关于动态数组的接口实现在arrray.h文件中,如下图所示:
//动态数组的头文件(结构和接口声明)
#ifndef _ARRAY_H_
#define _ARRAY_H_
#include <stdio.h>
#include <stdlib.h>
#include "iterator.h"
#define TRUE (1)
#define FALSE (0)
#define ZERO (0)
#define FIRST (0)
#define STEP_SIZE (64)
typedef unsigned char Boolean;
struct Array; //动态数组结构体
typedef struct Array Array;
struct Array{
int capacity; //动态数组的容量
int count; //数组元素个数
void **data; //动态数组数据
void (*free)(void *ptr); //元素的释放
Boolean (*match)(