C语言 -- 动态数组&链表

2023-10-27

目录

动态数组

 动态数组的实现:

用户test

链表

目的:

链表的结构体:

链表的实现:

初始化链表

插入节点

遍历链表

删除节点

清空链表、销毁链表

用户回调函数

给用户提供接口获取链表长度

用户test


动态数组

将数组开辟到堆区,实现动态扩展。

问题:

① 用户的数据类型无法确定;

② 用户的数据无法确定创建在堆区还是栈区;

③ 不管数据在堆区还是栈上,都是在内存中,就会有地址,只需维护数据的地址就行。eg:如果数据类型是 int,则使用 int* 来指向该数据地址。

所以,这里使用万能指针 void* 来指向用户的数据地址。

第二点,我们是在堆区创建的一个数组,每一个元素都是 void* 类型的。如果要操纵这个数组,则可以使用二级指针 void** 来指向该数组的地址。

 动态数组的实现:

typedef struct DynamicArray
{
	void** pAddr;	//维护真实在堆区开辟的数组的二级指针
	int m_Capacity; //数组容量
	int m_Size;     //数组大小
}dynamicArray;
//初始化数组,参数:初始数组的容量。返回值:数组指针
dynamicArray* init_dynamicArray(int capacity)
{
	if (capacity <= 0)
	{
		return NULL;
	}
	//给数组分配内存
	dynamicArray* arr = malloc(sizeof(dynamicArray));
	if (arr == NULL)
		return NULL;
	//数组属性初始化
	arr->pAddr = malloc(sizeof(void*) * capacity); //我们维护的数据类型是void*,
 //这里是capacity个void*类型的数据大小,又malloc返回的是void**类型,pAddr同样是void**类型
	arr->m_Capacity = capacity;
	arr->m_Size = 0;
}
//数组元素插入
void insert_dynamicArray(dynamicArray* arr, void* data, int pos)
{
	if (arr == NULL)
		return;
	if (data == NULL)
		return;
	if (pos<0 || pos>arr->m_Size)
		pos = arr->m_Size;
	//判断数组是否满了
	if (arr->m_Size == arr->m_Capacity)
	{
		//计算新的空间大小
		int newCapacity = arr->m_Capacity * 2;
		//开辟新空间
		void** newSpace = malloc(sizeof(void*) * newCapacity);
		//将原空间下数据拷贝到新空间下
		memcpy(newSpace, arr->pAddr, sizeof(void*) * arr->m_Capacity);
		//释放原空间
		free(arr->pAddr);
		//更改指向
		arr->pAddr = newSpace;
		//更新容量
		arr->m_Capacity = newCapacity;
	}	
	//将新元素插入到指定位置
	for (int i = arr->m_Size - 1; i >= pos; i--)  //从最后边的数据开始移
	{
		arr->pAddr[i + 1] = arr->pAddr[i];
	}
	arr->pAddr[pos] = data;
	//更新数组大小
	arr->m_Size++;
}
//遍历
void foreach_dynamicArray(dynamicArray* arr,void(*myPrint)(void*))
{
	if (arr == NULL)
		return;
	for (int i = 0; i < arr->m_Size; i++)
	{
		myPrint(arr->pAddr[i]);
	}
}
//删除数组中元素
//按照位置删除
void removeByPos_dynamicArray(dynamicArray* arr, int pos)
{
	if (arr == NULL)
		return;
	if (pos<0 || pos>arr->m_Size - 1)
		return;
	for (int i = pos; i < arr->m_Size; i++)
	{
		arr->pAddr[i] = arr->pAddr[i + 1];
	}
	arr->m_Size--;
}
//按照数值删除
void removeByVal_dynamicArray(dynamicArray* arr, void* data, int(*myCompare)(void*,void*))
{
	if (arr == NULL)
		return;
	if (data == NULL)
		return;
	for (int i = 0; i < arr->m_Size; i++)
	{
		if (myCompare(arr->pAddr[i], data))//利用回调函数让用户告诉我们如何对比数据
		{
			removeByPos_dynamicArray(arr,i);
			break;
		}
	}
}

void destory_dynamicArray(dynamicArray* arr)
{
	if (arr == NULL)
		return;
	//内部维护在堆区数组指针先释放
	if (arr->pAddr != NULL)
	{
		free(arr->pAddr);
		arr->pAddr = NULL;
	}
	free(arr);
	arr = NULL;
}

