可存放任意类型变量的动态数组--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 ...
随机推荐
- 高CPU、数据库无法读写的真凶
有兴趣的同学可以参考如下系列文章,都是针对dump分析的实战和总结: Windbg DUMP分析(原创汇总) http://www.cnblogs.com/LoveOfPrince/p/6653341 ...
- 浅谈如何保证discuz插件安全
1.非直接执行程序请加上 if(!defined('IN_DISCUZ')) { exit('Access Denied'); } 2.记得过滤 比如说uid等id需要intval过滤,避免溢出 ...
- [codeforces113D]Museum
D. Museum time limit per test: 2 seconds memory limit per test: 256 megabytes input: standard input ...
- 基于servlet和ajax的聊天室
(手贱点了更新发布时间,发布时间变成6-9...) 2017-5-20,在这个奇特的日子,我不再满足于在本地测试javaweb,于是在上腾讯云买了第一个云服务器,由于是学生认证,所以一个月只要10块钱 ...
- JAVA优雅停机的实现
最近在项目中需要写一个数据转换引擎服务,每过5分钟同步一次数据.具体实现是启动engine server后会初始化一个ScheduledExecutorService和一个ThreadPoolExec ...
- 《HelloGitHub》第 15 期
公告 这段时间没怎么写文章,跑去写 https://hellogithub.com 这个网站了,现在已经顺利上线,功能后面会持续迭代. 最后,这个 https://hellogithub.com 网站 ...
- 中奖概率算法(php 可用于刮刮卡,大转盘等抽奖算法)
<?php //中奖概率算法(php 可用于刮刮卡,大转盘等抽奖算法) /* * 经典的概率算法, * $proArr是一个预先设置的数组, * 假设数组为:array(100,200,300, ...
- linux虚拟机下安装samba
默认情况下,Linux系统在默认安装中已经安装了Samba服务包的一部分 ,为了对整个过程有一个完整的了解,在此先将这部分卸载掉.使用命令 rpm -qa | grep samba ,默认情况下可以查 ...
- memcached使用文档
使用memcached进行内存缓存 通常的网页缓存方式有动态缓存和静态缓存等几种,在ASP.NET中已经可以实现对页面局部进行缓 存,而使用memcached的缓存比ASP.NET的局部缓存更加灵活, ...
- 读RCNN论文笔记
1. RCNN的模型(如下图)描述: RCNN相比传统的物体检测,还是引入传统的物体检测的基本流程,先找出候选目标物体,逐个的提取特征,不过rbg大神引入了当时炙手可热的CNN卷积网络取代传统上的HO ...