【C++】list原理讲解及其实现

news2024/11/29 22:35:06

目录
一、认识list底层结构
二、list的构造类函数
三、迭代器
四、数据的访问
五、容量相关的函数
六、关于数据的增删查改操作

前言

要模拟实现list,必须要熟悉list的底层结构以及其接口的含义,在上一篇我们仔细讲解了list的常见接口的使用及其含义,这篇我们就直接进入主题


一、list底层结构

list底层实现的是带头双向循环链表,list底层实现需要三个类,分别是链表的结点类,链表的迭代器类,链表本身

// List的节点类
template<class T>
struct ListNode
{
	ListNode<T>* _prev;
	ListNode<T>* _next;
	T _data;

	ListNode(const T& data=T())
		:_prev(nullptr)
		,_next(nullptr)
		,_data(data)
	{}
};
//List的迭代器类
template<class T,class Ref,class Ptr>
struct ListIterator
{
    typedef ListNode<T> Node;
    typedef ListIterator<T,Ref,Ptr> Self;
    Node* _node;
    ListIterator(Node* node)
   			:_node(node)
    {}
    Ref operator*();
    Ptr operator->();
    Self& operator++();
    Self operator++(int);
    Self& operator--();
    Self operator--(int);
    bool operator!=(const Self& l);
    bool operator==(const Self& l);
};
 //list类
template<class T>
class list
{
    typedef ListNode<T> Node;
    Node* _head;//哨兵位的头节点
    size_t _size;//链表数据个数
public:
    typedef ListIterator<T, T&, T*> iterator;
    typedef ListIterator<T, const T&, const T&> const_iterator;
}

给大家讲一下这三个类的关联,链表类是主类,链表的结点用一个类封装起来,构造起来更方便,放在主类里面不方便且冗余,由于链表的物理结构不连续,所以不能像vector和string那样单纯的用原生指针来实现,我们可以把结点类再次进行封装,封装成迭代器,让它能很好的指向链表的结点,利用它的结构优势来重载运算符遍历这个链表


二、初始化list的构造函数

1、默认构造
list();

void empty_init()
{
	_head = new Node;
	_head->_next = _head;
	_head->_prev = _head;
	_size = 0;
}

list()
{
	empty_init();
	//这里没有直接写默认构造,而是通过empty_init来实现,因为后面我们要多次用到这个函数来初始化哨兵位的头节点,写在默认构造里面就不方便其他地方的调用了
}

2、用n个val来构造链表
list(size_t n, const T& val = T());

list(size_t n, const T& val = T())
{
	empty_init();//要记得初始化哨兵位的头节点
	for (size_t i = 0; i < n; i++)
	{
		push_back(val);
	}
	_size = n;
}

3、迭代器区间构造
template <class InputIterator> list(InputIteratorfirst, InputIterator last);

list(InputIterator first, InputIterator last)
{
	empty_init();
	while (first != last)
	{
		push_back(*first);
		first++;
	}
}

特别注意:
如果你写了迭代器区间构造,就一定要重载上面的list(size_t n, const T& val = T()),如果你不重载的话,他会报一个错误 非法间接寻址,博主我深受其害🤡,再重载一个这个函数list(int n, const T& val = T());就可以 其实就是改一个类型的事

为什么要重载一下?

list<int> li(10,1);
你传的参数都是int类型,两个类型是一样的,而迭代器区间构造的类型是一样的,最符合,这就导致你没走你
想走的那个构造函数,你想走的那个函数list(size_t n, const T& val = T())在这个案例中是size_t和int类型,虽然
也可以走这个函数,但是编译器觉得迭代器区间构造更好,所以你得重载一个int,int类型的,这样编译器就会
走你重载的函数了

4、拷贝构造
(用来初始化一个正在创建的对象)
list(const list<T>& li);

list(const list<T>& li)
{
	empty_init();
	for (auto& e : li)
	{
		push_back(e);
	}
	_size = li._size;
}

5、赋值构造
(两个已经存在的对象,一个赋值给另一个)
list<T>& operator=(const list<T> li)

void swap(list<T>& li)
{
	std::swap(_head, li._head);
	std::swap(_size, li._size);
}
list<T>& operator=(list<T> li)
{
	swap(li);
	return *this;
}

6、析构函数
~list();

void clear()
{
	iterator it = begin();
	while (it != end())
	{
		it = erase(it);
	}
	_size = 0;
}
~list()
{
	clear();
	delete _head;
	_head = nullptr;
}

