二级指针骚操作实现链表虚拟头节点

news2024/12/28 11:15:16

重点是不用像其他文章里那样,用一个普通节点成员变量当头节点,节省一点空间占用,反正我觉得有点骚。就不详细交代技术背景了,简而言之,就是链表中第一个节点前没有节点了,只有一个指向它的指针,所以不能像其他节点一样对第一个节点进行删除操作,代码中必须判断这个特例,详细的参考链表头指针和虚拟头节点小结。可以用一个普通节点当作头节点来指向第一个节点,从而让第一个节点也有前一个节点,统一操作,如下图:

在这里插入图片描述

但是头节点里会有一个没用的数据域,浪费空间,所以才想到用二级指针。

原理

首先是单向链表的节点结构体:

struct LinkNode {
	LinkNode* next;
	int data;
};

重点就是让next 指针作为结构体第一个成员。然后考虑一个指向节点的指针:

LinkNode *head;

// 由于next 指针在结构体头部,所以:
head == &(head->next)  // => true

//即,指向节点的指针和指向该节点next 指针的指针相同,是指向同一个地址,或者说
LinkNode **ptr_to_next = &(head->next)
head == ptr_to_next // => true

指向节点的指针可以视为指向next 指针的指针,也就是指向节点的二极指针。那么反过来,指向节点的二级指针就可以当作一级指针来用:

// 创建一个节点,当作链表中的第一个节点
LinkNode node;

// 指向该节点的头指针是一级指针
LinkNode *head = &node;

// 指向头指针的指针是二级指针
LinkNode **ptr_to_head = &head;

// 如果把二级指针强制转换成一级指针
LinkNode *psudo_node_ptr = (LinkNode*)(ptr_to_head);

// 就可以对其使用成员操作符,得到指向第一个节点的指针
psudo_node_ptr->next == &node

// 实际等效于对二级指针解引用,结果就是头指针,或者指向第一节点的指针
psudo_node_ptr->next == *psudo_node_ptr->next == *ptr_to_head == head

可能还是画个图更好理解。首先是之前的虚拟头节点:

在这里插入图片描述

用了一个没用的普通节点,其中的next 指针指向第一个节点。如果把next 指针放在结构体头部,再有一个指向头节点的指针:

在这里插入图片描述
此时可以用ptr_to_head 访问第一节点,方法是先访问头节点所在的内存,再用头节点里的next 指针访问第一节点。既然头节点的用处只是那个存储了第一节点地址的next 指针,那么如果只要next 指针,不要头节点的其他部分:

在这里插入图片描述
此时头节点就变成了指向第一节点的指针,是个一级指针,相应的,ptr_to_head 就变成指向节点的二级指针了。如果把ptr_to_head 强制转换成节点的一级指针:

在这里插入图片描述
那么编译器就会当它确实指向一个节点,即原来的头节点。头节点其他部分没了没关系,只要next 指针还在,就可以像头节点还在时一样,通过next 访问第一节点。

我觉得这样表达应该足够清楚了。顺便简单写一下链表类的实现:

class LinkedList {
	private:
	LinkNode* head;   // 把头节点退化成头指针
	
	public:
	LinkNode* ptr_to_head() {
		return (LinkNode*)(&head);   // 把二级指针强制转换成一级指针,转换结果只能使用->next 访问next 成员
		                             // 访问其他成员会BUG,因为不存在那些东西
	}
	
	// 传入要删除的目标节点和前一个节点
	// 如果要删除第一节点,前一个节点就是ptr_to_head()
	void remove_node(LinkNode *target, LinkNode *prev) {
		prev->next = target->next;    
		// 不用为第一节点特殊处理,若prev 是ptr_to_head(),
		// prev->next 就等效于 *ptr_to_head,解引用得到head,对head 赋值。
	}
};

对比用一个普通节点当头节点的情况:

class LinkedList {
	private:
	LinkNode head;   // 用完整的普通节点作为头节点
	
