【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);//头插0
	list<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还有一部分没有介绍完,我们下次见,拜拜~ 

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

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

相关文章

IT服务团队建设与管理

在 IT 服务团队中&#xff0c;需要明确各种角色。例如系统管理员负责服务器和网络设备的维护与管理&#xff1b;软件工程师专注于软件的开发、测试和维护&#xff1b;运维工程师则保障系统的稳定运行&#xff0c;包括监控、故障排除等。通过清晰地定义每个角色的职责&#xff0…

初学 flutter 问题记录

windows搭建flutter运行环境 一、运行 flutter doctor遇到的问题 Xcmdline-tools component is missingRun path/to/sdkmanager --install "cmdline-tools;latest"See https://developer.android.com/studio/command-line for more details.1&#xff09;cmdline-to…

神经网络(系统性学习二):单层神经网络(感知机)

此前篇章&#xff1a; 神经网络中常用的激活函数 神经网络&#xff08;系统性学习一&#xff09;&#xff1a;入门篇 单层神经网络&#xff08;又叫感知机&#xff09; 单层网络是最简单的全连接神经网络&#xff0c;它仅有输入层和输出层&#xff0c;没有隐藏层。即&#x…

H.265流媒体播放器EasyPlayer.js播放器提示MSE不支持H.265解码可能的原因

随着人工智能和机器学习技术的应用&#xff0c;流媒体播放器将变得更加智能&#xff0c;能够根据用户行为和偏好提供个性化的内容推荐。总体而言&#xff0c;流媒体播放器的未来发展将更加注重技术创新和用户互动&#xff0c;以适应不断变化的市场需求和技术进步。 提示MSE不支…

MySQL原理简介—6.简单的生产优化案例

大纲 1.MySQL日志的顺序写和数据文件的随机读指标 2.Linux存储系统软件层原理及IO调度优化原理 3.数据库服务器使用的RAID存储架构介绍 4.数据库Too many connections故障定位 1.MySQL日志的顺序写和数据文件的随机读指标 (1)磁盘随机读操作 (2)磁盘顺序写操作 (1)磁盘随…

svn 崩溃、 cleanup失败 怎么办

在使用svn的过程中&#xff0c;可能出现整个svn崩溃&#xff0c; 例如cleanup 失败的情况&#xff0c;类似于 这时可以下载本贴资源文件并解压。 或者直接访问网站 SQLite Download Page 进行下载 解压后得到 sqlite3.exe 放到发生问题的svn根目录的.svn路径下 右键呼出pow…

前后端分离,解决vue+axios跨域和proxyTable不生效等问题

看到我这篇文章前可能你以前看过很多类似的文章。至少我是这样的&#xff0c;因为一直没有很好的解决问题。 正文 当我们通过webstorm等IDE开发工具启动项目的时候&#xff0c;通过命令控制台可以观察到启动项目的命令 如下&#xff1a; webpack-dev-server --inline --prog…

在win10环境部署opengauss数据库(包含各种可能遇到的问题解决)

适用于windows环境下通过docker desktop实现opengauss部署&#xff0c;请审题。 文章目录 前言一、部署适合deskdocker的环境二、安装opengauss数据库1.配置docker镜像源2.拉取镜像源 总结 前言 注意事项&#xff1a;后面docker拉取镜像源最好电脑有科学上网工具如果没有科学上…

Java开发经验——Spring Test 常见错误

摘要 本文详细介绍了Java开发中Spring Test的常见错误和解决方案。文章首先概述了Spring中进行单元测试的多种方法&#xff0c;包括使用JUnit和Spring Boot Test进行集成测试&#xff0c;以及Mockito进行单元测试。接着&#xff0c;文章分析了Spring资源文件扫描不到的问题&am…

2024年亚太地区数学建模大赛D题-探索量子加速人工智能的前沿领域

