可存放任意类型变量的动态数组--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 ...
随机推荐
- Layered Windows窗口的半透明效果
介绍: Layered Windows是windows窗口中的一类,提供类似半透明的效果(阿尔法混合).半透明效果是字面上有能看出来的,但实际上根据MSND,该类型的窗口还能更好的支持非矩形的窗口,使 ...
- 静态代码块详解(原出处:http://versioneye.iteye.com/blog/1129579)
一 般情况下,如果有些代码必须在项目启动的时候就执行的时候,需要使用静态代码块,这种代码是主动执行的;需要在项目启动的时候就初始化,在不创建对象的情 况下,其他程序来调用的时候,需要使用静态方法,这种 ...
- java io 流
Java流操作有关的类或接口: Java流类图结构: 流的概念和作用 流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象.即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输 ...
- Python处理Excel文件
因为工作需求,需要审核一部分query内容是否有效,query储存在Excel中,文本内容为页面的Title,而页面的URL以HyperLink的格式关联到每个Cell. 于是本能的想到用Python ...
- ios在Xcode里关于图片的权限设置
<key>NSPhotoLibraryUsageDescription</key> <string>This app requires access to the ...
- JS 事件派发器EventDispatcher
在Java和AS中经常用到EventDispatcher,写了一个JS版本的. addListener :添加事件监听器 removeListener:移除事件监听器 dispatchEvent:派发 ...
- Spring+SpringMVC+MyBatis深入学习及搭建(十四)——SpringMVC和MyBatis整合
转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/7010363.html 前面讲到:Spring+SpringMVC+MyBatis深入学习及搭建(十三)--S ...
- OpenResty知识汇集
OpenResty目录详解: nginx_lua_module执行顺序:
- [leetcode-474-Ones and Zeroes]
In the computer world, use restricted resource you have to generate maximum benefit is what we alway ...
- android怎么输出信息到logcat