三、迭代器

讲到list迭代器这里,我们先把迭代器类完善一下

template<class T,class Ref,class Ptr>//Ref表示引用,Ptr表示指针
struct ListIterator
{
    typedef ListNode<T> Node;
    typedef ListIterator<T,Ref,Ptr> Self;
    Node* _node;
    ListIterator(Node* node)
   			:_node(node)
    {}
    //*it
    Ref operator*()
    {
    	return _node->_data;
    }
    //it->
    Ptr operator->()
    {
    	return &_node->_data;
    }
    //++i
	Self& operator++()
	{
		_node = _node->_next;
		return *this;//返回的变量出了作用域不会被销毁用引用返回更合适
	}
	//i++
	Self operator++(int)
	{
		Self temp(*this);
		_node = _node->_next;
		return temp;//返回的变量出了作用域会被销毁用传值返回更合适
	}
	Self& operator--()
	{
		_node = _node->_prev;
		return *this;
	}
	Self operator--(int)
	{
		Self temp(*this);
		_node = _node->_prev;
		return temp;
	}
	Ref operator*()
	{
		return _node->_data;
	}
	bool operator!=(const Self& s)
	{
		return _node != s._node;//这里是用迭代器的地址去比较
	}
	bool operator==(const Self& s)
	{
		return _node == s._node;
	}
};

这个类里面的接口,我们重点讲解一下这个Ptr operator->();如下图:
在这里插入图片描述

iterator begin();
iterator end();
const_iterator begin();
const_iterator end();

template<class T>
class list
{
    typedef ListNode<T> Node;
    Node* _head;//哨兵位的头节点
    size_t _size;//链表数据个数
public:
    typedef ListIterator<T, T&, T*> iterator;
    typedef ListIterator<T, const T&, const T&> const_iterator;
    iterator begin()
    {
    	//return iterator(_head->_next);匿名对象
		return _head->_next;//单参数的构造函数隐式类型转换
	}
    iterator end()
    {
		return _head;
	}
    const_iterator begin()
    {
		return _head->_next;
	}
    const_iterator end()
    {
		return _head;
	}
}

四、数据的访问

1、front 访问头结点
T& front();

T& front()
{
	return _head->_next->_data;
}

const T& front()const;

const T& front() const
{
	return _head->_next->_data;
}

2、back 访问尾节点
T& back();

T& back()
{
	return _head->_prev->_data;
}

const T& back()const;

const T& back() const
{
	return _head->_prev->_data;
}

五、容量相关的函数

size 有效数据个数
size_t size()const;

size_t size() const 
{
	return _size;
}

empty 判断是否为空
bool empty()const;

bool empty()
{
   return _size == 0;
}

六、关于数据的增删查改操作

push_back 尾插数据
void push_back(const T& val) ;

void push_back(const T& val)
{
	Node* newnode = new Node(val);
	Node* tail = _head->_prev;

	tail->_next = newnode;
	newnode->_prev = tail;
	newnode->_next = _head;
	_head->_prev = newnode;

	_size++;
}

pop_back 尾删数据
void pop_back() ;

void pop_back()
{
	Node* tail = _head->_prev;
	Node* prev = tail->_prev;
	delete tail;
	prev->_next = _head;
	_head->_prev = prev;
	_size--;
}

push_front 头插数据
void push_front(const T& val);

void push_front(const T& val)
{
	Node* newnode = new Node(val);
	Node* next = _head->_next;
	//_head newnode next
	newnode->_next = next;
	next->_prev = newnode;
	newnode->_prev = _head;
	_head->_next = newnode;
	_size++;
}

pop_front 头删数据
pop_front();

void pop_front()
{
	Node* del = _head->_next;
	Node* next = del->_next;
	delete del;
	_head->_next = next;
	next->_prev = _head;
	_size--;
}

insert 在pos位置前插入值为val的节点
iterator insert(iterator pos, const T& val);

void  insert(iterator pos, const T& val)
{
	Node* cur = pos._node;
	Node* prev = cur->_prev;
	Node* newnode = new Node(val);
	//prev newnode cur
	prev->_next = newnode;
	newnode->_prev = prev;
	newnode->_next = cur;
	cur->_prev = newnode;

	_size++;
	
}

erase 删除pos位置的节点,返回该节点的下一个位置
iterator erase(iterator pos);