量子计算在解决复杂问题和处理大规模数据集方面具有巨大的潜力&#xff0c;远远超过了经典计算机的能力。当与人工智能&#xff08;AI&#xff09;集成时&#xff0c;量子计算可以带来革命性的突破。它的并行处理能力能够在更短的时间内解决更复杂的问题&#xff0c;这对优化和…

基于 RBF 神经网络整定的 PID 控制

基于 RBF 神经网络整定的 PID 控制 是结合了传统 PID 控制和 RBF&#xff08;径向基函数&#xff09;神经网络的自适应控制方法。在这种方法中&#xff0c;RBF 神经网络用于自适应地调整 PID 控制器的增益&#xff08;比例增益 KpK_pKp​&#xff0c;积分增益 KiK_iKi​ 和微分…

空间注意力网络的性能优化与多维评估

在本文中&#xff0c;首先分析空间注意力网络&#xff08;Spatial Attention Neural Network&#xff09;在五个不同数据集上的训练结果。这些数据集包括Daily_and_Sports_Activities、WISDM、UCI-HAR、PAMAP2和OPPORTUNITY。通过对比这些结果&#xff0c;我们可以深入理解空间…

Linux——1_系统的延迟任务及定时任务

系统的延迟任务及定时任务 在系统中我们的维护工作大多数时在服务器行对闲置时进行 我们需要用延迟任务来解决自动进行的一次性的维护 延迟任务时一次性的&#xff0c;不会重复执行 当延迟任务产生输出后&#xff0c;这些输出会以邮件的形式发送给延迟任务发起者 在RHEL9中…

【数据结构】—— 线索二叉树

引入 我们现在提倡节约型杜会&#xff0c; 一切都应该节约为本。对待我们的程序当然也不例外&#xff0c;能不浪费的时间或空间&#xff0c;都应该考虑节省。我们再观察团下图的二叉树&#xff08;链式存储结构)&#xff0c;会发现指针域并不是都充分的利用了&#xff0c;有许…

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

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

虚幻引擎---初识篇

一、学习途径 虚幻引擎官方文档&#xff1a;https://dev.epicgames.com/documentation/zh-cn/unreal-engine/unreal-engine-5-5-documentation虚幻引擎在线学习平台&#xff1a;https://dev.epicgames.com/community/unreal-engine/learning哔哩哔哩&#xff1a;https://www.b…

汽车HiL测试:利用TS-GNSS模拟器掌握硬件性能的仿真艺术

一、汽车HiL测试的概念 硬件在环&#xff08;Hardware-in-the-Loop&#xff0c;简称HiL&#xff09;仿真测试&#xff0c;是模型基于设计&#xff08;Model-Based Design&#xff0c;简称MBD&#xff09;验证流程中的一个关键环节。该步骤至关重要&#xff0c;因为它整合了实际…

C++编程库与框架实战——sqlite3数据库

一,SQLite数据库简介 SQLite是可以实现类似于关系型数据库中各种操作的事务性SQL数据库引擎。 SQLite可以为应用程序提供存储于本地的嵌入式数据库,帮助应用程序实现轻量级的数据存储。 SQLite是一个库文件,并不是单独的进程,它可以静态或动态链接到C++应用程序中,然后…

STM32F10x 定时器

使用定时器实现&#xff1a;B5 E5的开关 添加相关的.h路径文件 添加相关的.c配置文件 led.h文件 用于声明LED函数 #ifndef __LED_H //没有定义__LED_H #define __LED_H //就定义__LED_H #define LED1_ON GPIO_ResetBits(GPIOB,GPIO_Pin_5) #defi…

PyQt6+pyqtgraph折线图绘制显示

1、实现效果 2、环境&#xff1a; 确认已经安装pyqtgraph的模块&#xff0c;如果没有安装&#xff0c;使用命令安装&#xff1a; pip install pyqtgraph 3、代码实现&#xff1a; 绘制折线函数&#xff1a; import sys import random from PySide6.QtWidgets import QAppl…