	public:
	LinkNode* ptr_to_head() {
		return &head;   // 这次是真的指向节点的一级指针,可以访问其他成员,但是没意义
	}
	
	// 传入要删除的目标节点和前一个节点
	// 如果要删除第一节点,前一个节点就是ptr_to_head()
	void remove_node(LinkNode *target, LinkNode *prev) {
		prev->next = target->next;    
		// 使用上和二级指针没区别
	}
};

可见,两种实现在使用上没区别,只是要牢记,二级指针的方案中,ptr_to_head 并不指向一个完整的节点,不能访问节点中next 以外的成员。如果节点是动态在堆上创建的,不能用ptr_to_head 删除内存。

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

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

相关文章

强化学习基础篇[3]:DQN、Actor-Critic详解

【强化学习原理+项目专栏】必看系列:单智能体、多智能体算法原理+项目实战、相关技巧(调参、画图等、趣味项目实现、学术应用项目实现 专栏详细介绍:【强化学习原理+项目专栏】必看系列:单智能体、多智能体算法原理+项目实战、相关技巧(调参、画图等、趣味项目实现、学术应…

从实习到秋招成为一名安全工程师,我是怎么做的

前言 借朋友口述总结了安全招聘面试经历分享,希望更多的人看到这篇文,从中得到启发,找到自己心仪的工作。 基本情况 签了字节的三方,秋招终于告一段落。从八月份边实习边准备秋招到现在,经历了许多,这篇帖…

Linux :: 【简单开发篇 :: vim 编辑器:(1)】:: vim 编辑器的基本认识与三种 vim 常用模式 | 使用:打开编辑、退出保存关闭vim

前言:本篇是 Linux 基本操作篇章的内容! 笔者使用的环境是基于腾讯云服务器:CentOS 7.6 64bit。 学习集: C 入门到入土!!!学习合集Linux 从命令到网络再到内核!学习合集 目录索引&am…

yolov8Pose实战

目录 前言一、yolov8环境搭建二、测试训练模型,评估模型,并导出模型实测检测效果 测试人体姿态估计 前言 YOLO系列层出不穷,从yolov5到现在的yolov8仅仅不到一年的时间。追踪新技术,了解前沿算法,一起来测试下yolov8在…

【密码学复习】第十章 身份鉴别

身份鉴别的定义 定义:身份鉴别,又称为身份识别、身份认证。它是证实客户的真实身份与其所声称的身份是否相符的过程。 口令身份鉴别 固定口令(四) 注册环节:双因子认证 ① 接收用户提供的口令pw(PIN&…

车辆救援道路救援预约汽修托运小程序

道路救援:指汽车道路紧急救援,为故障车主提供包括诸如:拖吊、换水、充电、换胎、送油以及现场小修等服务(Road-Side Service); 同时也指交通事故道路救援,包括伤员救治、道路疏导等。 随着我国巨大的汽车拥有量&…

1计算机系统概述_1.2计算机系统层次结构

1.2 计算机系统层次结构 计算机系统(CO 自命名) 1、CO的组成 硬件系统和软件系统共同构成了一个完整的计算机系统 ——硬件:有形的物理设备,是CO中实际物理装置的总称 ——软件:在硬件上运行的程序和相关的数据及文…

SpringCloud:分布式缓存之Redis哨兵

Redis提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复。 1.哨兵原理 1.1.集群结构和作用 哨兵的结构如图: 哨兵的作用如下: 监控:Sentinel会不断检查您的master和slave是否按预期工作自动故障恢复&#xff…

人工智能(pytorch)搭建模型9-pytorch搭建一个ELMo模型,实现训练过程

大家好,我是微学AI,今天给大家介绍一下人工智能(pytorch)搭建模型9-pytorch搭建一个ELMo模型,实现训练过程,本文将介绍如何使用PyTorch搭建ELMo模型,包括ELMo模型的原理、数据样例、模型训练、损失值和准确率的打印以及…

labelimg闪退解决方法(之前使用过labelimg,但新一次使用,打开文件夹无反应,再次打开闪退的问题)及标注经验

问题描述: 之前使用过labelimg进行好多次的标注,但新一次运行使用,发现打开目录无反应,再次打开闪退的问题,重启电脑并且从新运行labelimg仍然无效。 解决方法: 关闭labelimg,然后删除文件C…

一文纵览Umi‘s Friends生态,GameFi浪潮的变革者

以“P2E”为特性的 GameFi,代表着游戏时代的新盈利模式,它将 NFT 或其他形式的代币化资产作为游戏内容,游戏内资产的寿命会,则随着这些资产继续存在于玩家的钱包中而延长(即便游戏关闭),资产的互…

class文件中,常量池、方法表、属性表,异常表等等相关数据解析!小白就跟我一起对照学【class字节码文件分析】

前言:前段时间读《深入java虚拟机》介绍到class文件的时候,由于理论知识较多,人总感觉疲惫不堪,就泛泛阅读了一下。在工作中使用起来知识点知道,但是总是需要查阅各种资料。今天有时间,继续整理常量池后面的…

session与cookie

session是一种会话机制。当客户端发送登录请求时,服务端会生成一个sessionId存储在cookie中返回给客户端,客户端通过响应数据中的set-cookie字段来获取cookie并保存。如果客户端再向同一网站发送请求时,会自动携带cookie,相当于一…

离散数学_十章-图 ( 5 ):连通性 - 下

📷10.5 图的连通性 4. 有向图的连通性4.1 强连通4.2 弱连通4.3 (有向图的)强连通分支 5. 通路与同构6. 顶点间通路个数的计算 4. 有向图的连通性 根据是否考虑边的方向,在有向图中有两种连通性概念: 4.1 强连通 强连…

C/C++线程绑核详解

在一些大型的工程或者特殊场景中,我们会听到绑核,绑核分为进程绑核和线程绑核。绑核的最终目的都是为了提高程序和性能或者可靠性。 一:为什么需要绑核 操作系统发展至今,已经能很好的平衡运行在操作系统上层的应用,兼…

16.3:岛屿数量问题2

岛屿数量问题2 https://leetcode.cn/problems/number-of-islands-ii/ 给你一个大小为 m x n 的二进制网格 grid 。网格表示一个地图,其中,0 表示水,1 表示陆地。最初,grid 中的所有单元格都是水单元格(即&#xff0c…

Dubbo源码解析一网络通信原理

Dubbo 网络通信原理 1. Dubbo高可用集群1.1 服务集群的概述1.1.1 服务集群的概述1.1.2 调用过程1.1.3 组件介绍 1.2 集群容错机制1.2.1 内置集群容错策略1.2.1.1 Failover(失败自动切换)1.2.1.2 Failsafe(失败安全)1.2.1.3 Failfast(快速失败)1.2.1.4 Failback(失败自动恢复)1.…

卡尔曼滤波 | Matlab实现利用卡尔曼滤波器估计电池充电状态(Kalman Filtering)

文章目录 效果一览文章概述研究内容程序设计参考资料效果一览 文章概述 卡尔曼滤波 | Matlab实现利用卡尔曼滤波器估计电池充电状态(Kalman Filtering) 研究内容

gyp verb `which` failed Error: not found: python2

安装node-sass居然需要python2,7环境,不能python3 我只能重新降版本: python2.7:https://www.python.org/ftp/python/2.7/python-2.7.amd64.msi npm ERR! code 1 npm ERR! path F:\idea2021work\music01 初始化\music-client\node_modules\node-sass np…

自然语言处理从入门到应用——自然语言处理的基础任务:词性标注(POS Tagging)和句法分析(Syntactic Parsing)

分类目录:《自然语言处理从入门到应用》总目录 词性标注 词性是词语在句子中扮演的语法角色,也被称为词类(Part-Of-Speech,POS)。例如,表示抽象或具体事物名字(如“计算机”)的词被…