内核链表的实现

news2024/9/29 3:27:23

内核链表和普通链表的区别:

1. 普通链表当中数据域和指针域,没有做到区分,数据与指针形成了一个整体,而内核链表数据与指针是完全剥离的没有直接的关系。
2. 在普通链表当中所有节点的数据都是一样的类型,而内核链表中每一个节点的类型都可以是不同的。
3. 普通的链表每一个节点都只能出现在一个链表当中,因为它只有一对前驱 / 后继指针,而内核链表可以有多对前驱/ 后继指针。
代码部分将会与普通链表结合起来一起理解。

1.内核链表的结构

从结构上我们就知道为什么内核链表的数据可以不受限制,我们将节点的指针和数据分离,就可以在大的结构体里存放不同的数据,而普通链表指针数据是在一块的无法改变数据类型。

指针是独立在一个结构体里的,因此他可以有多个头多个尾,因此他可以存在于多个链表中

2.内核链表代码实现

在实现过程中思路相同的我就不多讲了,完成内核链表经典的几个操作,由于带哨兵位的链表使我们更容易操作,因此我们书写的是带哨兵位的双向循环内核链表。

内核链表,顾名思义我们针对链表的操作都是针对核也就是指针域的那个结构体。

2.1内核链表创建

通过结构我们知道内核链表用大结构体包含了一个存储指针的结构体,要注意的是大结构体里的指针域结构体是一个普通变量,不可以是指针类型!因此他的结构如下:

typedef int DataTay;


//链表的指针域//核心
struct HeadAndTail
{
	struct HeadAndTail* next;
	struct HeadAndTail* prev;
};

//链表节点
typedef struct Node
{
	DataTay data;
	struct HeadAndTail ptr;//这里只能是普通变量不可以是指针,如果是指针那结构上有什么变化?
}Node, * P_Node;

2.2内核链表初始化

初始化我们做了以下步骤:

我们先要在堆开辟一个节点的空间,存入我们想要存储的数据,让节点内核指针prev指向内核地址,next也指向内核地址。

void ListHeadInit(struct HeadAndTail* NewSpace)
{
	NewSpace->next = NewSpace;
	NewSpace->prev = NewSpace;
}

P_Node GetNodeSpace(DataTay Data)
{
	P_Node NewSpace = (P_Node)calloc(1, sizeof(Node));
	printf("成功获取新的节点\n");
	NewSpace->data = Data;
	printf("成功获取小结构体节点地址:%x\n", &NewSpace->ptr);
	return NewSpace;
}

//创建一个节点并初始化,返回该节点的地址。
P_Node ListNodeInit(DataTay Data)
{
	P_Node NewNodeAddr = GetNodeSpace(Data);
	ListHeadInit(&NewNodeAddr->ptr);//指向的是核的地址
	return NewNodeAddr;
}

2.3头插

我们保持一个理念,一切的指向操作都是针对这个核,我们在书写过程中基本是不会出错的!

头插思路:用头结点获取到头结点核地址和头结点下一个节点的核地址,我们新创建的节点就可以前指向头节点的核地址,尾指向头结点下一个节点的核地址,再让头结点next指向新的核地址,让头结点下个节点的核的prev指向新的节点核地址,接完成了头插,尾插思路是相同的我就不在多说了。

//头插函数
void DLinkListPushFront(P_Node StLinkListHead, DataTay x)
{
	struct HeadAndTail* Head = &StLinkListHead->ptr;
	struct HeadAndTail* HeadNext= StLinkListHead->ptr.next;
	struct HeadAndTail* NewHead = &(GetNodeSpace(x)->ptr);
	NewHead->next = HeadNext;//新的节点下一个指向头结点
	NewHead->prev = Head;//新节点的前一个节点是尾节点
	Head->next = NewHead;//找到尾节点,头结点的前一个节点就是尾节点,尾节点的下一个节点就是新节点
	HeadNext->prev = NewHead;//头节点的前一个指向新节点
}

2.4头删

头删的思路就是要删除的节点(释放节点空间),再将断开处链接起来,就OK。

//头删
int  DelListHead(P_Node StLinkListHead)
{
	struct HeadAndTail* Head = &StLinkListHead->ptr;
	struct HeadAndTail* HeadNext_Next = StLinkListHead->ptr.next->next;

	if (StLinkListHead->ptr.next == &StLinkListHead->ptr)
	{
		printf("链表为空");
		return -1;
	}
	else
	{
		P_Node DelNodeAddr = GetP_NodeAddrforHeadNodeAddr(Head->next);
		Head->next = HeadNext_Next;//找到尾节点,头结点的前一个节点就是尾节点,尾节点的下一个节点就是新节点
		HeadNext_Next->prev = Head;//头节点的前一个指向新节点
		free(DelNodeAddr);
		return 0;
	}
}

2.5查找链表中数据的地址

这个非常重要,这是我们根据内核地址获取到大结构体地址从而根据大结构体遍历数据的关键!

这里我就啰嗦的补充一下C语言相关知识点!

2.5.1指针类型强制转换

