可存放任意类型变量的动态数组--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 ...
随机推荐
- android登录实现,存储数据到/data/data/包名/info.txt
1.一个简单登录界面布局代码如下: @1采用线性布局加相对布局方式 @2线性布局采用垂直排列 <?xml version="1.0" encoding="utf-8 ...
- 水平方向的RecyclerView
最近做了一个项目需要实现一个卡片式的水平滑动,但是不能手势滑动,点击卡片上的按钮之后滑动到下一个卡片,所以想到用RecyclerView实现,去掉它的手势滑动,点击按钮之后再代码控制滑动到下一个卡片. ...
- .NET ORM框架 SqlSuagr4.0 功能详解与实践【开源】
SqlSugar 4.0 ORM框架的优势 为了未来能够更好的支持多库分布式的存储,并行计算等功能,将SqlSugar3.x全部重写,现有的架构可以轻松扩展多库. 源码下载: https://gith ...
- scala环境配置+hello world!
下载地址: http://www.scala-lang.org/download/ 我下载的是zip 配置环境变量 需要jdk支持,jdk的安装配置此处略过 控制台命令 scala -version ...
- centOS6.4安装python3.5,并且安装pip
前言: 如果你也是用的centos系统,打算装python3.0以上版本,再装python下载工具pip,那么恭喜你,你可能也会像我一样遇到各种各样的问题! 另外非常重要的一点:centos都会自带p ...
- ecshop开发帮助
http://www.ecshop.com/template_tutorial/ ECSHOP模板结构说明 http://help.ecshop.com/index.php ECSHOP帮助中心 ht ...
- Redis中的数据对象
redis对象 redis中有五种常用对象 我们所说的对象的类型大多是值的类型,键的类型大多是字符串对象,值得类型大概有以下几种,但是无论哪种都是基于redisObject实现的 redisObjec ...
- 小程序之发起请求 wx.request(object)的坑
这是官方的API,然后官方的实例中 wx.request({ url: 'test.php', //仅为示例,并非真实的接口地址 data: { x: '' , y: '' }, header: { ...
- PDO数据库操作类
<?php include 'common_config.php'; /** * Class Mysql * PDO数据库操作类 */ class Mysql { protected stati ...
- Swift数组的存取与修改
对数组的存取与修改可以通过数组的方法和属性来进行,或者使用数组的下标语法. 要知道数组中元素的数量,可以查看它的只读属性count: println("The shopping list c ...