【C++】list模拟实现(详解)

         本篇来详细说一下list的模拟实现,list的大体框架实现会比较简单,难的是list的iterator的实现。我们模拟实现的是带哨兵位头结点的list。

1.准备工作

为了不和C++库里面的list冲突,我们在实现的时候用命名空间隔开

//list.h
#pragma once
#include <iostream>
using namespace std;
namespace lyj
{}
//test.cpp
#include "list.h"
namespace lyj
{void test1(){//后续测试代码}
}
int main()
{lyj::test1();return 0;
}

list.hnamespace里面实现list的节点,他的节点是一个单独的结构,并且要用类模板

namespace lyj
{template<class T>struct list_node{T _data; //存的数据list_node<T>* _next;//指向后一个节点list_node<T>* _prev;//指向前一个节点};
}

再在list.hnamespace里面实现list的类,也要用类模板

template<class T>
struct list_node
{T _data; //存的数据list_node<T>* _next;//指向后一个节点list_node<T>* _prev;//指向前一个节点
};template<class T>
class list
{typedef list_node<T> Node; //换个短的名字
public://成员函数private:Node* _head;size_t _size;//list原本没有,我们自己加的
};

成员变量加一个_size方便我们计算链表结点个数。 

list_node类里面还需要一个构造函数。

struct list_node
{T _data; //存的数据list_node<T>* _next;//指向后一个节点list_node<T>* _prev;//指向前一个节点list_node(const T& data = T()) //给缺省值T():_data(data),_next(nullptr),_prev(nullptr){}
};

2.list构造函数

2.1 无参构造/默认构造

list.hlist类里面实现。

list()
{_head = new Node;_head->_next = _head;_head->_prev = _head;
}

哨兵位头节点,自己指向自己。

3.增删查改操作(1)

3.1 size和empty

前面说过,list里面没有设计这个成员变量,我们自己加上,方便记录个数。

size_t size() const
{return _size;
}bool empty() const
{return _size == 0;
}

3.2 push_back 尾插

我们要让尾节点的_next指向新节点,哨兵位头结点的_prev指向新节点新节点的_prev指向原来的尾节点,让新节点的_next指向哨兵位头节点,这样,新节点就成了新的尾节点

wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

​编辑

代码实现如下。

void push_back(const T& x)
{Node* newnode = new Node(x);Node* tail = _head->_prev;//头结点的前一个就是尾节点//将新节点连接起来tail->_next = newnode;newnode->next = _head;newnode->_prev = tail;_head->_prev = newnode;++_size;
}

4.迭代器的实现(重难点)

list部分的迭代器已经不是原生指针了因为链表空间不连续,对指针++,是+到了下一个连续的地址的位置,但是这个位置不是节点。想要访问数据,也不是简单的解引用。string和vector的迭代器可以直接用原生指针,但是list的结构比较特殊,所以他的迭代器的实现也比较特殊。

所以,我们要用一个类来封装节点的指针

4.1 list_iterator类

list.hnamespace里面实现list_iterator类,目前我们已经有3个类了。

template<class T>
struct list_iterator
{};

struct和class的区别在【C++】类和对象(上):初识类和对象-CSDN博客 的第1点有详细介绍。

类里面我们对解引用运算符(*)++运算符进行重载

4.1.1 operator*

template<class T>
struct list_iterator
{typedef list_node<T> Node; //换个短的名字Node* _node;T& operator*() //重载解引用运算符{return _node->_data;//返回数据}
};

解引用运算符重载的返回值是T类型的引用,因为我们需要对数据进行修改

4.1.2 operator++和operator-- (前置++/--)

