list结构刨析与模拟实现
目录
1.引言
2.C++模拟实现
2.1模拟实现结点
2.2模拟实现list前序
1)构造函数
2)push_back函数
2.3模拟实现迭代器
1)iterator
构造函数和析构函数:
*操作符重载函数:
前置/后置++/--:
==/!=操作符重载函数:
->操作符重载函数:
2)const_iterator
2.4模拟实现list后序
3)insert函数
4)erase函数
5)头插头删尾插尾删函数
6)size函数
7)begin/end函数
8)clear函数
9)析构函数
3.完整list
1.引言
先来说说为什么要模拟实现库里的list?
我认为,模拟实现list有以下几个意义和好处:
1. 学习:通过自己实现一个类似于STL中的list数据结构,可以加深对数据结构和算法的理解。通过亲自编码实现,能更好地理解list的内部工作原理,以及C++语言中与数据结构相关的特性和语法。
2. 练习:编写一个类似于STL中的list可以作为练习和提升编码能力的手段。这有助于加强对C++语言的掌握,并提高面向对象编程的技能。
3. 定制化需求:有时候,STL中提供的list功能不能完全满足特定需求,此时可以通过自己实现一个list类来扩展或定制list的功能,以更好地适应特定的应用场景。
2.C++模拟实现
提前声明,由于list不同类型的函数重载太多太杂,本篇仅仅模拟实现简单的构造,析构,操作符重载,深浅拷贝,增删查改等部分函数的介绍,感谢读者的支持!
建议先创建一个list.hpp头文件,单独创建一个命名空间,防止已经展开了std的命名空间,实现的list与库中list发生冲突。
我们就定义命名空间为drw,接下来完成三个部分的编写:
2.1模拟实现结点
思路:
- 我们先来定义一个结构体__list_node来代表list中存放数据的结点,方便list中函数操作等等访问要求
- 设置一个模板class T来接受各种类型的显式实例化
- 这里不能将__list_node直接命名为Node是为了防止与其他同名结构体冲突
- 设置三个成员变量,分别为:prev next val,用来储存前后结点以及此处结点的数值
- 使用初始化列表完成构造和析构函数
实现:
template<class T>
struct __list_node
{__list_node<T>* prev;__list_node<T>* next;T val;__list_node(const T& t=T()):prev(nullptr),next(nullptr),val(t){}~__list_node(){prev = nullptr;next = nullptr;val = 0;}
};
2.2模拟实现list前序
由于list同样需要模板class T来显式实例化,那么我们先设置一个class T模板参数,为什么是先呢?是因为后面还会有补充,请耐心看完~
因为list不希望结点被外界访问,将结点进行了封装,所以可以先将__list_node重命名为Node来方便表示,并且只能在public之外重命名,list类中只有一个私有成员变量,就是Node* _head,这代表头结点。接下来完成成员函数:
1)构造函数
思路:
- 这里我们实现两个构造函数,分别是直接构造和拷贝构造
- 因为这两种构造都要先初始化一个头结点,我们不妨设置一个Emptyinit函数来做这个事情,方便复用
- 在Emptyinit函数中,先new一个Node,对val不做处理,让prev指向自己,next也指向自己
- 直接构造:就是Emptyinit函数的过程,直接复用
- 拷贝构造:参数是const list<T>& l,除了调用Emptyinit之外还要调用push_back对新的list进行尾插拷贝,这里的push_back后续会讲解
实现:
void Emptyinit()
{Node* guard = new Node;guard->prev = guard;guard->next = guard;_head = guard;
}list()
{Emptyinit();
}list(const list<T>& l)
{Emptyinit();for (auto& e : l)//加上&防止自定义类型深拷贝{push_back(e);}
}
2)push_back函数
思路:
- 先new一个新结点node,将t传进去初始化node
- 再将新结点的prev设置为_head的prev,next为_head
- 更新_head的prev以及原先_head之前结点的next
实现:
void push_back(const T& t)
{Node* newnode = new Node(t);newnode->prev = _head->prev;_head->prev->next = newnode;newnode->next = _head;_head->prev = newnode;//双向带头循环链表,需要复习!
}
void push_back(const T& t)
{insert(_head, t);
}
这里还可以直接调用insert函数,后面介绍!由于后续函数需要迭代器,这里穿插介绍模拟实现迭代器:
2.3模拟实现迭代器
在使用list的时候,我们知道迭代器可以++/--,但是不能+/-,因为list迭代器属于双向迭代器但不属于随机迭代器,但每个结点存储位置是分散开的啊,这怎么实现++/--呢,于是可以定义一个迭代器结构体,将其封装成类,就可以进行这一操作了!
设置模板template<class T>,定义__list_iterator,为了方便表示__list_node,这里也提前重命名一下为Node,设置成员变量为node
1)iterator
构造函数和析构函数:
思路:直接使用初始化列表进行赋值nullptr即可,同样赋值nullptr进行析构,因为node已经有默认构造和析构,就不需要更多的处理
实现:
__list_iterator(Node* _node):node(_node)
{ }~__list_iterator()
{node = nullptr;
}
*操作符重载函数:
思路:直接返回node代表的val即可
实现:
T& operator*()
{return node->val;
}
前置/后置++/--:
思路:++:前置就让node指向next即可,后置就拷贝tmp,让node指向next返回tmp
--:前置node指向prev,后置拷贝tmp,node指向prev返回tmp
实现:
__list_iterator<T>& operator++()//前置
{node = node->next;return *this;
}__list_iterator<T>& operator++(int)//后置
{__list_iterator<T> tmp(*this);node = node->prev;return tmp;
}__list_iterator<T>& operator--()//前置
{node = node->next;return *this;
}__list_iterator<T>& operator--(int)//后置
{__list_iterator<T> tmp(*this);node = node->prev;return tmp;
}
==/!=操作符重载函数:
思路:判断一下是否相等即可
实现:
bool operator!=(const __list_iterator<T>& it){return node != it.node;}bool operator==(const __list_iterator<T>& it){return node == it.node;}
->操作符重载函数:
思路:为什么要有这个函数?是因为如果list存储的是含有多个成员变量的结构体,那么想要访问成员变量不应该仅仅有*.还应该提供->
这里直接返回T*类型的&(node->val)即可就不进行实现展示了。
2)const_iterator
思考:const迭代器与iterator相差在哪里呢?无非就是*操作符重载函数多了一些const,其他大致相同,所以我们就不必再去大费周章重新写,这里增加一个模板参数Ref,在显式实例化的时候传T&或者const T&就可以解决这个问题:
那么这仅仅解决了*函数的重载问题,->函数呢?这当然又需要一个模板参数Ptr,传参方法是一样的。为了简化类型,将 __list_iterator<T, Ref,Ptr> 重命名为self
演示整个迭代器:
template<class T,class Ref,class Ptr>//为什么要传Ref是因为两个结构体太累赘,这样可以简化,要传Ptr是为了给->函数的返回类型也进行模板化
struct __list_iterator
{typedef __list_node<T> Node;typedef __list_iterator<T, Ref,Ptr> self;//这里再次重定义一下方便Node* node;__list_iterator(Node* _node):node(_node){ }Ref operator*(){return node->val;}Ptr operator->()//为什么要重载访问成员操作符呢?是因为显式实例化传参也就是vector里面可能保存的是自定义类型而不是内置类型{return &(node->val);}self& operator++()//前置{node = node->next;return *this;}self& operator++(int)//后置{self tmp(*this);node = node->next;return tmp;}self& operator--()//前置{node = node->prev;return *this;}self& operator--(int)//后置{self tmp(*this);node = node->prev;return tmp;}bool operator!=(const self& it){return node != it.node;}bool operator==(const self& it){return node == it.node;}~__list_iterator(){node = nullptr;}
};
2.4模拟实现list后序
3)insert函数
思路:
- insert函数就是在迭代器位置为pos的地方插入数据
- 开辟一个新结点newnode,将数据传入初始化,记录一下pos的前一个结点地址prev
- 让prev的next指向newnode,newnode的prev指向prev,newnode的next指向pos,pos的prev指向newnode,返回newnode
实现:
iterator insert(iterator pos, const T& t)
{//无需断言Node* prev = pos.node->prev;Node* newnode = new Node(t);prev->next = newnode;newnode->prev = prev;newnode->next = pos.node;pos.node->prev = newnode;return newnode;
}
4)erase函数
思路:
- 判断一下pos是否合法,不能删除头结点
- 记录一下前一个和后一个结点地址,分别为prev和next
- 让prev的next指向next,next的prev指向prev
- 释放掉pos结点,返回next的地址,这是防止迭代器失效的措施
实现:
iterator erase(iterator pos)
{assert(pos != end());//不能删除头结点Node* prev = pos.node->prev;Node* next = pos.node->next;prev->next = next;next->prev = prev;delete pos.node;return next;
}
5)头插头删尾插尾删函数
思路:
- 由于实现了insert和erase,这里直接复用就方便多了
实现:
void push_back(const T& t)
{insert(_head, t);
}void pop_back()
{erase(_head->prev);
}void push_front(const T& t)
{insert(_head->next, t);
}void pop_front()
{erase(_head->next);
}
6)size函数
思路:
- 要计算链表中的结点个数,但不能算入头结点
- 定义size_t sz,从头结点的下一个开始遍历,到指针指向_head结束
实现:
size_t size()
{size_t sz = 0;iterator it = begin();while (it != end()){sz++;it++;}return sz;
}
7)begin/end函数
思路:
- 直接将_head的下一个结点或者_head返回,因为end代表最后一个元素的下一个位置
实现:
iterator begin()
{return _head->next;
}iterator end()
{return _head;
}const_iterator begin()const
{return _head->next;
}const_iterator end()const
{return _head;
}
8)clear函数
思路:
- clear函数是将list中的每一个结点进行释放删除,相当于清空链表
- 用循环实现即可,但是要注意迭代器的失效问题!erase之后的迭代器要及时更新
实现:
void clear()
{iterator it = begin();while (it != end()){it=erase(it);//迭代器失效了!!!要注意!!!}
}
9)析构函数
思路:
- 调用clear函数之后释放掉头结点就完成了析构
实现:
~list()
{clear();delete _head;_head = nullptr;
}
3.完整list
这里给出完整的实现代码:
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
namespace drw
{template<class T>struct __list_node{__list_node<T>* prev;__list_node<T>* next;T val;__list_node(const T& t=T()):prev(nullptr),next(nullptr),val(t){}~__list_node(){prev = nullptr;next = nullptr;val = 0;}};template<class T,class Ref,class Ptr>//为什么要传Ref是因为两个结构体太累赘,这样可以简化,要传Ptr是为了给->函数的返回类型也进行模板化struct __list_iterator{typedef __list_node<T> Node;typedef __list_iterator<T, Ref,Ptr> self;//这里再次重定义一下方便Node* node;__list_iterator(Node* _node):node(_node){ }Ref operator*(){return node->val;}Ptr operator->()//为什么要重载访问成员操作符呢?是因为显式实例化传参也就是vector里面可能保存的是自定义类型而不是内置类型{return &(node->val);}self& operator++()//前置{node = node->next;return *this;}self& operator++(int)//后置{self tmp(*this);node = node->next;return tmp;}self& operator--()//前置{node = node->prev;return *this;}self& operator--(int)//后置{self tmp(*this);node = node->prev;return tmp;}bool operator!=(const self& it){return node != it.node;}bool operator==(const self& it){return node == it.node;}~__list_iterator(){node = nullptr;}};//这样实现太过于累赘,最好再添加一个模版参数来实现//template<class T>//struct __const_list_iterator//{// typedef __list_node<T> Node;// Node* node;// __const_list_iterator(Node* _node)// :node(_node)// {// }// const T& operator*()const// {// return node->val;// }//__const_list_iterator<T>& operator++()//前置//{// node = node->next;// return *this;//}//__const_list_iterator<T>& operator++(int)//后置//{// __const_list_iterator<T> tmp(*this);// node = node->next;// return tmp;//}// bool operator!=(const __const_list_iterator<T>& it)// {// return node != it.node;// }// bool operator==(const __const_list_iterator<T>& it)// {// return node == it.node;// }// ~__const_list_iterator()// {// node = nullptr;// }//};template<class T>class list{typedef __list_node<T> Node;public:/*typedef __list_iterator<T> iterator;typedef __const_list_iterator<T> const_iterator;*///typedef const __list_iterator<T> const_iterator;//不能这样使用 const迭代器那么迭代器就不能改变了typedef __list_iterator<T, T&, T*> iterator;typedef __list_iterator<T, const T&, const T*> const_iterator;void Emptyinit(){Node* guard = new Node;guard->prev = guard;guard->next = guard;_head = guard;}list(){Emptyinit();}list(const list<T>& l){Emptyinit();for (auto& e : l)//加上&防止自定义类型深拷贝{push_back(e);}}list<T>& operator=(list<T> l){swap(_head, l._head);return *this;}//void push_back(const T& t)//{// Node* newnode = new Node(t);// newnode->prev = _head->prev;// _head->prev->next = newnode;// newnode->next = _head;// _head->prev = newnode;//双向带头循环链表,需要复习!//}void push_back(const T& t){insert(_head, t);}void pop_back(){erase(_head->prev);}void push_front(const T& t){insert(_head->next, t);}void pop_front(){erase(_head->next);}iterator begin(){return _head->next;}iterator end(){return _head;}const_iterator begin()const{return _head->next;}const_iterator end()const{return _head;}iterator insert(iterator pos, const T& t){//无需断言Node* prev = pos.node->prev;Node* newnode = new Node(t);prev->next = newnode;newnode->prev = prev;newnode->next = pos.node;pos.node->prev = newnode;return newnode;}iterator erase(iterator pos){assert(pos != end());//不能删除头结点Node* prev = pos.node->prev;Node* next = pos.node->next;prev->next = next;next->prev = prev;delete pos.node;return next;}size_t size(){size_t sz = 0;iterator it = begin();while (it != end()){sz++;it++;}return sz;}void clear(){iterator it = begin();while (it != end()){it=erase(it);//迭代器失效了!!!要注意!!!}}~list(){clear();delete _head;_head = nullptr;}private:Node* _head;};
}
相关文章:
list结构刨析与模拟实现
目录 1.引言 2.C模拟实现 2.1模拟实现结点 2.2模拟实现list前序 1)构造函数 2)push_back函数 2.3模拟实现迭代器 1)iterator 构造函数和析构函数: *操作符重载函数: 前置/后置/--: /!操作符重载…...

