5.数据结构之通用动态数组

本文介绍了动态数组的概念和创建条件,强调了动态数组在内存堆上扩展的优势。通过malloc和realloc函数实现数组动态扩容。接着讨论了通用动态数组,它允许存储任意类型元素,并引入迭代器组件进行遍历。文章详细阐述了迭代器的四个基本接口及其使用,以及动态数组接口的实现,包括数据结构和工具函数。最后,通过main.c展示了动态数组的实践应用,预示将探讨如何利用动态数组实现栈和队列。
摘要由CSDN通过智能技术生成

数据结构之通用动态数组

引言

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)(
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值