	Self& operator++() //重载++{_node = _node->_next;//加到下一个节点return *this;//返回自己}

迭代器++之后还是迭代器,所以返回类型是迭代器自己的类型。

Self& operator--() //重载--
{_node = _node->_prev;//减到前一个节点return *this;//返回自己
}

--也是一样。

4.1.3 operator!=和operator==

另外,我们还需要重载!=运算符。

bool operator!=(const Self& s) const
{return _node != s._node;
}

还可以弄一个==。

bool operator==(const Self& s) const
{return _node == s._node;
}

4.1.4 迭代器这个类的构造函数

list_iterator(Node* node):_node(node)
{}

4.2 list类里的迭代器实现

list.hlist类里面实现。

先改个名字,统一一下。

public:typedef list_iterator<T> iterator;

迭代器的begin第一个节点的位置。

iterator begin()
{iterator it(_head->next);return it;
}

上面的写法是有名对象,我们还可以用匿名对象,如下。

iterator begin()
{return iterator(_head->next);
}

还可以走隐式类型转换,因为单参数构造函数支持隐式类型转换,如下。

iterator begin()
{return _head->next;
}

三种写法都可以,任选其一。

迭代器的end最后一个节点的下一个位置,这里就是哨兵位头节点。

iterator end()
{return _head;
}

test.cpp中测试。

list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
list<int>::iterator it = lt.begin();
while (it != lt.end())
{cout << *it << " ";++it;
}
cout << endl;

5.增删查改操作(2)

5.1 insert

在pos位置之前插入节点。

void insert(iterator pos, const T& x)
{Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(x);//连接起来newnode->_next = cur;newnode->_prev = prev;prev->_next = newnode;cur->_prev = newnode;++_size;
}

test.cpp中测试。

void test2()
{list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.insert(lt.begin(), 0);//头插0list<int>::iterator it = lt.begin();while (it != lt.end()){cout << *it << " ";++it;}cout << endl;
}

5.2 头插以及尾插改装

我们实现了insert,push_back尾插就可以套用insert,第一个参数传迭代器end就可以了。

void push_back(const T& x)
{insert(end(), x);++_size;
}

push_front就是头插,也可以套用insert,第一个参数传迭代器begin就可以了。

void push_front(const T& x)
{insert(begin(), x);++_size;
}

两个一起在test.cpp中测试。

void test3()
{list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_front(3);lt.push_front(4);list<int>::iterator it = lt.begin();while (it != lt.end()){cout << *it << " ";++it;}cout << endl;
}

5.3 erase 删除

把pos位置的数据删除,就是把pos前一个节点和pos后一个节点连接起来。但是我们不可以删除哨兵位头节点,迭代器end是尾节点的下一个节点,就是哨兵位,所以pos不可以是end。

void erase(iterator pos)
{assert(pos != end());//不可以删哨兵位头节点Node* prev = pos._node->_prev;//存pos前后节点Node* next = pos._node->_next;prev->_next = next;//链接pos前后节点next->_prev = prev;delete pos._node;//释放pos节点--_size;
}

使用assert要包含头文件#include <assert.h>。 

test.cpp中测试。

list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.erase(++lt.begin());//删除第二个节点
list<int>::iterator it = lt.begin();
while (it != lt.end())
{cout << *it << " ";++it;
}
cout << endl;

5.4 头删和尾删

实现了erase,头删和尾删就可以复用它。

void pop_back()//尾删
{erase(--end());//迭代器部分重载过--
}

尾节点是end的前一个节点。 

void pop_front()//头删
{erase(begin());
}