机器人部分专业课
华东理工 人工智能与机器人导论 Introduction of Artificial Intelligence and Robots 必修 考查 0.5 8 8 0 1 16477012 程序设计基础 The Fundamentals of Programming 必修 考试 3 64 32 32 1 47450012 算法与数据结构 Algorithm and Data Structure 必修 考试 3 56 40 …...

流行粗野主义几何风现代曲线标题logo设计psai无衬线英文字体安装包 Mortend – Extended Family
介绍我们名为 Mortend 的新探索,这是一个强大的扩展字体系列。Mortend 的设计具有几何形状、大胆、强烈的曲线和现代感。灵感来自当今流行的粗野主义海报和极简主义设计,让您有更多机会表达您的创造力。这个字体系列带来了强烈的感觉而优雅的外观&#x…...

前端常见面试题-2025
vue4.0 Vue.js 4.0 是在 2021 年 9 月发布。Vue.js 4.0 是 Vue.js 的一个重要版本,引入了许多新特性和改进,旨在提升开发者的体验和性能。以下是一些关键的更新和新特性: Composition API 重构:Vue 3 引入了 Composition API 作为…...

高通Camera点亮3——Camera Module
Camera点亮除了Sensor之外还需要配置module、EEPROM等,multicamera;配置好编译设置。 Module <?xml version"1.0" encoding"utf-8" ?> <cameraModuleData<!--Module group can contain either 1 module or 2 modules…...