用户test

typedef struct Person  //用户的数据类型
{
	char name[32];
	int age;
}person;
//回调函数,打印用户数据
void printPerson(void* data)
{
	person* person = data;
	printf("Name:%s Age:%d\n", person->name, person->age);
}
int comparePerson(void* data1, void* data2)
{
	person* p1 = data1;
	person* p2 = data2;
	return strcmp(p1->name, p2->name) == 0 && p1->age == p2->age;
}


void test01()
{
	dynamicArray* arr = init_dynamicArray(5);
	person p1 = { "sun",18 };
	person p2 = { "yu",19 };
	person p3 = { "hang",17 };
	person p4 = { "li",20 };
	person p5 = { "hai",21 };

	printf("插入数据前:容量 = %d,大小 = %d\n", arr->m_Capacity, arr->m_Size);
	insert_dynamicArray(arr, &p1, 0);
	insert_dynamicArray(arr, &p2, 1);
	insert_dynamicArray(arr, &p3, -1);
	insert_dynamicArray(arr, &p4, 0);
	insert_dynamicArray(arr, &p5, 2);

	foreach_dynamicArray(arr, printPerson);
	printf("插入数据后:容量 = %d,大小 = %d\n", arr->m_Capacity, arr->m_Size);
	printf("-------------------------------\n");
	//按位置删除
	removeByPos_dynamicArray(arr,0);
	foreach_dynamicArray(arr, printPerson);
	printf("-------------------------------\n");
	//按值删除
	removeByVal_dynamicArray(arr, &p5, comparePerson);
	foreach_dynamicArray(arr, printPerson);
    printf("删除数据后:容量 = %d,大小 = %d\n", arr->m_Capacity, arr->m_Size);

	destory_dynamicArray(arr);
	arr = NULL;
}
插入数据前:容量 = 5,大小 = 0
Name:li Age:20
Name:sun Age:18
Name:hai Age:21
Name:yu Age:19
Name:hang Age:17
插入数据后:容量 = 5,大小 = 5
-------------------------------
Name:sun Age:18
Name:hai Age:21
Name:yu Age:19
Name:hang Age:17
-------------------------------
Name:sun Age:18
Name:yu Age:19
Name:hang Age:17
删除数据后:容量 = 5,大小 = 3

链表

目的:

设计一个可以存不同类型数据的链表。

首先来看一个存储void*类型数据的普通链表。

 其中的一个节点只是一个独立的个体,无法代表整个链表。

链表的结构体:

整个链表用一个新的结构体来维护,如下:

struct LList
{
    struct LinkNode pHeader;  //链表的头结点,拿到链表的头结点即可还原整个链表
    int m_Size;               //记录链表的节点个数
}

在动态数组的实现中,我们可以随时访问动态数组的容量arr->m_Capacity,但由于整个数组的结构体暴露给了用户,导致用户也可以随意修改。

链表的实现中,我们就不直接让用户拿到链表的结构体 LList,防止其被篡改。进行如下操作:

typedef void* LinkList;  //给 void* 起别名

则用户拿到的数据永远都是 void* 类型的,我们在操作的时候,再把void*转为struct LList类型。

链表的实现:

//链表的节点结构体
typedef struct LinkNode
{
	void* data;
	struct LinkNode* next;
}LinkNode;

//链表结构体
typedef struct LList
{
	struct LinkNode pHeader;
	int m_Size;
}Llist;

//void别名
typedef void* LinkList;

初始化链表

//初始化链表
LinkList init_LinkList()
{
	//分配内存
	Llist* mylist = malloc(sizeof(Llist));
	if (mylist == NULL)
		return;
	mylist->pHeader.data = NULL;
	mylist->pHeader.next = NULL;
	mylist->m_Size = 0;

	return mylist;  //返回的是void*类型
}

插入节点

//插入节点
void insert_LinkList(LinkList list, int pos, void* data)
{
	if (list == NULL)
		return;
	if (data == NULL)
		return;
	Llist* mylist = list;
	if (pos<0 || pos>mylist->m_Size)
	{
		//如果传进来的位置是无效位置
		pos = mylist->m_Size;
	}
	//创建临时节点
	LinkNode* pCurrent = &mylist->pHeader; //pHeader直接是一个节点,所以用&取到其地址
	for (int i = 0; i < pos; i++)
	{
		pCurrent = pCurrent->next;
	}
	//此时pCurrent就是插入位置的前驱节点
	//创建新节点
	LinkNode* newNode = malloc(sizeof(LinkNode));
	newNode->data = data;
	newNode->next = NULL;
	//建立节点之间的关系
	newNode->next = pCurrent->next;
	pCurrent->next = newNode;
	//更新链表长度
	mylist->m_Size++;
}