 在test.cpp中测试。

list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.pop_front();//头删
lt.pop_back();//尾删
list<int>::iterator it = lt.begin();
while (it != lt.end())
{cout << *it << " ";++it;
}
cout << endl;

本次分享就到这里,list还有一部分没有介绍完,我们下次见,拜拜~ 

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

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

相关文章

数字化工厂 MES试点方案全解析(三)

目 录 三、试点实施步骤 需求分析与方案设计阶段 系统开发与测试阶段 系统部署与培训阶段 试点运行与优化阶段 总结与评估阶段 三、试点实施步骤 需求分析与方案设计阶段 1、成立由企业生产、工艺、质量、设备、IT 等多部门人员组成的项目团队&#xff0c;与 MES 供应商共…

ShuffleNet V2:高效卷积神经网络架构设计的实用指南

摘要 https://arxiv.org/pdf/1807.11164 当前&#xff0c;神经网络架构设计大多以计算复杂度的间接指标&#xff0c;即浮点运算数&#xff08;FLOPs&#xff09;为指导。然而&#xff0c;直接指标&#xff08;例如速度&#xff09;还取决于其他因素&#xff0c;如内存访问成本…

【Opencv学习】PART1-图像基础处理

目录 一、图像的读入、显示和保存 1、读入图像 imread函数 范例 显示控制参数 2、显示图像 imshow函数 范例 tips waitkey函数 含义 delay参数: tips destoryAllWindows函数 3、保存图像 imwrite函数 范例 实操 01-读入显示保存 代码 结果 二、图像处理入…

硬中断关闭后的堆栈抓取方法

一、背景 性能和稳定性是一个计算机工程里的一个永恒的主题。其中尤其稳定性这块的问题发现和问题分析及问题解决就依赖合适的对系统的观测的手段&#xff0c;帮助我们发现问题&#xff0c;识别问题原因最后才能解决问题。稳定性问题里尤其底层问题里&#xff0c;除了panic问题…

MT8768/MTK8768安卓核心板性能参数_联发科安卓智能模块开发方案

MT8768安卓核心板 是一款采用台积电12nm FinFET制程工艺的智能手机芯片。MT8768核心板不仅提供所有高级功能和出色体验&#xff0c;同时确保智能终端具备长电池寿命。该芯片提供了一个1600x720高清(20:9比例)分辨率显示屏&#xff0c;排除了清晰度和功耗之间的平衡问题。该芯片…

NVR管理平台EasyNVR多个NVR同时管理:全方位安防监控视频融合云平台方案

EasyNVR是基于端-边-云一体化架构的安防监控视频融合云平台&#xff0c;具有简单轻量的部署方式与多样的功能&#xff0c;支持多种协议&#xff08;如GB28181、RTSP、Onvif、RTMP&#xff09;和设备类型&#xff08;IPC、NVR等&#xff09;&#xff0c;提供视频直播、录像、回放…

ETAS工具导入DBC生成Com协议栈

文章目录 前言DBC配置关键属性Cobra参数配置Cobra使用isolar工程配置总结前言 ETAS工具导入DBC主要也是生成arxml用的,ETAS推荐使用Cobra导入,本文介绍导入过程及注意事项 DBC配置关键属性 对于普通Com报文,配置为周期发送,及其周期,NmMessage配置为No,示例如下: 对…

图形化界面MySQL(MySQL)(超级详细)

1.官网地址 MySQL :: Download MySQL Workbench 1.1在Linux直接点击NO thanks..... 下载完后是这个页面 1.2任何远端登录&#xff0c;再把jj数据库给授权 1.3建立新用户 进行连接 点击这个就运行了 只执行show tables&#xff1b;要先选中 圆圈处支持自己输入 点击这个就执…

vulhub靶场与pikachu靶场

一、搭建vulhub 环境&#xff1a;kaildocker 1.1 提权&#xff1a; :::color4 sudo su #权限升级为root ::: 1.2更新软件&#xff1a; :::color4 apt-get update ::: (此处我已更新过) 1.3安装HTTPS协议和CA证书&#xff1a; :::color4 apt-get install -y apt-transpo…

计算机网络socket编程(6)_TCP实网络编程现 Command_server

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 计算机网络socket编程(6)_TCP实网络编程现 Command_server 收录于专栏【计算机网络】 本专栏旨在分享学习计算机网络的一点学习笔记&#xff0c;欢迎大家在评论…

D78【 python 接口自动化学习】- python基础之HTTP

day78 pycharm创建项目并进行接口请求 学习日期&#xff1a;20241124 学习目标&#xff1a;http定义及实战 -- pycharm创建项目并进行接口请求 学习笔记&#xff1a; 安装requests 安装方式&#xff1a;pip/pip3 install requests 官网教程&#xff1a;Requests: HTTP fo…

Android 设备使用 Wireshark 工具进行网络抓包

背景 电脑和手机连接同一网络&#xff0c;想使用wireshark抓包工具抓取Android手机网络日志&#xff0c;有以下两种连接方法&#xff1a; Wi-Fi 网络抓包。USB 网络共享抓包。需要USB 数据线将手机连接到电脑&#xff0c;并在开发者模式中启用 USB 网络共享。 查看设备连接信…

Docker安装ubuntu1604

首先pull镜像 sudo docker run -d -P m.daocloud.io/docker.io/library/ubuntu:16.04国内使用小技巧&#xff1a; https://github.com/DaoCloud/public-image-mirror pull完成之后查看 sudo docker images 运行docker sudo docker run -d -v /mnt/e:/mnt/e m.daocloud.io/…

【数据结构与算法】树和二叉树

【数据结构与算法】树和二叉树 文章目录 【数据结构与算法】树和二叉树前言一、树的基本概念二、二叉树的基本概念三、二叉树的递归遍历四、二叉树的编程五、二叉树的非递归遍历总结 前言 本篇文章将讲到树的基本概念&#xff0c;二叉树的基本概念&#xff0c;二叉树的递归遍历…

大语言模型---Llama7B和Llama8B的区别;模型参数量;权重文件的不同;嵌入层权重的不同;输入序列长度的不同;应用场景

文章目录 1.概要2. 模型参数量3. 权重文件的不同4. 嵌入层权重的不同5. 输入序列长度的不同6. 应用场景 1.概要 LLaMA&#xff08;Large Language Model Meta AI&#xff09;是由Meta开发的一系列语言模型&#xff0c;其中不同版本的参数量&#xff08;如7B、8B等&#xff09;…

Android Binder技术概览

Android中的Binder是一种基于远程过程调用&#xff08;Remote Procedure Call, RPC&#xff09;的轻量级通信机制&#xff0c;核心用于 Android 系统中的进程间通信&#xff08;Inter-Process Communication, IPC&#xff09;。Binder 是 Android 系统中不可或缺的一部分&#…

NoteExpress导入知网论文无法智能更新题录的处理方法

知网论文下载下来一般为“标题_作者.caj”&#xff0c;只要在导入文件时对字段默认值进行设置就行了。 其他地方下载的论文也是一样&#xff0c;根据文件名称设置字段默认值。

搜索二维矩阵

搜索二维矩阵 给你一个满足下述两条属性的 m x n 整数矩阵&#xff1a; 每行中的整数从左到右按非严格递增顺序排列。每行的第一个整数大于前一行的最后一个整数。 给你一个整数 target &#xff0c;如果 target 在矩阵中&#xff0c;返回 true &#xff1b;否则&#xff0c…

Mysql中的 TEXT 和 BLOB 解析

&#x1f680; 博主介绍&#xff1a;大家好&#xff0c;我是无休居士&#xff01;一枚任职于一线Top3互联网大厂的Java开发工程师&#xff01; &#x1f680; &#x1f31f; 在这里&#xff0c;你将找到通往Java技术大门的钥匙。作为一个爱敲代码技术人&#xff0c;我不仅热衷…

2024强网拟态决赛-eBeepf

漏洞分析与利用 分析后面看情况吧&#xff0c;有时间再写吧&#xff0c;先贴个利用脚本&#xff1a; #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <…