使用C++实现尾插式循环链表结构

在编码中避免不了使用链表,特别是循环链表,很多同学使用时为了省事直接使用C++ STL库中的链表实现,这样当然很简单也不容易出错,但同时也不可避免的带来了一些问题:

  1. 是半个黑盒,虽然能看源码,但是经过层层封装的STL源码很少有人愿意去认真看
  2. 随着C++版本的修改,部分特性可能会改变,可能会引入一些不必要的问题

因此有必要实现一款属于自己的双向链表,这样在有需要的时候就能随时增加自己的特性,让链表更好的服务于其他模块。
在这里插入图片描述

在使用C++实现链表时,我们需要实现两个主要的类:1. node节点类 ,2. 链接node节点的类

一般为了实现node的后期扩展,会将node实现成抽象类,后期根据自己需要进行扩展

class Node {
public:~Node() = default;
};

然后在通过继承node类来实现自己链表中使用的node节点类

// 实现保存单个节点的链表
class NodeImpl : public Node {
public:explicit NodeImpl(uint64_t sequence_number): sequence_number_(sequence_number) {}uint64_t sequence_number() const { return sequence_number_; }private:// 节点管理类中需要直接使用成员变量,这里将其声明为友元类friend class TailList;// 双向链表要有指向前方和后方的指针NodeImpl* prev_{};NodeImpl* next_{};// 真正保存的数据const uint64_t sequence_number_;
};

首先,我们看到NodeImpl类继承自基类Node,它作为链表中的实际节点实现。每个NodeImpl对象都包含了一个表示序列号的uint64_t类型成员变量sequence_number_,以及两个指向前驱和后继节点的双向指针prev_next_。由于TailList类需要访问这些私有成员,因此NodeImplTailList声明为友元类以允许直接操作。

