之前在训练营的时候被要求用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语言实现的更多相关文章

  1. 动态数组C语言实现

    /* * DynamicArray.h * * Created on: 2019年7月22日 * Author: Jarvis */ #ifndef SRC_DYNAMICARRAY_H_ #defi ...

  2. vector:动态数组

    vector是C++标准模板库中的部分内容,中文偶尔译作“容器”,但并不准确.它是一个多功能的,能够操作多种数据结构和算法的模板类和函数库.vector之所以被认为是一个容器,是因为它能够像容器一样存 ...

  3. 【足迹C++primer】40、动态数组

    动态数组 C++语言定义了第二种new表达式语法.能够分配并初始化一个对象数组.标准库中包括 一个名为allocator的类.同意我们将分配和初始化分离. 12.2.1 new和数组 void fun ...

  4. C语言基础 - 实现动态数组并增加内存管理

    用C语言实现一个动态数组,并对外暴露出对数组的增.删.改.查函数 (可以存储任意类型的元素并实现内存管理) 这里我的编译器就是xcode 分析: 模拟存放 一个 People类 有2个属性 字符串类型 ...

  5. fortran常用语句--读写带注释文档、动态数组等语法

    1.判断读取文档有多少行数据(文档最后的空行不计入其中): 首先在变量定义区域下方和执行语句前声明在程序中要被调用的GetFileN函数: external GetFileN 接下来在函数外部后边写上 ...

  6. C++学习之动态数组类的封装

    动态数组(Dynamic Array)是指动态分配的.可以根据需求动态增长占用内存的数组.为了实现一个动态数组类的封装,我们需要考虑几个问题:new/delete的使用.内存分配策略.类的四大函数(构 ...

  7. 动态数组、allocator 类

    12.2 动态数组 12.2.1 new 和数组 1.分配一个动态数组即是在分配一个new对象时在类型名之后加一对方括号,用来存放数组大小,该数可以是任意表达式.也可以是0,只需是整形.无需是常量.数 ...

  8. 一篇文章让你了解动态数组的数据结构的实现过程(Java 实现)

    目录 数组基础简单回顾 二次封装数组类设计 基本设计 向数组中添加元素 在数组中查询元素和修改元素 数组中的包含.搜索和删除元素 使用泛型使该类更加通用(能够存放 "任意" 数据类 ...

  9. [数据结构1.2-线性表] 动态数组ArrayList(.NET源码学习)

    [数据结构1.2-线性表] 动态数组ArrayList(.NET源码学习) 在C#中,存在常见的九种集合类型:动态数组ArrayList.列表List.排序列表SortedList.哈希表HashTa ...

随机推荐

  1. Spring事务管理—aop:pointcut expression解析

    先来看看这个spring的配置文件的配置: <!-- 事务管理器 --> <bean id="transactionManager"  class="o ...

  2. JS键盘事件对象之keyCode、charCode、which属性对比

    先说一些有关键盘事件的事项:用js实现键盘记录,要关注浏览器的三种按键事件类型,即keydown,keypress和keyup,它们分别对应onkeydown. onkeypress和onkeyup这 ...

  3. SSH的Eclips环境搭建

    一.创建数据库名字为:dungouoa create database dungouoa default character set utf8; mysql> show create datab ...

  4. PHPCMS v9点击量增加值加大的方法

    PHPCMS v9点击量增加值加大的方法 在根目录/api 50行 $views = $r['views'] + 1; 修改数字1即可修改每次刷新页面点击量增加的数值.

  5. Ubuntu下安装并配置VS Code编译C++

    作者:tongqingliu 转载请注明出处:http://www.cnblogs.com/liutongqing/p/7069091.html Ubuntu下安装并配置VS Code编译C++ 安装 ...

  6. 流畅的python学习笔记:第五章

    在python中一切都可以视作为对象,包括函数.我们来看个例子: def function_try():     '''it is funciton try doc'''     print 'fun ...

  7. Angular中使用Swiper不能滑动的解决方法

    Swiper是目前较为流行的移动端触摸滑动插件,因为其简单好用易上手,很受很多设计师的欢迎. 今天在使用Swiper的时候遇到这个问题: 使用angularjs动态循环生成swiper-slide类, ...

  8. 如何在非 React 项目中使用 Redux

    本文作者:胡子大哈 原文链接:https://scriptoj.com/topic/178/如何在非-react-项目中使用-redux 转载请注明出处,保留原文链接和作者信息. 目录 1.前言 2. ...

  9. 矩阵的f范数及其求偏导法则

    转载自: http://blog.csdn.net/txwh0820/article/details/46392293 矩阵的迹求导法则   1. 复杂矩阵问题求导方法:可以从小到大,从scalar到 ...

  10. 【Android Developers Training】 108. 使用模拟定位进行测试

    注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...