iterator erase(iterator pos)
{
	Node* cur = pos._node;
	Node* prev = cur->_prev;
	Node* next = cur->_next;
	delete cur;
	prev->_next = next;
	next->_prev = prev;
	_size--;
	return next;
}

clear 清除结点
void clear();

void clear()
{
	iterator it = begin();
	while (it != end())
	{
		it = erase(it);
	}
	_size = 0;
}

swap 交换两个链表
void swap(list<T>& l);

void swap(list<T>& li)
{
	std::swap(_head, li._head);
	std::swap(_size, li._size);
}

list篇到这里就结束了🎉,欢迎大家来指教我的下一篇stack和queue

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

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

相关文章

consul启动Error_server_rejoin_age_max (168h0m0s) - consider wiping your data dir

consul 启动报错&#xff1a; consul[11880]: 2024-05-12T08:37:51.095-0400 [ERROR] agent: startup error: error"refusing to rejoin cluster because server has been offline for more than the configured server_rejoin_age_max (168h0m0s) - consider wiping you…

IntelliJ的Maven编译返回找不到有效证书

文章目录 小结问题及解决找不到有效证书找不到org.springframework.stereotype.Service问题IntelliJ: Cannot resolve symbol springframework 参考 小结 将IntelliJ工程拷贝到新的机器中&#xff0c;返回Maven编译返回找不到有效证书的问题&#xff0c;进行了解决。 问题及解…

通过内网穿透实现远程访问个人电脑资源详细过程(免费)(NatApp + Tomcat)

目录 1. 什么是内网穿透 2. 内网穿透软件 3. NatApp配置 4. 启动NatApp 5. 通过内网穿透免费部署我们的springboot项目 通过内网穿透可以实现远程通过网络访问电脑的资源&#xff0c;本文主要讲述通过内网穿透实现远程访问个人电脑静态资源的访问&#xff0c;下一章节将讲…

Java入门基础学习笔记18——赋值运算符

赋值运算符&#xff1a; 就是“”&#xff0c;就是给变量赋值的&#xff0c;从右边往左边看。 int a 10; // 把数据赋值给左边的变量a存储。 扩展赋值运算符&#xff1a; 注意&#xff1a;扩展的赋值运算符隐含了强制类型转换。 package cn.ensource.operator;public class…

Unity Animation--动画窗口指南(使用动画视图)

Unity Animation--动画窗口指南&#xff08;使用动画视图&#xff09; 使用动画视图 window -> Animation 即可打开窗口 查看GameObject上的动画 window -> Animation -> Animation 默认快捷键 Ctrl 6 动画属性列表 在下面的图像中&#xff0c;“动画”视图&am…

【LAMMPS学习】八、基础知识(6.3)使用 LAMMPS GUI

8. 基础知识 此部分描述了如何使用 LAMMPS 为用户和开发人员执行各种任务。术语表页面还列出了 MD 术语,以及相应 LAMMPS 手册页的链接。 LAMMPS 源代码分发的 examples 目录中包含的示例输入脚本以及示例脚本页面上突出显示的示例输入脚本还展示了如何设置和运行各种模拟。 …

合并连个有序链表(递归)

21. 合并两个有序链表 - 力扣&#xff08;LeetCode&#xff09; 2.讲解算法原理 2.1重复子问题 2.2只关心其中的一个子问题是如何解决的 2.3细节&#xff0c;递归出口 3.小总结 &#xff08;循环&#xff08;迭代&#xff09;VS 递归&#xff09;&#xff08;递归VS深搜&…

49. UE5 RPG 使用Execution Calculations处理对目标造成的最终伤害

Execution Calculations是Unreal Engine中Gameplay Effects系统的一部分&#xff0c;用于在Gameplay Effect执行期间进行自定义的计算和逻辑操作。它允许开发者根据特定的游戏需求&#xff0c;灵活地处理和修改游戏中的属性&#xff08;Attributes&#xff09;。 功能强大且灵…

AI 重塑产品设计

作者&#xff1a;明明如月学长&#xff0c; CSDN 博客专家&#xff0c;大厂高级 Java 工程师&#xff0c;《性能优化方法论》作者、《解锁大厂思维&#xff1a;剖析《阿里巴巴Java开发手册》》、《再学经典&#xff1a;《Effective Java》独家解析》专栏作者。 热门文章推荐&am…

系统设计中的泛化调用