class TailList {
public:TailList() : head_(0) {// 将自己指向自己形成一个最小环head_.prev_ = &head_;head_.next_ = &head_;}// 如果自己指向自己说明是空的,没有任何节点数据bool empty() const { return head_.next_ == &head_; }NodeImpl* oldest() const {return head_.next_;}NodeImpl* newest() const {return head_.prev_;}// new 出一个node节点,并把对应的node放到链表结尾NodeImpl* New(uint64_t sequence_number) {auto* node = new NodeImpl(sequence_number);// 生成一个节点,并将节点插入环装链表的尾部node->next_ = &head_;node->prev_ = head_.prev_;node->prev_->next_ = node;node->next_->prev_ = node;return node;}// 清理链表中的节点static void Delete(const NodeImpl* node) {node->prev_->next_ = node->next_;node->next_->prev_ = node->prev_;delete node;}private:// 双向链表的原点,默认不存储任何数据NodeImpl head_;
};

接下来,TailList类负责维护整个循环链表的结构。初始化时,它创建一个头节点head_,该节点的前驱和后继均指向自身,形成一个空链表的“闭环”。通过empty()函数可以快速检查链表是否为空,只需看head_的下一个节点是否仍是指向自己即可。

链表提供了oldest()newest()接口,分别用于获取链表中最旧(第一个加入)和最新(最后一个加入)的节点。核心方法New(uint64_t sequence_number)用于创建新节点并将新节点插入到链表的尾部,即每次新增节点都会成为新的末尾节点。这个方法巧妙地利用双向链表的特点,通过更新新节点及其相邻节点的前后指针来完成插入操作。

同时,Delete(const NodeImpl* node)静态方法用来安全地从链表中移除指定节点,并释放其内存资源。此方法同样通过调整被删除节点前后节点之间的链接关系,确保链表的连续性不受影响。

int main(int argc, char* argv[]) {TailList  list;list.New(1);list.New(2);list.New(3);list.New(3);list.New(3);list.New(3);do {auto lpNode = list.newest();std::cout << lpNode->sequence_number() << std::endl;TailList::Delete(lpNode);} while (!list.empty());return 0;
}

最后,在main()函数中,我们展示了链表的实际应用。程序首先创建了一个TailList对象,并连续调用New()方法添加了几个具有不同序列号的节点。然后,通过一个do-while循环不断地找到链表中的最新节点,输出其序列号,并通过Delete()方法删除它,直到链表为空为止。

//
// Created by wangyz38535 on 2024/4/24.
//#include <iostream>// 尾插式循环链表
class TailList;class Node {
public:~Node() = default;
};// 实现保存单个节点的链表
class NodeImpl : public Node {
public:explicit NodeImpl(uint64_t sequence_number): sequence_number_(sequence_number) {}uint64_t sequence_number() const { return sequence_number_; }private:// 节点管理类中需要直接使用成员变量,这里将其声明为友元类friend class TailList;// 双向链表要有指向前方和后方的指针NodeImpl* prev_{};NodeImpl* next_{};// 真正保存的数据const uint64_t sequence_number_;
};class TailList {
public:TailList() : head_(0) {// 将自己指向自己形成一个最小环head_.prev_ = &head_;head_.next_ = &head_;}// 如果自己指向自己说明是空的,没有任何节点数据bool empty() const { return head_.next_ == &head_; }NodeImpl* oldest() const {return head_.next_;}NodeImpl* newest() const {return head_.prev_;}// new 出一个node节点,并把对应的node放到链表结尾NodeImpl* New(uint64_t sequence_number) {auto* node = new NodeImpl(sequence_number);// 生成一个节点,并将节点插入环装链表的尾部node->next_ = &head_;node->prev_ = head_.prev_;node->prev_->next_ = node;node->next_->prev_ = node;return node;}// 清理链表中的节点static void Delete(const NodeImpl* node) {node->prev_->next_ = node->next_;node->next_->prev_ = node->prev_;delete node;}private:// 双向链表的原点,默认不存储任何数据NodeImpl head_;
};int main(int argc, char* argv[]) {TailList  list;list.New(1);list.New(2);list.New(3);list.New(3);list.New(3);list.New(3);do {auto lpNode = list.newest();std::cout << lpNode->sequence_number() << std::endl;TailList::Delete(lpNode);} while (!list.empty());return 0;
}

总结起来,这段代码提供了一个简洁而实用的尾插式循环链表的实现,适用于需要高效进行顺序添加和按最近添加顺序删除元素的场景。通过合理的封装和设计,使得链表的操作既直观又易于维护。

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

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

相关文章

一个缓存泛型自动处理队列,留有处理事件接口

在C#中&#xff0c;创建一个通用泛型缓存队列工具通常涉及到使用泛型&#xff08;T&#xff09;来支持任意类型的队列项&#xff0c;并且实现先进先出&#xff08;FIFO&#xff09;的队列逻辑。为了提供自动处理和事件通知的功能&#xff0c;我们可以使用后台线程来处理队列中的…

【禅道客户案例】同方智慧能源数智化转型新实践 禅道助力前行

同方智慧能源是同方股份有限公司的骨干企业。依托中核集团、清华大学的科技优势&#xff0c;坚持技术和资源双核驱动&#xff0c;基于30多年行业积淀&#xff0c;面向建筑、交通、工业、北方供热、数据中心等主要用能场景提供设计咨询、产品技术、投资建设、运营服务&#xff0…

Redis集合 set 详解

Set简介 set 类似于 Java 中的 HashSet ,是redis中的一种数据结构&#xff0c;它是一个无序并且唯一的键值集合&#xff0c;并且储存时不会按照插入的先后顺序进行.一个集合中最多可以储存2^32 -1个元素。当你需要存储一个列表数据&#xff0c;又不希望出现重复数据时&#xf…

day26 java lambda

lambda lambda表达式 &#xff1a;对匿名内部类的对象的一种简写lambda表达式的格式 : (形参列表) -> {方法体}说明 &#xff1a;-> : lambda操作符 例:&#xff1a;Comparator 原代码&#xff1a; Collections.sort(list, new Comparator<Integer>() {Override…

DELL PowerEdge服务器通过iDRAC升级BIOS遇到的问题

本文对PowerEdge 12G系统&#xff0c;也就是iDRAC 7版本升级BIOS中遇到的几个问题做个总结&#xff0c;对于其他版本理论上应该也是适用的。如果还遇到其他问题&#xff0c;可以添加VX&#xff0c;VX号为 StorageExpert 进行进一步的分析探讨。 第一个问题&#xff0c;成功下载…

Android 11 裁剪系统显示区域(适配异形屏)

概述 在显示技术中&#xff0c;"OverScan"&#xff08;超扫描&#xff09;是一种调整显示图像边界的技术。通常情况下&#xff0c;OverScan 会在显示屏的边缘周围裁剪一小部分图像。这种裁剪是为了确保显示内容在屏幕上的完整可见性&#xff0c;尤其是在老式电视或投…

ZABAPGIT问题,导入github上的程序包时报 DBSQL_DUPLICATE_KEY_ERROR

跟踪程序发现在94050行 INSERT seocompotx FROM TABLE it_descriptions 报的错 刚开始&#xff0c;不想着改动他&#xff0c;把seocompotx 表的数据做下指定清楚&#xff0c;但是5次清楚后&#xff0c;果断注释掉 改成 MODIFY seocompotx FROM TABLE it_descriptions。 在用…

航片水体空洞修补

水体空洞情况如下图所示&#xff1a; 水体空洞修补结果如下图所示&#xff1a; 操作视频教程&#xff1a; MCM智拼图软件V8.5-漏洞空洞修补-水体修补_哔哩哔哩_bilibili

鸿蒙OpenHarmony【小型系统 编译】(基于Hi3516开发板)

编译 OpenHarmony支持hb和build.sh两种编译方式。此处介绍hb方式&#xff0c;build.sh脚本编译方式请参考[使用build.sh脚本编译源码]。 使用build.sh脚本编译源码 进入源码根目录&#xff0c;执行如下命令进行版本编译。 ./build.sh --product-name name --ccache 说明&…

基于LDLT分解求解Ax=b

前置知识&#xff0c;矩阵 A T ∗ A A^T * A AT∗A的性质&#xff1a; 实对称性&#xff1a;如果 A A A是实矩阵&#xff0c;那么 A T ∗ A A^T * A AT∗A是实对称矩阵。这意味着 ( A T ∗ A ) T A T ∗ ( A T ) T A T ∗ A (A^T * A)^T A^T * (A^T)^T A^T * A (AT∗A)…

基于数据挖掘的斗鱼直播数据可视化分析系统

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长 QQ 名片 :) 1. 项目简介 随着网络直播平台的兴起&#xff0c;斗鱼直播作为其中的佼佼者&#xff0c;吸引了大量用户和观众。为了更好地理解和分析斗鱼直播中的数据&#xff0c;本项目介绍了一个基于数据挖掘的斗鱼直播数据…

【AI导师写作】毕业论文答辩PPT生成

无论是大专、本科或者硕博&#xff0c;撰写毕业论文、开题报告、文献综述、任务书、课程论文、调研报告等都是必不可少的一件事。而这些任务重往往都需要我们花费大量的时间和精力&#xff0c;而“AI导师写作”在这一方面无疑提供了高效和便捷。可毕业季的论文答辩也是每个学者…

C#调用skiasharp实现绘制并拖拽图形

SkiaSharp是基于.net的跨平台二维图形库&#xff0c;封装的谷歌的Skia库&#xff0c;SkiaSharp支持在以下平台或运行时中使用&#xff0c;能够在图片中绘图&#xff0c;也提供控件在Winform、WPF等使用。本文学习skiasharp在Winform的基本用法&#xff0c;并参照参考文献5实现绘…

【Java那些年系列-启航篇 04】Java程序架构:深入理解类与对象的设计原则

作者名称&#xff1a;纸飞机-暖阳 作者简介&#xff1a;专注于Java和大数据领域&#xff0c;致力于探索技术的边界&#xff0c;分享前沿的实践和洞见 文章专栏&#xff1a;Java那些年专栏 专栏介绍&#xff1a;本专栏涵盖了 Java SE从基础语法到面向对象编程&#xff0c;从异常…

一键PDF水印添加工具

一键PDF水印添加工具 引言优点1. 精准定位与灵活布局2. 自由旋转与透明度调控3. 精细化页码选择4. 全方位自定义水印内容5. 无缝整合工作流程 功能详解结语工具示意图【工具链接】 引言 PDF作为最常用的文档格式之一&#xff0c;其安全性和版权保护显得尤为重要。今天&#xff…

(delphi11最新学习资料) Object Pascal 学习笔记---第10章第3节( 委托的概念)

10.3.2 委托的概念 ​ 乍一看&#xff0c;委托这项技术的目标可能并不明确&#xff0c;但它却是Object Pascal 组件技术的基石之一。秘密就在委托这个词上。如果有人创建了一个对象&#xff0c;这个对象有一些方法指针&#xff0c;那么你只需把新的方法赋值给这些指针&#xf…

[移动端] “viewport“ content=“width=device-width, initial-scale=1.0“ 什么意思

布局视口, 代码如下 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title>Document</title><style>body,html {margin: 0;padding: 0;}.box {width: 200px;height: 200px;background-color: pi…

数据库基础:理解与应用索引与视图

文章目录 前言 索引视图 前言 数据库管理涉及索引、视图。本基础篇不涵盖索引和视图的高级应用和核心概念。 索引 MySQL索引是提高查询性能的数据结构&#xff0c;类似于书籍目录&#xff0c;帮助数据库快速找到数据行&#xff0c;避免全表扫描。索引可应用于单列或多列&a…

vue 项目关于不同分辨率的电脑网页适配方案

流式布局&#xff1a;这是一种相对灵活的布局方式&#xff0c;页面的元素宽度使用相对宽度&#xff08;例如百分比&#xff09;来定义&#xff0c;而不是使用绝对宽度&#xff08;例如像素&#xff09;。这样&#xff0c;当浏览器窗口大小变化时&#xff0c;元素会自动调整大小…

CocoaPods使用详解

CocoaPods使用详解 引言 在iOS开发过程中&#xff0c;第三方库的集成是一个常见的需求。CocoaPods作为一个流行的依赖管理工具&#xff0c;能够大大简化这一过程。它允许开发者通过一个名为Podfile的配置文件来管理项目中的第三方库。本文将详细介绍CocoaPods的安装、基本使用…