可存放任意类型变量的动态数组--C语言实现
之前在训练营的时候被要求用C语言实现一个可以存放任意类型数据的栈。现在尝试实现一个数组版本。
首先用到的结构体如下(接触了Win32编程所以长得有点像里面的那些类型):
typedef struct {
void *data; //用于保存数据的数组
size_t numOfElements; //表示数组元素个数
size_t sizeOfElements; //表示数组元素的大小
size_t capacity; //表示数组实际能容纳的元素个数
} VECTOR, *LPVECTOR;
在这里存放数据用的是void *指针,相较其余类型的指针,void *仅保存地址信息,不支持指针运算、解除引用、索引访问以及自增减操作。任意类型的指针都可以隐式转换成void *,而void *需要强制转换成指定类型的指针才可以使用其内部的数据。
由于void *不支持指针偏移操作,然而对于数组访问数据,指针偏移又是必要的。我们可以将其转换为char *,这样我们就可以对其偏移任意字节以访问任何位置了。
((char *)lpVec->data) + 5; //对data偏移5个字节
如果要取出数据,假定数据类型为double,那我们可以这样:
((double *)lpVec->data)[3]; //访问第4个double
对于void *,除了最基本的malloc()、realloc()和free()来管理内存外,我们还需要如下mem系列函数:
void * memset(void *_Dst,int _Val,size_t _Size);
//将_Dst所指向的某一块内存中的前_Size个字节内容全部设定为_Val指定的ASCII值
void * memcpy(void * _Dst,const void * _Src,size_t _Size);
//将_Src所指向的某一块内存中的前_Size个字节内容拷贝到_Dst所指向内存的起始位置中
void * memmove(void *_Dst,const void *_Src,size_t _Size);
//将_Src所指向的某一块内存中的前_Size个字节内容搬移到_Dst所指向内存的起始位置中
//支持重叠内存区域的操作,而memcpy不行
int memcmp(const void *_Buf1,const void *_Buf2,size_t _Size);
//比较_Buf1和_Buf2所指向的前_Size个字节
//相等时返回0,前者较大时返回1,后者较大时返回-1
//这里面没有用到该函数
然后是元素个数numOfElements和实际容量capacity,在分配内存的时候按实际容量来分配,而往往实际容量是比元素个数要大的,这样在数组元素增长的时候仅需要修改数组末的实际位置而不需要分配内存,仅当元素个数达到实际容量时才调用一次realloc()使得容量扩增为原来的两倍。这样做可以减少重分配的次数,以及节省数据迁移所花费的时间。
下面是功能函数的实现(用法和C++里的Vector比较像)
/*
* 用于创建一个VECTOR对象,可储存的元素个数为numOfElements
* 且元素大小为sizeOfElements的数组
*/
LPVECTOR VectorCreate(size_t numOfElements, size_t sizeOfElements) {
LPVECTOR lpVec = (LPVECTOR)malloc(sizeof(VECTOR));
lpVec->numOfElements = numOfElements;
lpVec->sizeOfElements = sizeOfElements;
//若元素个数小于5,预分配5个元素大小的实际容量
lpVec->capacity = numOfElements > 5 ? numOfElements : 5;
lpVec->data = malloc(lpVec->capacity * sizeOfElements);
//所有成员初始化为0
memset(lpVec->data, 0, lpVec->capacity * sizeOfElements);
return lpVec;
}
/*
* 用于重新分配内存,调整的是数组实际能容纳的元素个数。该函数只允
* 许在对数组元素个数有修改,可能要超出或远低于实际容纳量的情况下
* 才能调用它。不建议使用者直接调用该函数
*/
void VectorReallocate(LPVECTOR lpVec, size_t newCapacity) {
void *vptr = realloc(lpVec->data, newCapacity * lpVec->sizeOfElements);
if (vptr)
lpVec->data = vptr;
else
return;
lpVec->capacity = newCapacity;
if (lpVec->numOfElements > lpVec->capacity)
lpVec->numOfElements = lpVec->capacity;
}
//在数组现有元素的末端加入一个元素
void VectorPushBack(LPVECTOR lpVec, void *pVal) {
//元素个数即将突破容量大小时,分配2倍大小的容量
if (lpVec->numOfElements == lpVec->capacity)
VectorReallocate(lpVec, 2 * lpVec->capacity);
//将pVal所指向的数据复制到
memcpy((char*)lpVec->data + lpVec->numOfElements++ * lpVec->sizeOfElements, pVal, lpVec->sizeOfElements);
}
//删去数组末端的元素
void VectorPopBack(LPVECTOR lpVec) {
if (lpVec->numOfElements == 0)
return;
//元素个数即将达到实际容量的1/4以下时,将容量缩小为原来的1/2.
if (lpVec->numOfElements == lpVec->capacity / 4 && lpVec->capacity > 5)
{
VectorReallocate(lpVec, lpVec->capacity / 2);
lpVec->capacity /= 2;
}
//不需要删除数据,只需修改元素个数。
lpVec->numOfElements--;
}
//将数组元素个数置0,但实际元素容量置为5
void VectorClear(LPVECTOR lpVec) {
lpVec->numOfElements = 0;
VectorReallocate(lpVec, 5);
}
//将元素val插入到数组的索引pos位置上
void VectorInsert(LPVECTOR lpVec, size_t pos, void* pVal) {
if (pos > lpVec->numOfElements)
return;
else if (pos == lpVec->numOfElements)
VectorPushBack(lpVec, pVal); //此处可以直接使用前面的尾插函数
else
{
if (lpVec->numOfElements == lpVec->capacity)
VectorReallocate(lpVec, 2 * lpVec->capacity);
//将插入点后面的数据全部向右偏移sizeofElements字节
memmove((char*)lpVec->data + (pos + 1) * lpVec->sizeOfElements, (char*)lpVec->data + pos * lpVec->sizeOfElements, (lpVec->numOfElements++ - pos) * lpVec->sizeOfElements);
//将插入元素复制到插入点上
memcpy((char*)lpVec->data + pos * lpVec->sizeOfElements, pVal, lpVec->sizeOfElements);
}
}
//将数组索引区间[beg,end)的元素删除
void VectorErase(LPVECTOR lpVec, size_t beg, size_t end) {
if (beg > lpVec->numOfElements || end > lpVec->numOfElements || beg > end)
return;
//将删除区域末端后面的数据搬移至删除区域的起始位置
memmove((char*)lpVec->data + beg * lpVec->sizeOfElements, (char*)lpVec->data + end * lpVec->sizeOfElements, (lpVec->numOfElements - end) * lpVec->sizeOfElements);
//直接修改元素个数即可,被删除的元素是被直接覆盖掉
lpVec->numOfElements -= end - beg;
}
//遍历所有元素
void VectorTraversal(LPVECTOR lpVec, void(*func)(void*)) {
size_t i;
for (i = 0; i < lpVec->numOfElements; ++i)
func((char*)lpVec->data + i * lpVec->sizeOfElements);
}
//释放VECTOR内存,将指向的指针置为NULL
void VectorFree(LPVECTOR *ppVec) {
free((*ppVec)->data);
free(*ppVec);
*ppVec = NULL;
}
如果我们需要输出里面的数据,可以写一个回调函数:
void PrintInt(void* pVal)
{
printf("%d ", *(int *)pVal);
}
//...这样调用
VectorTraversal(lpVec, PrintInt);
最后是测试用的代码:
void PrintInt(void * lpVal)
{
printf("%d ", *(int *)lpVal);
}
void PrintDouble(void * lpVal)
{
printf("%.2f ",*(double *)lpVal);
}
int main()
{
LPVECTOR lpVec;
int i;
double v;
//测试开始
//使用int
lpVec = VectorCreate(0, sizeof(int));
for (i = 1;i <= 10;++i)
VectorPushBack(lpVec, &i);
VectorTraversal(lpVec, PrintInt);
puts("");
for (i = 0;i < 5;++i)
VectorPopBack(lpVec);
VectorTraversal(lpVec, PrintInt);
puts("");
VectorClear(lpVec);
VectorTraversal(lpVec, PrintInt);
puts("");
VectorFree(&lpVec);
if (lpVec)
puts("-1");
//使用double
lpVec = VectorCreate(0, sizeof(double));
v = 0.25;
for (i = 0;i < 5;++i, v += 0.25)
VectorPushBack(lpVec, &v);
VectorTraversal(lpVec, PrintDouble);
puts("");
for (i = 0;i < 5;++i, v -= 0.25)
VectorInsert(lpVec, 0, &v);
VectorTraversal(lpVec, PrintDouble);
puts("");
VectorErase(lpVec, 7, 17);
VectorTraversal(lpVec, PrintDouble);
puts("");
VectorFree(&lpVec);
if (lpVec)
puts("-1");
return 0;
}
可存放任意类型变量的动态数组--C语言实现的更多相关文章
- 动态数组C语言实现
/* * DynamicArray.h * * Created on: 2019年7月22日 * Author: Jarvis */ #ifndef SRC_DYNAMICARRAY_H_ #defi ...
- vector:动态数组
vector是C++标准模板库中的部分内容,中文偶尔译作“容器”,但并不准确.它是一个多功能的,能够操作多种数据结构和算法的模板类和函数库.vector之所以被认为是一个容器,是因为它能够像容器一样存 ...
- 【足迹C++primer】40、动态数组
动态数组 C++语言定义了第二种new表达式语法.能够分配并初始化一个对象数组.标准库中包括 一个名为allocator的类.同意我们将分配和初始化分离. 12.2.1 new和数组 void fun ...
- C语言基础 - 实现动态数组并增加内存管理
用C语言实现一个动态数组,并对外暴露出对数组的增.删.改.查函数 (可以存储任意类型的元素并实现内存管理) 这里我的编译器就是xcode 分析: 模拟存放 一个 People类 有2个属性 字符串类型 ...
- fortran常用语句--读写带注释文档、动态数组等语法
1.判断读取文档有多少行数据(文档最后的空行不计入其中): 首先在变量定义区域下方和执行语句前声明在程序中要被调用的GetFileN函数: external GetFileN 接下来在函数外部后边写上 ...
- C++学习之动态数组类的封装
动态数组(Dynamic Array)是指动态分配的.可以根据需求动态增长占用内存的数组.为了实现一个动态数组类的封装,我们需要考虑几个问题:new/delete的使用.内存分配策略.类的四大函数(构 ...
- 动态数组、allocator 类
12.2 动态数组 12.2.1 new 和数组 1.分配一个动态数组即是在分配一个new对象时在类型名之后加一对方括号,用来存放数组大小,该数可以是任意表达式.也可以是0,只需是整形.无需是常量.数 ...
- 一篇文章让你了解动态数组的数据结构的实现过程(Java 实现)
目录 数组基础简单回顾 二次封装数组类设计 基本设计 向数组中添加元素 在数组中查询元素和修改元素 数组中的包含.搜索和删除元素 使用泛型使该类更加通用(能够存放 "任意" 数据类 ...
- [数据结构1.2-线性表] 动态数组ArrayList(.NET源码学习)
[数据结构1.2-线性表] 动态数组ArrayList(.NET源码学习) 在C#中,存在常见的九种集合类型:动态数组ArrayList.列表List.排序列表SortedList.哈希表HashTa ...
随机推荐
- HTML5 客户端存储数据的两种方式
HTML5 提供了两种在客户端存储数据的新方法: localStorage - 没有时间限制的数据存储 sessionStorage - 针对一个 session 的数据存储 之前,这些都是由 coo ...
- mailto调用本地默认客户端发邮件
下面介绍如何利用 Mailto功能: 实现 Mailto的基本html代码: <a href="mailto:123@qq.com">点击这里发邮件!</a> ...
- 【Python3之面向对象的程序设计】
一.面向对象的程序设计的由来 1.第一阶段:面向机器,1940年以前 最早的程序设计都是采用机器语言来编写的,直接使用二进制码来表示机器能够识别和执行的指令和数据. 简单来说,就是直接编写 0 和 1 ...
- ajax基础知识、用ajax做登录页面、用ajax验证用户名是否可用、ajax动态调用数据库
1.ajax的基础知识 ajax是结合了jquery.php等几种技术延伸出来的综合运用的技术,不是新的内容.ajax也是写在<script>标签里面的. 如果使用ajax一定是要有1个处 ...
- easyUI的简单了解
首先简单的介绍一下jQuery EasyUI,它是一组基于jQuery的UI插件集合体,而jQuery EasyUI的目标就是帮助web开发者更轻松的打造出功能丰富并且美观的UI界面.开发者不需要编写 ...
- 团体队列UVA540 Team Queue(队列简单用法)
题目背景 队列和优先级队列是大多数计算机科学家都知道的数据结构.但是团队队列却不被人熟知,尽管在生活中经常出现.比如,午餐时间的食堂门口的队列就是一个团队队列.在一个团队队列中,每个元素属于一个团队. ...
- 【MFC】利用双缓冲和随机函数rand()实现蒲公英飞舞
原始日期:2014-05-29 22:44 这几天有些懒,几乎没怎么学MFC了,好容易有个题目:用双缓冲实现蒲公英飞舞,想来想去也没想到好方法,索性动手开始 写了 ,这一写,得,出来了,呵呵,无意中产 ...
- 使用Swagger实现webapi接口自动化文档生成
这里是实现自动化api稳当的生成,在网上看了很多swagger的文档,可能都是在为实现接口时直接使用的swagger,其实步骤差不多,但是更加详细的我还没看到,又或者说,我看着文档来的时候还是出错啦, ...
- ICG_System之全自动代码生成器V2.0版本
大家好! 早在2014年本人就已经利用业余时间开发自己的ICG之代码生成器系统.依靠bootstrap的崛起本人也在不断完善此应用.目的是为了减少开发人员的工作量. 减少不必要的复制粘贴操作,该系统已 ...
- LODOP之票据连续套打笔记<一>
之前项目中需要使用套打,费了半天劲,最后找到LODOP,整体感觉还是不错,简单,满足大多数web套打 这是我项目中需要打印的票据 该票据每张做多显示6条数据,数据超过6条的时候需要进行分页打印,当时做 ...