我们定义了一个int类型的指针,这个指针变量减一的含义是什么?地址向下偏移4个字节。

那char类型的指针呢?不再多说。

2.5.2结构体占用内存的大小计算(了解)

对齐数:结构体成员大小与系统默认对齐数比较,小的为最终对齐数

第一个成员变量从结构体入口地址开始存储

之后所有的成员变量都要以入口地址为基准,在对齐数的整数倍的偏移地址处开始存储

结构体的总大小为系统默认对齐数的整数倍

2.5.3根据核地址计算节点入口地址

我们将0强制转换成节点类型的指针,再获取到内核地址,这样我们就直接得到了内核地址到节点入口地址的偏移量。我们再用知道的小结构体地址减去偏移量就得到了大结构体地址!

注意:这个公式每个强制类型转换都是有意义的,缺一不可。

//根据小结构体地址计算大结构体的地址
P_Node GetP_NodeAddrforHeadNodeAddr(struct HeadAndTail* SmallStrAddr)
{
	//获取偏移量  	//再根据偏移量得到大结构体地址
	return (P_Node)((char*)SmallStrAddr - (unsigned long)(&((P_Node)0)->ptr));
}

2.6遍历链表

//打印链表的所有值
int PrintfDlist(P_Node HeadNodeAddr)
{
	struct HeadAndTail* Newhead = &(HeadNodeAddr->ptr);
	struct HeadAndTail* NewheadNext = HeadNodeAddr->ptr.next;
	P_Node NewNode;
	int i = 0;
	if (HeadNodeAddr->ptr.next == &HeadNodeAddr->ptr)
	{
		printf("链表为空");
		return -1;
	}
	else
	{
		for (; NewheadNext != Newhead; NewheadNext = NewheadNext->next)
		{
			NewNode = GetP_NodeAddrforHeadNodeAddr(NewheadNext);
			printf("第%d个节点的值为:%d\n", i,NewNode->data);
			i++;
		}
		return 0;
	}

}

3.总结

内核链表最强的的地方在于,它可以存储任意类型的数据,我们只需要在节点里加上自己设定变量类型的标识,在遍历时我们获取节点的数据类型就可以进行打印

并且内核指针可以有多对,这样这个节点可以存在于多个链表中。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2052326.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vmware安装openEuler操作系统

文章目录 vmware安装openEuler操作系统官网地址下载方式 新建虚拟机新建虚拟机新建虚拟机向导输入新建的虚拟机名称预览安装虚拟机的操作系统开始安装语言选项基础配置 网络配置关机拍快照克隆模板机 vmware安装openEuler操作系统 官网地址 https://www.openeuler.org/zh/dow…

6.4 MySQL 常见查询优化案例分析

欢迎来到我的博客,很高兴能够在这里和您见面!欢迎订阅相关专栏: 工💗重💗hao💗:野老杂谈 ⭐️ 全网最全IT互联网公司面试宝典:收集整理全网各大IT互联网公司技术、项目、HR面试真题.…

Python 函数参数介绍

目录 函数 -- 普通参数 函数 -- 参数进一步 -- 指定参数 函数 -- 参数带 * -- 将传入的参数打包成元组 函数 -- 参数带 ** -- 关键字传参 -- 将传入的参数打包成字典 综合使用 函数 -- 普通参数 函数 -- 参数进一步 -- 指定参数 函数调用时,未指定的参数需要写…

风清扬/基于Java语言的光伏监控系统+光伏发电预测+光伏项目+光伏运维+光伏储能项目

基于Java语言的光伏监控系统光伏发电预测光伏项目光伏运维光伏储能项目 介绍 基于Java语言的光伏监控系统光伏发电系统光伏软件系统光伏监控系统源码光伏发电系统源码 基于Java语言的光伏监控系统光伏发电预测光伏项目光伏运维光伏储能项目 安装教程 参与贡献 Fork 本仓库新…

十分钟搭建一个RTMP服务器

使用SRS搭建RTMP服务器 如果您需要搭建一个RTMP服务器,您可以使用SRS(Simple-RTMP-Server)来完成此任务。SRS是一个开源的RTMP服务器下面是一个简单的步骤指南: 获取srs srs官⽹:https://github.com/ossrs/srs 码云…

软考2024下半年软考报名时间安排及报名流程

简介 软件资格考试也称计算机软件水平考试 [1],是原中国计算机软件专业技术资格和水平考试的完善与发展。是对从事或准备从事计算机应用技术、网络、信息系统和信息服务等专业技术工作的人员水平和能力的测试。这项考试是由国家人力资源和社会保障部、工业和信息化…

【通信协议】I2C总线(一主多从)

目录 I2C简介 硬件电路 软件模拟初始化 基本单元 起始信号 停止信号 发送一个字节 接收一个字节 发送应答 接收应答 I2C基本单元代码 MyI2C.h MyI2C.c 完整数据帧 学习资料分享 本博客使用软件模拟的代码进行I2C总线​​​​​​(总线指多个设备共用…

【Python机器学习系列】使用Hyperopt搜索随机森林分类模型最优超参数(案例+源码)

这是我的第342篇原创文章。 一、引言 Hyperopt是一个强大的python库,用于超参数优化,由jamesbergstra开发。Hyperopt使用贝叶斯优化的形式进行参数调整,允许你为给定模型获得最佳参数。它可以在大范围内优化具有数百个参数的模型。 在本节中…

Vue3.0生命周期钩子(包含:Vue 2.0 和 Vue 3.0)

1、Vue 2.0 生命周期钩子 每个应用程序实例在创建时都有一系列的初始化步骤。例如,创建数据绑定、编译模板、将实例挂载到 DOM 并在数据变化时触发 DOM 更新、销毁实例等。在这个过程中会运行一些叫做生命周期钩子的函数,通过这些钩子函数可以定义业务逻…

【STM32+HAL】巡逻打靶小车

一、前言 作为电赛最爱出的小车和视觉题,将两者结合起来出题也是一个方向,故写下此文供学者参考,也作为备赛电赛的记录。 如有小伙伴想交流学习心得,欢迎加入群聊751950234,群内不定期更新代码,以及提供本…

elasticsearch的高亮查询三种模式查询及可能存在的问题

目录 高亮查询使用介绍 高亮参数 三种分析器 可能存在的查询问题 fvh查询时出现StringIndexOutOfBoundsException越界 检索高亮不正确 参考文档 高亮查询使用介绍 Elasticsearch 的高亮(highlight)可以从搜索结果中的一个或多个字段中获取突出显…

python:霍夫变换检测直线

霍夫变换检测直线 在Python中,可以使用OpenCV库来实现霍夫变换进行直线检测。 一、原理 1、霍夫变换(Hough Transform) 霍夫变换是图像处理中从图像中识别几何形状的基本方法之一,应用很广泛,也有很多改进算法。主要用来从图像中分离出具有…

Linux中的锁

user2正在进行抢票: 4 user3正在进行抢票: 3 user1正在进行抢票: 2 user4正在进行抢票: 1 user2正在进行抢票: 0 user3正在进行抢票: -1 user1正在进行抢票: -2 int tickets10000; void* getTicket(void* args) {string usernamestatic_cast<const char*>(args);while(…

【C++篇】迈入新世界的大门——初识C++(上篇)

文章目录 C发展历史C起源C版本更新C23小故事 C在工作领域的应用C参考网站及文档书籍编程语言排行榜C难度参考文档书籍参考文档参考书籍 C第一个程序命名空间为什么要使用namespacenamespace定义及规则命名空间使用 C输入&输出名字含义 缺省参数函数重载 C发展历史 C起源 …

新手小白零基础,该怎样学习编程呢?零基础入门到精通,收藏这一篇就够了

零基础编程入门先学什么&#xff1f;编程语言有几百种&#xff0c;我们应该怎么选择。想学习编程&#xff0c;加入互联网行业&#xff0c;哪一个更有前途&#xff1f;在小白学习编程会有各种各样的问题&#xff0c;今天小编我就来为你解答。 一、怎么选择编程语言 编程语言有很…

geomagic怎么删除平面?geomagic怎么修模

在现代三维建模和3D打印技术的发展中&#xff0c;Geomagic作为一款专业的软件工具&#xff0c;广泛应用于逆向工程、产品设计和质量检测等领域。本文将详细介绍geomagic怎么删除平面&#xff1f;geomagic怎么修模&#xff0c;并探讨Geomagic的主要应用领域。通过这些内容&#…

SAP_ABAP模块-批量导入货源清单

一、业务背景 有个朋友做ECC 6.0的项目&#xff0c;期初上线时&#xff0c;有一个需求是批量导入货源清单&#xff0c;我问了好几个朋友&#xff0c;加上自己以前的积累&#xff0c;硬是没有找到一个完全能用的程序&#xff0c;下面我来说一下我遇到的问题&#xff1b; 对货源清…

【软件造价咨询】软件造价之全国各省市功能点单价分析

在软件工程领域&#xff0c;功能点是衡量软件规模的一种单位&#xff0c;功能点分析是一种广泛使用的方法&#xff0c;用于估算软件项目的规模和成本。其中功能点单价是指每功能点的软件开发费用&#xff08;单位&#xff1a;元/功能点&#xff09;。 本篇文章通过调研了20多份…

运维开发——局域网SSH访问服务器与应用

摘要 本博文主要介绍局域网SSH访问登陆虚拟机和及其应用相关配置操作。 1. 局域网SSH访问登陆虚拟机 目标&#xff1a;在局域网内A电脑使用SSH登陆B电脑上虚拟机的服务器。 前提条件:B电脑为宿主机&#xff0c;可以正常使用ssh访问虚拟机服务器&#xff0c;虚拟机网络连接方…

【面试题】文本左右对齐

文本左右对齐 学习 一、题目 这个问题是一个典型的文本排版问题。 二、解题思路 初始化&#xff1a;创建一个结果列表result来存储每一行的文本&#xff0c;以及一个临时列表current_line来存储当前正在构建的行的单词。 贪心算法填充&#xff1a;遍历words数组&#xff0c;…