学习路程二 LangChain基本介绍
前面简单调用了一下deepseek的方法,发现有一些疑问和繁琐的问题,需要更多的学习,然后比较流行的就是LangChain这个东西了。 目前大部分企业都是基于 LangChain 、qwen-Agent、lammaIndex框架进行大模型应用开发。LangChain 提供了 Chain、To…...

Docker-技术架构演进之路
目录 一、概述 常见概念 二、架构演进 1.单机架构 2.应用数据分离架构 3.应用服务集群架构 4.读写分离 / 主从分离架构 5.引入缓存 —— 冷热分离架构 6.垂直分库 7.业务拆分 —— 微服务 8.容器化引入——容器编排架构 三、尾声 一、概述 在进行技术学习过程中&am…...
API接口设计模式:从分层架构到CQRS的实战应用
以下将从分层架构和 CQRS(命令查询职责分离)的基本概念入手,为你阐述从分层架构到 CQRS 的实战应用相关内容: 分层架构 概念:分层架构是将系统按照功能划分为不同的层次,每个层次负责特定的职责,…...

【机器学习】【KMeans聚类分析实战】用户分群聚类详解——SSE、CH 指数、SC全解析,实战电信客户分群案例
1.引言 在实际数据分析中,聚类算法常用于客户分群、图像分割等场景。如何确定聚类数 k 是聚类分析中的关键问题之一。本文将以“用户分群”为例,展示如何通过 KMeans 聚类,利用 SSE(误差平方和,也称 Inertiaÿ…...
【C++】 时间库chrono计算程序运行时间
C 时间库chrono计算程序运行时间 本文总结了chrono库的引入方法以及计算程序片段运行时间的方法 一、chrono库的引入方法(注意事项) 首先chrono是属于std命名空间的。 所以在程序中应该这样包含头文件: #include <chrono> using n…...
PCL 边界体积层次结构(Boundary Volume Hierarchy, BVH)
文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 边界体积层次结构(Boundary Volume Hierarchy, BVH) 是一种高效的空间数据结构,广泛应用于计算机图形学、计算机视觉、机器人学、物理仿真等领域。它的核心思想是通过将空间递归地划分为层次化的包围体(通常是轴…...
算法随笔_58: 队列中可以看到的人数
上一篇:算法随笔_57 : 游戏中弱角色的数量-CSDN博客 题目描述如下: 有 n 个人排成一个队列,从左到右 编号为 0 到 n - 1 。给你以一个整数数组 heights ,每个整数 互不相同,heights[i] 表示第 i 个人的高度。 一个人能 看到 他右边另一个人…...

创建React项目的三个方式
创建React项目 创建一个React项目非常简单,通常有几种方法可以进行,下面是最常见的几种方法: 1. 使用 create-react-app (已经不被推荐了) create-react-app 是一个官方的脚手架工具,用于快速创建 React 项目。它会为你配置好很…...
QT闲记-工具栏
工具栏通常用来放置常用的操作按钮,如QPushButton,QAction等。可以放置在顶部,底部,左侧,右侧,并且支持拖曳,浮动。 1、创建工具栏 通常通过QMainWindow 提供的addToolBar()来创建,它跟菜单栏一样,如果需要工具栏,一般情况下,我们设置这个类的基类为QMainWindow。 …...

为什么继电器要加一个反向并联一个二极管
1 动感就是电流不突变 2 为什么有的继电器上面要反向并联一个二极管和电阻 1 并联二极管是为消除掉动感产生的高压 2 加上二极管是为了让继电器更快的断开(二极管选型的工作电流要大于动感电流,开关要够快) 3 公式:二极管压降0…...
【Leetcode 每日一题 - 扩展】1512. 好数对的数目
问题背景 给你一个整数数组 n u m s nums nums。 如果一组数字 ( i , j ) (i,j) (i,j) 满足 n u m s [ i ] n u m s [ j ] nums[i] nums[j] nums[i]nums[j] 且 i < j i < j i<j,就可以认为这是一组 好数对 。 返回好数对的数目。 数据约束 1 ≤ n …...
vue3 采用xlsx库实现本地上传excel文件,前端解析为Json数据
需求:本地上传excel 文件,但需要对excel 文件的内容进行解析,然后展示出来 1. 安装依赖 首先,确保安装了 xlsx 库: bash复制 npm install xlsx 2. 创建 Vue 组件 创建一个 Vue 组件(如 ExcelUpload.v…...

计算机视觉:经典数据格式(VOC、YOLO、COCO)解析与转换(附代码)
第一章:计算机视觉中图像的基础认知 第二章:计算机视觉:卷积神经网络(CNN)基本概念(一) 第三章:计算机视觉:卷积神经网络(CNN)基本概念(二) 第四章:搭建一个经典的LeNet5神经网络(附代码) 第五章࿱…...

FPGA DSP:Vivado 中带有 DDS 的 FIR 滤波器
本文使用 DDS 生成三个信号,并在 Vivado 中实现低通滤波器。低通滤波器将滤除相关信号。 介绍 用DDS生成三个信号,并在Vivado中实现低通滤波器。低通滤波器将滤除较快的信号。 本文分为几个主要部分: 信号生成:展示如何使用DDS&am…...

记录此刻:历时两月,初步实现基于FPGA的NVMe SSD固态硬盘存储控制器设计!
背景 为满足实验室横向项目需求,在2024年12月中下旬导师提出基于FPGA的NVMe SSD控制器研发项目。项目核心目标为:通过PCIe 3.0 x4接口实现单盘3000MB/s的持续读取速率。 实现过程 调研 花了半个月的时间查阅了一些使用FPGA实现NVME SSD控制器的论文、…...
Cursor实现用excel数据填充word模版的方法
cursor主页:https://www.cursor.com/ 任务目标:把excel格式的数据里的单元格,按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例,…...

CTF show Web 红包题第六弹
提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框,很难让人不联想到SQL注入,但提示都说了不是SQL注入,所以就不往这方面想了 先查看一下网页源码,发现一段JavaScript代码,有一个关键类ctfs…...

简易版抽奖活动的设计技术方案
1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...

(二)TensorRT-LLM | 模型导出(v0.20.0rc3)
0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述,后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作,其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...

Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...

第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词
Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵,其中每行,每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid,其中有多少个 3 3 的 “幻方” 子矩阵&am…...
OD 算法题 B卷【正整数到Excel编号之间的转换】
文章目录 正整数到Excel编号之间的转换 正整数到Excel编号之间的转换 excel的列编号是这样的:a b c … z aa ab ac… az ba bb bc…yz za zb zc …zz aaa aab aac…; 分别代表以下的编号1 2 3 … 26 27 28 29… 52 53 54 55… 676 677 678 679 … 702 703 704 705;…...

Unity VR/MR开发-VR开发与传统3D开发的差异
视频讲解链接:【XR马斯维】VR/MR开发与传统3D开发的差异【UnityVR/MR开发教程--入门】_哔哩哔哩_bilibili...
32单片机——基本定时器
STM32F103有众多的定时器,其中包括2个基本定时器(TIM6和TIM7)、4个通用定时器(TIM2~TIM5)、2个高级控制定时器(TIM1和TIM8),这些定时器彼此完全独立,不共享任何资源 1、定…...