遍历链表

//遍历链表
void foreach_LinkList(LinkList list,void(*myPrint)(void*))
{
	if (list == NULL)
		return;
	//还原链表真实结构体
	Llist* mylist = list;
	LinkNode* pCurrent = mylist->pHeader.next;
	for (int i = 0; i < mylist->m_Size; i++)
	{
		myPrint(pCurrent->data); //我们不知道data的类型,所以这里使用用户自己的回调来打印
		pCurrent = pCurrent->next;
	}
}

删除节点

//删除链表节点之按位置删除
void removeByPos_LinkList(LinkList list, int pos)
{
	if (list == NULL)
		return;
	Llist* mylist = list;
	if (pos<0 || pos>mylist->m_Size - 1)
		return; //无效位置直接返回
	//找到待删除位置的前驱节点的位置
	LinkNode* pCurrent = &mylist->pHeader;
	for (int i = 0; i < pos; i++)
	{
		pCurrent = pCurrent->next;
	}
	//PCurrent就是待删除节点的前驱节点
	//利用一个指针记录待删除节点
	LinkNode* pDel = pCurrent->next;
	//更改指针的指向
	pCurrent->next = pDel->next;
	//释放待删除节点
	free(pDel);
	pDel = NULL;
	mylist->m_Size--;
}
//删除链表节点之按值删除
void removeByVal_LinkList(LinkList list, void* data, int(*myCompare)(void*))
{
	if (list == NULL)
		return;
	if (data == NULL)
		return;
	//将list还原为真实链表结构体
	Llist* mylist = list;
	//创建两个辅助指针变量
	LinkNode* pPrev = &mylist->pHeader;
	LinkNode* pCurrent = mylist->pHeader.next;
	//遍历链表,找用户要删的数据
	for (int i = 0; i < mylist->m_Size; i++)
	{
		if (myCompare(data, pCurrent->data))
		{
			//开始删除,更改指针指向
			pPrev->next = pCurrent->next;
			free(pCurrent);
			pCurrent = NULL;
			mylist->m_Size--;
			break;
		}
		//辅助指针向后移动
		pPrev = pCurrent;
		pCurrent = pCurrent->next;
	}
}

清空链表、销毁链表

//请空链表
void clear_LinkList(LinkList list)
{
	if (list == NULL)
		return;
	Llist* mylist = list;
	LinkNode* pCurrent = mylist->pHeader.next;
	for (int i = 0; i < mylist->m_Size; i++)
	{
		//记录下一个节点的位置
		LinkNode* pNext = pCurrent->next;

		free(pCurrent);
		pCurrent = pNext;
	}
	mylist->pHeader.next = NULL;
	mylist->m_Size = 0;
}

//销毁链表
void destory_LinkList(LinkList list)
{
	if (list == NULL)
		return;
	//先清空链表再销毁头结点
	clear_LinkList(list);

	free(list);
	list = NULL;
}

用户回调函数

另外涉及到:打印用户数据,以及比较用户数据。由于我们不知道用户的数据类型,所以需要用户自己提供回调函数。

//回调函数之打印
void printPerson(void* data)
{
	Person* person = data;
	printf("Name:%s,Age:%d\n", person->name, person->age);
}
//回调函数之比较
int ComparePerson(void* data1, void* data2)
{
	Person* p1 = data1;
	Person* p2 = data2;
	return strcmp(p1->name, p2->name) == 0 && p1->age == p2->age;
}

给用户提供接口获取链表长度

//给用户提供接口获取链表长度
int size_LinkList(LinkList list)
{
	if (list == NULL)
		return -1;
	Llist* mylist = list;
	return mylist->m_Size;
}

用户test

