使用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;
}


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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何免费生成网址二维码?支持自定义设计的二维码生成器

在国内外的许多创意广告中都在使用网址二维码。比如&#xff1a;大众汽车隐藏在汽车零件上的企业招聘二维码&#xff0c;扫码后进入大众汽车官网在线申请投递简历&#xff1b;帕森斯设计学院的户外广告中打印在红色沙滩椅上的二维码&#xff0c;扫描后可以在线申请暑期课程&…

详细分析mysqlslap的基本知识 | 压力测试(附Demo)

目录 前言1. 基本知识2. 参数解读2.1 auto-generate-sql2.2 only-print2.3 iterations2.4 并发处理参数 前言 对数据库进行压力测试&#xff0c;对此补充这方面的详细知识点 1. 基本知识 mysqlslap 是 MySQL 自带的用于模拟数据库负载的压力测试工具 可以模拟多个客户端并发…

【Java | 多线程】LockSupport 的使用和注意事项

了解一下 LockSupport LockSupport是一个类&#xff0c;位于java.util.concurrent.locks包中&#xff0c;提供了基本的线程同步机制。 LockSupport的主要作用是挂起和唤醒线程。它提供了两个主要的静态方法&#xff1a;park()和unpark()。 park()&#xff1a;用于挂起当前线…

AI论文速读 |从图结构角度统一车道级交通预测:基准和基线

题目&#xff1a;Unifying Lane-Level Traffic Prediction from a Graph Structural Perspective: Benchmark and Baseline 作者&#xff1a;Shuhao Li, Yue Cui, Jingyi Xu, Libin Li, Lingkai Meng, Weidong Yang(杨卫东), Fan Zhang, Xiaofang Zhou(周晓方) 机构&#xff…

【Python】Python函数的黑魔法:递归,嵌套函数与装饰器

欢迎来到CILMY23的博客 本篇主题为&#xff1a; Python函数的黑魔法&#xff1a;递归&#xff0c;嵌套函数与装饰器 个人主页&#xff1a;CILMY23-CSDN博客 系列专栏&#xff1a;Python | C | C语言 | 数据结构与算法 感谢观看&#xff0c;支持的可以给个一键三连&#xff…

redis基于Stream类型实现消息队列,命令操作,术语概念,个人总结等

个人大白话总结 1 在Redis Stream中&#xff0c;即使消息被消费者确认&#xff08;acknowledged, ACK&#xff09;&#xff0c;消息也不会自动从Stream数据结构中删除。这与Kafka或RabbitMQ等传统消息队列系统的做法不同&#xff0c;在那些系统中&#xff0c;一旦消息被消费并…

废液收集系统物联网远程监控解决方案

废液收集系统物联网远程监控解决方案 在面对日益严峻的环保压力和严格的法律法规要求下&#xff0c;构建一套高效、智能的废液收集系统物联网远程监控解决方案显得尤为重要。该方案旨在通过深度融合物联网技术、云计算、大数据分析等先进手段&#xff0c;实现对废液收集系统的…

RuoYi-Vue-Plus (SaToken 注解鉴权)

一、SaInterceptor 注解鉴权和路由拦截鉴权 拦截器&#xff1a;SaInterceptor 实现类位置&#xff1a; cn.dev33.satoken.interceptor.SaInterceptor 功能&#xff1a;Sa-Token 综合拦截器&#xff0c;提供注解鉴权和路由拦截鉴权能力 /*** 创建一个 Sa-Token 综合拦截器&…

测试用例设计方法-异常测试

飞的最高的海鸥&#xff0c;能看到最远的奇景。大家好&#xff0c;继续给大家分享如何进行异常测试&#xff0c;首先要做好异常测试&#xff0c;需要我们对被测系统进行全面的了解&#xff0c;熟悉被测系统的功能、架构和运行机制&#xff0c;然后在这个基础上尽可能覆盖各种的…

再谈“协议”

1.认识协议 之前我们使用TCP的方式实现了一个服务器&#xff0c;而TCP是面向字节流的&#xff0c;而UDP是面向数据报的&#xff0c;接下来通过一个例子区分两种的区别。 UDP面向数据报&#xff1a;就如同发快递&#xff0c;你发多少个快递&#xff0c;对面就收到多少个快递&am…

探索React Router:实现动态二级路由

我有一个路由配置的二维数组&#xff0c;想根据这个数组结合路由组件来动态生成路由&#xff0c;应该怎么样实现。在 React Router 6 中渲染二级路由的方式跟 React Router 65相比有一些变化,但核心思路仍然是利用 Route 组件和路由嵌套的方式。下面是具体的步骤: 定义路由数组…

OpenCompass 大模型评测实战——作业

OpenCompass 大模型评测实战——作业 一、基础作业1.1、使用 OpenCompass 评测 internlm2-chat-1_8b 模型在 C-Eval 数据集上的性能1.1.1、安装基本环境1.1.2、解压数据集1.1.3、查看支持的数据集和模型1.1.4、启动评测 二、进阶作业2.1、将自定义数据集提交至OpenCompass官网 …

WIFISKY 7层流控路由器 confirm.php RCE漏洞复现

0x01 产品简介 WIFISKY-7层流控路由器是一款可用于家庭或办公环境的无线路由器,具备流控功能以优化网络流量和提供更稳定的网络连接。该路由器采用了7层流控技术,能够依据网络数据包的内容进行智能管理,从而实现对网络流量的精细化控制和优化。这种技术可以提升网络的整体性…

vscode 使用文件模板功能来添加版权信息

vscode 新建文件的时候&#xff0c;自动填充作者及版权信息 无需使用插件&#xff0c;操作如下&#xff1a; 选择 “首选项(Preferences)”。在搜索框中输入 “file template” 或者 “文件模板”&#xff0c;然后选择相关的设置项。 {"C_Cpp.clang_format_fallbackSt…

ctfshow web入门 SQl注入 web191--web200

web191 多了一个正则绕过 上脚本布尔盲注 用ord #author:yu22x import requests import string url"http://70adf0cb-2208-4974-b064-50a4f4103541.challenge.ctf.show/api/index.php" sstring.ascii_lettersstring.digits flag for i in range(1,45):print(i)for j…

【熵与特征提取】从近似熵,到样本熵,到模糊熵,再到排列熵,包络熵,散布熵,究竟实现了什么?(第六篇)——“散布熵”及其MATLAB实现

今天讲散布熵&#xff0c;之前用了几篇文章分别讲述了功率谱熵、奇异谱熵、能量熵、近似熵、样本熵、模糊熵、排列熵、包络熵这8种类型的熵&#xff1a; Mr.看海&#xff1a;【熵与特征提取】基于“信息熵”的特征指标及其MATLAB代码实现&#xff08;功率谱熵、奇异谱熵、能量…

脚手架搭建项目package.json配置中依赖的版本问题

脚手架搭建项目package.json配置中依赖的版本问题 问题描述&#xff1a;项目刚搭建好&#xff0c;运行没有问题&#xff0c;为什么过一段时间&#xff0c;删除node_modules&#xff0c;或者重新安装包依赖&#xff0c;然后项目某些地方出现莫名的错误&#xff08;依赖库的地方…

希捷HDD最新财报:销售同比下降11%,环比增长6%,4Q24前景看好

Seagate Technology Holdings plc公布了截至2024年3月29日的第三财季财务业绩。 “随着云需求改善、我们强大的运营纪律和价格执行&#xff0c;希捷3月季度的营收增长了6%&#xff0c;非GAAP每股收益较上一季度翻了一番多。这种组合为我们市场复苏时回归目标利润率奠定了基础。…

C++:类与对象完结篇

hello&#xff0c;各位小伙伴&#xff0c;本篇文章跟大家一起学习《C&#xff1a;运算符重载》&#xff0c;感谢大家对我上一篇的支持&#xff0c;如有什么问题&#xff0c;还请多多指教 &#xff01; 文章目录 重新认识构造函数1.初始化列表2.explicit关键字 static成员1.sta…

面试:ThreadLocal

目录 1、ThreadLocal可以实现〔资源对象】的线程隔离&#xff0c;让每个线程各用各的【资源对象】&#xff0c;避免争用引发的线程安全问题 2、ThreadLocal同时实现了线程内的资源共享 3、原理 4、为什么ThreadLocalMap 中的 key (即 ThreadLocal &#xff09;要设计为弱引用…