背景 目前在学习一些中间件&#xff0c;里面看到了一个词是叫泛化调用&#xff0c; 其实这个场景在JAVA中比较常见。我们常用的有反射&#xff0c;反射就是我知道类名称、类方法和参数&#xff0c;调用一个Object的类&#xff0c;但是在HTTP或者RPC远程调用过程中&#xff0c;…

【C++】stack和queue 适配器

&#x1f525;个人主页&#xff1a;北辰水墨 &#x1f525;专栏&#xff1a;C学习仓 本节内容我们来讲解栈和队列的模拟实现&#xff0c;文末会赋上模拟实现的代码 一、stack的使用和模拟实现 stack适配器的介绍&#xff1a; 1. stack是一种容器适配器&#xff0c;专门用在具…

Redis的数据淘汰策略——Java全栈知识(19)

Redis的数据淘汰策略 什么是数据淘汰策略 数据过期策略是 redis 中设置了 TTL 的数据过期的时候 Redis 的处理策略。数据淘汰策略是 Redis 内存不够的时候&#xff0c; 数据的淘汰策略&#xff1a;当 Redis 中的内存不够用时&#xff0c;此时在向 Redis 中添加新的 key, 那么…

物联网设计竞赛_2_Ubuntu联网配置

采用nat配置 随便定义一个VMnet虚拟网络接口&#xff0c;定义成nat模式 如果主机用的校园网&#xff0c;那么虚拟机发送消息将通过nat转换&#xff0c;转换成用户校园网ip进行发送&#xff0c;发送到校园网路由器再经过nat转换成公网ip访问互联网 点击NAT设置和DHCP设置记录好…

3kCTF2021 echo klibrary

文章目录 前言echoklibrary 前言 今天状态不好&#xff0c;很多事情都不想干&#xff0c;就做一做简单的题目 echo 内核版本&#xff1a;v5.9.10smap/smep/kaslr 开启modprobe_path 可写 题目给了源码&#xff0c;非常简单就是无限次的任意地址读写&#xff1a; #include …

js逆向-某投资平台参数分析

声明 本文仅供学习参考&#xff0c;如有侵权可私信本人删除&#xff0c;请勿用于其他途径&#xff0c;违者后果自负&#xff01; 如果觉得文章对你有所帮助&#xff0c;可以给博主点击关注和收藏哦&#xff01; 分析 aHR0cDovLzIyMS4yMTQuOTQuNTE6ODA4MS9pY2l0eS9pcHJvL2hhb…

如何在适用于 Linux 的 Visual Studio Code 中使用 .NET 8 上的 FastReport Avalonia

我们将继续撰写有关在各种操作系统上的 Visual Studio Code 中使用 FastReport Avalonia 的系列文章。在本文中&#xff0c;我们将详细分析如何使用 Visual Studio Code IDE 在 Linux 操作系统上运行 FastReport Avalonia。 Avalonia UI 是一个积极用于开发跨平台用户界面的 .…

Keysight 是德 N1077B 光/电时钟恢复设备,收藏保存

Keysight N1077B是一款光/电时钟恢复设备&#xff0c;支持115 MBd至24 GBd的数据速率范围&#xff0c;适用于多模和单模光信号以及电信号。该设备能够处理PAM4和NRZ两种类型的数据信号&#xff0c;并提供符合标准的时钟恢复功能。 N1077B具备可调峰值和环路带宽&#xff08;高…

第一课,idle的使用

一&#xff0c;什么是python&#xff1f; 是咱们用来和计算机“交流”、“发号施令”的编程语言。但是&#xff0c;计算机是看不懂python的&#xff0c;我们还需要一个翻译官&#xff0c;把python翻译成0和1组成的二进制&#xff0c;才能让计算机明白&#xff01; 0000001111…

四、VGA项目:联合精简帧+双fifo+sobel算法 实现VGA显示

前言&#xff1a;该项目实际上是在很多基础的小练习上合成起来的&#xff0c;例如涉及到uart&#xff08;rs232&#xff09;的数据传输、双fifo流水线操作、VGA图像显示&#xff0c;本次内容在此基础上又增添了sobel算法&#xff0c;能实现图像的边沿监测并VGA显示。 文章目录…

部署tomcat部署LNAMT

这里写目录标题 部署tomcatjava环境安装 部署LNAMT更改tomcat端口号 tomcat就是中间件之一&#xff0c;tomcat本身是一个容器&#xff0c;专门用来运行java程序&#xff0c;java语言开发的网页.jsp就应该运行于tomcat中。而tomcat本身的运行也依赖于jdk环境。 部署tomcat java…