void test02()
{
	//初始化链表
	LinkList mylist = init_LinkList(); //mylist本质是一个void*类型的,用来隐藏结构体里的数据属性
	//mylist->m_size = -1;  //err,用户访问不到真实链表中的属性

	Person p1 = { "sun",18 };
	Person p2 = { "yu",19 };
	Person p3 = { "hang",17 };
	Person p4 = { "li",20 };
	Person p5 = { "hai",21 };

	insert_LinkList(mylist, 0, &p1);
	insert_LinkList(mylist, 1, &p2);
	insert_LinkList(mylist, -1, &p3);
	insert_LinkList(mylist, 0, &p4);
	insert_LinkList(mylist, 2, &p5);
	//遍历链表
	foreach_LinkList(mylist, printPerson);
	printf("--------------------\n");
	removeByPos_LinkList(mylist,1);
	foreach_LinkList(mylist, printPerson);
	printf("--------------------\n");
	removeByVal_LinkList(mylist,&p4,ComparePerson);
	foreach_LinkList(mylist, printPerson);
	printf("链表长度为:%d\n", size_LinkList(mylist));
	printf("--------------------\n");
	//清空链表
	clear_LinkList(mylist);
	printf("清空链表后链表长度为:%d\n", size_LinkList(mylist));
	//销毁链表
	destory_LinkList(mylist);
	mylist = NULL;
}
Name:li,Age:20
Name:sun,Age:18
Name:hai,Age:21
Name:yu,Age:19
Name:hang,Age:17
--------------------
Name:li,Age:20
Name:hai,Age:21
Name:yu,Age:19
Name:hang,Age:17
--------------------
Name:hai,Age:21
Name:yu,Age:19
Name:hang,Age:17
链表长度为:3
--------------------
清空链表后链表长度为:0

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C语言 -- 动态数组&链表 的相关文章

  • vue拖拽实现

    拖拽介绍 目标是将左侧list中的item拖入右侧card中 如下所示 将list1和list3拖入右侧拖拽区 一 拖拽样式实现 使用vue vuetifyjs实现 页面布局可根据不同的UI库自行修改 html内容
  • C++消消乐

    键盘版 include
  • 5、JSON.parse()

    JSON parse JSON 通常用于与服务端交换数据 在接收服务器数据时一般是字符串 我们可以使用 JSON parse 方法将数据转换为 JavaScript 对象 语法 JSON parse text reviver 参数说明 te
  • 虚幻引擎程序化资源生成框架PCG 之Extents Modifier 和 Bounds Modifier

    Extents Modifier 和 Bounds Modifier这两个节点看起来很像 都是修改Point的Bouding Box 查看一下源代码 简单看一下它们的区别 文章目录 两个节点的代码对比 Bounds Modifier 源代码
  • MySQL使用ReplicationConnection导致的连接失效分析与解决

    MySQL数据库读写分离 是提高服务质量的常用手段之一 而对于技术方案 有很多成熟开源框架或方案 例如 sharding jdbc spring中的AbstractRoutingDatasource MySQL Router等 而mysql

随机推荐

  • 基于vue-cli、elementUI的Vue超简单入门小例子

    这个例子还是比较简单的 独立完成后 能大概知道vue是干嘛的 可以写个todoList的小例子 开始写例子之前 先对环境的部署做点简单的介绍 其实和Vue官方的差不多 如若没有安装过vue cli 先全局安装一下vue cli cnpm i
  • minikube 快速使用入门 - 安装 - 1

    minikube的官网地址 Welcome minikube k8s io minikube是什么 Minikube是一个单机版的kubernetes集群 可以在windows mac linux 快速的创建一个kubernetes集群 它
  • 原来react的createContext 这么简单

    今天看了下react中createContext相关的源码 特意在这里拿出来分享下 同时也会体现出关于本人看源码的技巧 本文采用源码分析以及源码断点调试的方式进行列举 用法 import React from react const Cou
  • (转)解决windows10下无法安装.net framework 3.5,错误代码0x800F081F

    1 下载 NET Framework 3 5的安装包netfx3 cab 将下载的文件复制到复制到 C 盘的 Windows 文件夹 后请在 命令提示符 管理员 中执行下面的命令 dism online Enable Feature Fea
  • centos下离线安装PostgreSQL

    安装简述 1 配置系统环境 2 安装postgreSQL 3 创建用户和分配权限 4 设置远程连接 配置系统环境 前提条件 文件postgresql 12 2 tar gz 放在 opt路径 步骤1 解压文件 cd opt tar zxvf
  • 编译错误记录

    一 MDK编译错误 1 error 235 variable epos msg was declared with a never completed type 这个错误的意思是epos msg这个变量被一个 没有被完成的的类型 定义 原因
  • Android串口通讯SerialPort(使用篇)

    1 什么是串口 在不会使用串口通讯之前 暂且可以把它理解为 一个可通讯的口 使用篇不深入探讨理论及原理 能理解串口如何使用之后 可以查看Android串口通讯SerialPort 浅谈原理 2 添加依赖 1 在 module 中的 buil
  • 数据库学习笔记之数据查询(一)

    数据库学习笔记之数据查询 一 查询之前先添加几条数据叭 还是基于这个里面建的那三个表 Student Course SC 进行插入查询操作 数据都是书上的 为Student表添加数据 INSERT INTO Student VALUES 1
  • pip install requests 报错 Could not fetch URL https://pypi.python.org/simple/requests/: There was ..r

    如题 pip install requests 报错 Could not fetch URL https pypi python org simple requests There was a problem confirming the
  • pandas读写mysql、h2和oracle数据库

    pandas读写mysql h2和oracle数据库 一 mysql数据库 二 h2数据库 三 oracle数据库 前言 在机器学习过程中 除开自己导入数据 用pandas的read xx之外 很多时候同样需要从数据库导入数据 特别是在做工
  • 夜莺(Flashcat)V6监控(五):夜莺监控k8s组件(下)---使用kube-state-metrics监控K8s对象

    目录 一 前言 二 categraf作为Daemonset的方式去运行监控k8s组件 1 1 24版本以下的k8s集群部署方法 创建autu yaml绑定权限 Daemonset部署categraf采集监控kubelet kube prox
  • 在jsp页面获取url请求参数

    JSP页面 When using the JSTL s expression language the request parameters are made available in the implicit object param T
  • css flex布局 —— 项目属性 flex-grow

    flex grow 属性定义项目的放大比例 解决的问题是 在空间有多余时把多余空间分配给各个子元素 flex grow 的值默认为 0 也就是说 如果存在剩余空间 也不放大 flex grow 取值为非负数 如果取值为负数那么和0的取值效果
  • mybatis拦截器

    最近在用mybatis做项目 需要用到mybatis的拦截器功能 就顺便把mybatis的拦截器源码大致的看了一遍 为了温故而知新 在此就按照自己的理解由浅入深的理解一下它的设计 和大家分享一下 不足和谬误之处欢迎交流 直接入正题 首先 先
  • 踩坑将一个AndroidStudio项目变成一个module引入到自己的项目中

    最近工作中遇到了需要将一个完整的androidstudio项目移植到自己项目中去 因为考虑到自己已经有的项目和需要引入的项目资源都很庞大 为了方便代码管理 决定将需要引入的项目作为一个module导入到自己现有项目中来 操作步骤 1 在主项
  • Cookie 与 Session 的作用及区别

    Cookie Cookie 其实就是客户端储存的 什么是客户端 就是浏览器存储 能看的见的 在浏览器设置 历史纪录中能看见 能手动清除Cookie 所以它一般都会被用在不重要的地方 因为它很容易被发现 cookie以明文储存信息 而且储存量
  • unity中lightProbe的使用

    之前曾经介绍过Unity3D的LightMapping烘焙的用法 单独使用的LightMapping效果很好 但由于只是把光影烘焙到贴图上面 所以并不会对周围的动态物体产生真正的光照效果 这次来介绍一下LightProbe 这是对Light
  • 服务器小程序空间,服务器空间-小程序-建站-企业邮箱

    Your twitter username username wange1228 Prefix some text you want displayed before your latest tweet HTML is OK but be
  • 华为OD机试 - 单词倒序(Java)

    题目描述 输入单行英文句子 里面包含英文字母 空格以及 三种标点符号 请将句子内每个单词进行倒序 并输出倒序后的语句 输入描述 输入字符串S S的长度 1 N 100 输出描述 输出倒序后的字符串 备注 标点符号左右的空格 0 单词间空格
  • C语言 -- 动态数组&链表

    目录 动态数组 动态数组的实现 用户test 链表 目的 链表的结构体 链表的实现 初始化链表 插入节点 遍历链表 删除节点 清空链表 销毁链表 用户回调函数 给用户提供接口获取链表长度 用户test 动态数组 将数组开辟到堆区 实现动态扩