【STL】list类的讲解及模拟实现

news2024/10/5 21:20:28

🪐🪐🪐欢迎来到程序员餐厅💫💫💫

         今日主菜:vector类

         主厨:邪王真眼

          所属专栏:c++专栏

          主厨的主页:Chef‘s blog

总用光环在陨落,总有新星在闪烁


【本节目标】

1. 节点

2.迭代器

3.list

4. listvector的对比

前言:

这次的list类较vector和string会更复杂,因此我们将他拆分为节点的类,迭代器的类,以及list类主体三点进行讲解。

一. list的介绍

  • 1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
  • 2. list的底层是双向带头循环链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
  • 3. listforward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。
  • 4. 与其他的序列式容器相比(arrayvectordeque)list通常在任意位置进行插入、移除元素的执行效率更好。
  • 5. 与其他序列式容器相比,listforward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素

二.节点

要点须知:

  1. 使用struct,标明公有属性(这样从外部调用比较方便)
  2. list是带头双向循环链表
template<class T>
struct ListNode//驼峰式命名
{
	ListNode<T>* _prev;
	ListNode<T>* _last;
	T _val;
	ListNode(const T &val=T())//缺省参数
		:_prev(nullptr)
	    ,_last(nullptr)
		,_val(val)
	{}
};

二、迭代器

由于list的每个结点物理空间不连续,导致迭代器不能像之前string、vector那样简单的设计为指针,而是设计为一个类(进行封装),以此完成*、->、++、–-等一系列操作。

2.1 成员变量与默认构造函数

注意事项:

仍然使用struct,标明公有属性成员变量是一个结点的指针

template <class T,class Ref,class Ptr>
struct ListIterator
{
	typedef ListNode<T> Node;//为了方便使用节点类,我们给它重命名一下
	typedef ListIterator<T, Ref,  Ptr>self;//原理同上
	ListIterator(Node* ptr)
		:Node(ptr)
	{}

	Node* _Node;
};

我知道你现在很疑惑为什么模版要用到三个参数,别急,等会讲解。

这里的迭代器的实际不可谓不妙,后面你就会称赞它的

2.2 operator*

Ref operator*()
{
	return _Node->_val;
}

解释:对比一下以前的代码

不难看出我们常常要对const和非const对象进行分类重载函数以满足他们的不同需求,但是硬要说这里的operator*重载也就传参的返回值类I型不同,而且由于权限的放大缩小规则,传参也可以写成一样的,那就只有返回值不同了,于是直接对他进行模板泛型编程,直接设计参数Ref,然后把它当作返回值,就可以达到重载的效果了。
这里的返回值是数据val类型的引用,分为const和非const

2.3 operator->

Ptr operator->()
{
	return &_Node->val;
 }

同样的的道理,这就是为什么迭代器的模板参数有三个。

这里的返回值是数据val类型的指针,分为const和非const

2.4 operator++

注意事项:

  1. 为了区分前置和后置,后置参数加上int(无实际意义,以示区分)
  2. 前置传引用返回,后置传值返回
self& operator++()
{
	_Node = _Node->_last;
	return _Node;
}

self operator++(int)
{
	Node* tmp = _Node;
	_Node = _Node->last;
	return tmp;
}

2.5 operator- -

与++同理

self operator--(int)
{

	Node* tmp = _Node;
	_Node = _Node->_prev;
	return tmp;
}
self operator--()
{
	_Node = _Node->_prev;
	return _Node;
}

2.6operator==

bool operator==(self& s)
{
	return _Node == s._Node;
}

2.6operator!=

	bool operator!=(self& s)
	{
		return _Node != s._Node;
	}

三、list

3.1 成员变量

注意事项:

head是哨兵位

template<class T>
class List
{
public:
	typedef ListNode Node;
private:
	Node* _head;
};

3.2对迭代器的处理

typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&,const T*> const_iterator;
【注意】
  • 1. beginend为正向迭代器,对迭代器执行++操作,迭代器向后移动
  • 2. rbegin(end)rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动我们现在只实现begin和end

3.2.1begin()

注意事项:

  • 1.返回值类型要强制类型转换
  • 2.返回的是_head的下一个,不是_head,_head是哨兵位
iterator begin()
{
	return iterator(_head->_last);
}
const_iterator begin()const
{
	return const_iterator(_head->_last);
}

3.2.2 end

细节:

1.返回值类型要强制类型转换

2.返回的是_head

iterator end()
{
	return iterator(_head);
}
const_iterator end()const
{
	return const_iterator(_head);
}

3.3 默认成员函数

3.3.1构造函数

(1)创建哨兵位,缺省参数
List(T val=T())
:_head(new Node)
{
	_head->_last = _head;
	_head->_prev = _head;
	_head->_val = val;
}
(2)迭代器区间构造
template <class InputIterator>
List(InputIterator first, InputIterator last)
{
	_head=new Node;
	_head->_last = _head;
	_head->_prev = _head;
	_head->_val = T();
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

push_back是尾插,这个我们之后再写,先挖个坑

3.3.2 析构函数

~List()
{
	clear();
	delete _head;
	_head = nullptr;
}

clear的作用是消除除了哨兵位之外的节点。咱们后面实现

3.3.3拷贝构造函数

摩登写法:

List(List<T>& l)
{
	_head(new Node);
	_head->_last = _head;
	_head->_prev = _head;
	_head->_val = T();
	List<T>tmp(l.begin(), l.end());
	swap(tmp);
}

3.3.4 operator=

现代写法

注意事项:

  1. 传参变成传值,这样就会拷贝构造出一个临时对象
  2. 再使用list中的swap,交换*this和tmp的值,完成赋值重载
List<T>& operator=(List<T>l)
{
	swap(l);
	return*this;
}

3.4 修改

3.4.1 insert

注意事项:

迭代器不会失效

void intsert(iterator pos,T val)//原本在这个位置的节点向后移
{
	Node* cur = pos._Node;
	Node* prev = cur->_prev;
	Node* NewNode = new Node(val);
	NewNode->_prev = prev;
	prev->_last = NewNode;
	NewNode->_last = cur;
	cur->_prev = NewNode;
}

3.4.2 push_front

头插

void push_front(T val)
{
	Node* NewNode = new Node(val);
	Node* last = _head->_last;
	NewNode->_prev = _head;
	NewNode->_last = last;
	last->_prev = NewNode;
	_head->_last = NewNode;
}

3.4.3 push_back

尾插

void  push_back(T val)
{
	Node* NewNode = new Node(val);
	Node* tail = _head->_prev;
	tail->_last = NewNode;
	NewNode->_prev = tail;
	NewNode->_last = _head;
	_head->_prev = NewNode;
}

3.4.4 erase

指定位置删除

注意事项:

  1. assert断言,防止删除哨兵位
  2. 返回删除节点的下一位,防止迭代器失效
iterator erase(iterator pos)
{
assert(pos!=end());
	Node* cur = pos._Node;
	Node* prev = cur->_prev;
	Node* last = cur->_last;
	prev->_last =last;
	last->_prev = prev;
	delete cur;
	return iterator(last);
}

3.4.5 pop_front

void  pop_front()
{
	erase(begin());
}

3.4.6 pop_back

void pop_back()
{
	erase(--end());
}

3.4.7 clear

清除所有结点(除哨兵位以外)

void clear()
{
	Node* New = _head->_last;
	while (New != _head)
	{
		Node* News = New->_last;
		delete New;
		New = News;
	}
}

3.4.8 swap

交换两个list类的值

注意事项:

使用std库中的swap函数,要带有std::,不然会被认成你所写的swap,进而导致无限递归。

void swap(List<T>&l)
{
	std::swap(_head,l._head);
}

3.4.9list的迭代器失效

前面说过,此处大家可将迭代器暂时理解成类似于指针, 迭代器失效即迭代器所指向的节点的无效,即该节 点被删除了 。因为 list 的底层结构为带头结点的双向循环链表 ,因此 list 中进行插入时是不会导致 list 的迭代 器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响

4. listvector的对比

vector list 都是 STL 中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同,其主要不同如下:

我的模拟源码仅供参考,第一次写,有错误欢迎在评论区指出

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<assert.h>
using namespace std;
template<class T>
struct ListNode//驼峰式命名
{
	ListNode<T>* _prev;
	ListNode<T>* _last;
	T _val;
	ListNode(const T &val=T())//缺省参数
		:_prev(nullptr)
	    ,_last(nullptr)
		,_val(val)
	{}
};

template <class T,class Ref,class Ptr>
struct ListIterator
{
	typedef ListNode<T> Node;//为了方便使用节点类,我们给它重命名一下
	typedef ListIterator<T, Ref,  Ptr>self;//原理同上
	ListIterator(Node* ptr)
		:_Node(ptr)
	{}
	Ref operator*()
	{
		return _Node->_val;
	}

	Ptr operator->()
	{
		return &_Node->val;
	 }
	self& operator++()
	{
		_Node = _Node->_last;
		return _Node;
	}

	self operator++(int)
	{
		Node* tmp = _Node;
		_Node = _Node->last;
		return tmp;
	}
	self operator--(int)
	{

		Node* tmp = _Node;
		_Node = _Node->_prev;
		return tmp;
	}
	self operator--()
	{
		_Node = _Node->_prev;
		return _Node;
	}
	bool operator==(self& s)
	{
		return _Node == s._Node;
	}
	bool operator!=(self& s)
	{
		return _Node != s._Node;
	}
	Node* _Node;
};
template<class T>
class List
{
public:
	typedef ListIterator<T, T&, T*> iterator;
	typedef ListIterator<T, const T&,const T*> const_iterator;
	typedef ListNode<T> Node;
	iterator begin()
	{
		return iterator(_head->_last);
	}
	const_iterator begin()const
	{
		return const_iterator(_head->_last);
	}
	iterator end()
	{
		return iterator(_head);
	}
	const_iterator end()const
	{
		return const_iterator(_head);
	}
	List(T val=T())
	:_head(new Node)
	{
		_head->_last = _head;
		_head->_prev = _head;
		_head->_val = val;
	}
	template <class InputIterator>
	List(InputIterator first, InputIterator last)
	{
		_head=new Node;
		_head->_last = _head;
		_head->_prev = _head;
		_head->_val = T();
		while (first != last)
		{
			push_back(*first);
			++first;
		}
	}

	~List()
	{
		clear();
		delete _head;
		_head = nullptr;
	}
	List(List<T>& l)
	{
		_head(new Node);
		_head->_last = _head;
		_head->_prev = _head;
		_head->_val = T();
		List<T>tmp(l.begin(), l.end());
		swap(tmp);
	}
	List<T>& operator=(List<T>l)
	{
		swap(l);
		return*this;
	}
	void intsert(iterator pos,T val)//原本在这个位置的节点向后移
	{
		Node* cur = pos._Node;
		Node* prev = cur->_prev;
		Node* NewNode = new Node(val);
		NewNode->_prev = prev;
		prev->_last = NewNode;
		NewNode->_last = cur;
		cur->_prev = NewNode;
	}
	void push_front(T val)
	{
		Node* NewNode = new Node(val);
		Node* last = _head->_last;
		NewNode->_prev = _head;
		NewNode->_last = last;
		last->_prev = NewNode;
		_head->_last = NewNode;
	}
	void  push_back(T val)
	{
		Node* NewNode = new Node(val);
		Node* tail = _head->_prev;
		tail->_last = NewNode;
		NewNode->_prev = tail;
		NewNode->_last = _head;
		_head->_prev = NewNode;
	}
	iterator erase(iterator pos)
	{
		assert(pos != end());
		Node* cur = pos._Node;
		Node* prev = cur->_prev;
		Node* last = cur->_last;
		prev->_last = last;
		last->_prev = prev;
		delete cur;
		return iterator(last);
	}
	void  pop_front()
	{
		erase(begin());
	}
	void pop_back()
	{
		erase(--end());
	}
	void clear()
	{
		Node* New = _head->_last;
		while (New != _head)
		{
			Node* News = New->_last;
			delete New;
			New = News;
		}
	}
	void swap(List<T>&l)
	{
		std::swap(_head,l._head);
	}
private:
	Node* _head;
};

 


总结

学习完list类,与之前相比,最大的收获就是迭代器的设计,同时也对多参数模板有了更深一步的了解,虽然过程艰辛,但是,List,over!

相信下一个难题也会被攻克

觉得有用的话,就点个赞支持一下吧😘😘😘

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

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

相关文章

Conda 常用命令合集

Anaconda是一个开源的Python和R语言的分布式发行版&#xff0c;用于科学计算&#xff08;数据科学、机器学习应用、大规模数据处理和预测分析&#xff09;。Anaconda旨在提供一个简单的一站式解决方案来进行科学计算的需求。它包括了许多用于科学计算、数据分析的最流行的库和工…

各种需要使用的方法-->vue/微信小程序/layui

各种需要使用的方法-->vue/微信小程序/layui 1、vue里样式不起作用的方法&#xff0c;可以通过deep穿透的方式2、 js获取本周、上周、本月、上月日期3、ArrayBuffer Blob 格式转换ArrayBuffer与Blob的区别ArrayBuffer转BlobBlob转ArrayBuffer需要借助fileReader对象 4、使用…

Java面试篇:Redis使用场景问题(缓存穿透,缓存击穿,缓存雪崩,双写一致性,Redis持久化,数据过期策略,数据淘汰策略)

目录 1.缓存穿透解决方案一:缓存空数据解决方案二&#xff1a;布隆过滤器 2.缓存击穿解决方案一:互斥锁解决方案二:设置当前key逻辑过期 3.缓存雪崩1.给不同的Key的TTL添加随机值2.利用Redis集群提高服务的可用性3.给缓存业务添加降级限流策略4.给业务添加多级缓存 4.双写一致性…

每日一练:LeeCode-21、合并两个有序链表【链表+递归+非递归】

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4] 示例 2&#xff1a; 输入&#xff1a;l1 [], l2 [] 输出&#xff1a;[…

家政服务管理平台设计与实现|SpringBoot+ Mysql+Java+ B/S结构(可运行源码+数据库+设计文档)

本项目包含可运行源码数据库LW&#xff0c;文末可获取本项目的所有资料。 推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 2024年56套包含java&#xff0c;…

阐述区块链“链游”项目3D/2D模式系统开发

随着区块链技术的不断发展&#xff0c;区块链游戏作为其应用领域之一也逐渐受到关注。在区块链游戏中&#xff0c;构建3D/2D模式系统是至关重要的&#xff0c;它决定了游戏的视觉效果、用户体验和技术实现。本文将探讨区块链游戏开发中构建3D/2D模式系统的关键要素和实现方法。…

字符驱动程序-LCD驱动开发

一、驱动程序的框架 总共分为五步&#xff1a; 1、自己设定或者系统分配一个主设备号 2、创建一个file_operations结构体 这个结构体中有操作硬件的函数&#xff0c;比如drv_open、drv_read 3、写一个注册设备驱动函数 需要register_chrdev(major,name,结构体)&#xff0…

动态多态的注意事项

大家好&#xff1a; 衷心希望各位点赞。 您的问题请留在评论区&#xff0c;我会及时回答。 多态的基本概念 多态是C面向对象三大特性之一&#xff08;多态、继承、封装&#xff09; 多态分为两类&#xff1a; 静态多态&#xff1a;函数重载和运算符重载属于静态多态&#x…

【嵌入式——QT】多语言界面

【嵌入式——QT】多语言界面 多语言页面开发步骤tr()函数 多语言页面开发步骤 第一步 在你编写的代码中添加tr()函数&#xff0c;方便之后可以精准的定位到你所需要翻译的部分。 第二步 在.pro文件中添加以下代码&#xff0c;这样会让你生成相应的.ts文件&#xff0c;ts文件是…

关于RPC

初识RPC RPC VS REST HTTP Dubbo Dubbo 特性&#xff1a; 基于接口动态代理的远程方法调用 Dubbo对开发者屏蔽了底层的调用细节&#xff0c;在实际代码中调用远程服务就像调用一个本地接口类一样方便。这个功能和Fegin很类似&#xff0c;但是Dubbo用起来比Fegin还要简单很多&a…

xcode生成静态库.a

一、生成静态库 1.打开 Xcode 创建一个新的 Static Library 工程&#xff0c;取名applestudio 2.创建工程完毕后&#xff0c;简化目录结构 删除系统自动创建的同名类&#xff1a;applestudio.h和applestudio.m 把自己的代码复制进去&#xff0c;如例子&#xff1a;guiconnect.h…

RSTP环路避免实验(华为)

思科设备参考&#xff1a;RSTP环路避免实验&#xff08;思科&#xff09; 一&#xff0c;技术简介 RSTP (Rapid Spanning Tree Protocol) 是从STP发展而来 • RSTP标准版本为IEEE802.1w • RSTP具备STP的所有功能&#xff0c;可以兼容STP运行 • RSTP和STP有所不同 减少了…

警务数据仓库的实现

目录 一、SQL Server 2008 R2&#xff08;一&#xff09;SQL Server 的服务功能&#xff08;二&#xff09;SQL Server Management Studio&#xff08;三&#xff09;Microsoft Visual Studio 二、创建集成服务项目三、配置“旅馆_ETL”数据流任务四、配置“人员_ETL”数据流任…

数据结构·二叉树(1)

目录 1 树的概念及结构 1.1 树的结构 1.2 树的概念 1.3树的表示 2 二叉树的概念及结构 2.1二叉树的概念 2.2 特殊的二叉树 2.3 二叉树的存储结构 1 树的概念及结构 1.1 树的结构 前面所学到的顺序表链表等&#xff0c;都是线性的数据结构&#xff0c;今天介绍的树&am…

Android Native Crash奔溃

一.Native Crash 简介 从 Android 系统全局来说&#xff0c;Crash 通常分为 App/Framework Crash&#xff0c;Native Crash&#xff0c;以及 Kernel Crash。 对于 App 层或者 Framework 层的 Crash(即 Java 层面 Crash)&#xff0c;那么往往是通过抛出未捕获异常而导致的 Cras…

FPGA电平标准

1.LVTTL&#xff1a;&#xff08;3.3v&#xff09; 2.LVCOMS&#xff1a;&#xff08;1.8v&#xff09; 3.LVDS&#xff08;1.8v&#xff09;&#xff1a;LVDS_25&#xff08;2.5v&#xff09; 4&#xff1a;如果是ddr3与fpga相连接fpga的vcco推荐&#xff08;1.5v&#xff09;…

flask_restful的基本使用

优势&#xff1a; Flask-Restful 是一个专门用来写 restful api 的一个插件。 使用它可以快速的集成restful api 接口功能。 在系统的纯api 的后台中&#xff0c;这个插件可以帮助我们节省很多时间。 缺点&#xff1a; 如果在普通的网站中&#xff0c;这个插件就没有优势了&…

【SQL】1517. 查找拥有有效邮箱的用户(正则表达式regexp)

前述 sql-正则表达式SQL学习笔记 – REGEXP 题目描述 leetcode 题目&#xff1a;1517. 查找拥有有效邮箱的用户 Code select * from Users where mail regexp ^[a-zA-Z][a-zA-Z0-9_.-]*leetcode\\.com$图片引用自 MySQL正则表达式

后端常问面经之操作系统

请简要描述线程与进程的关系,区别及优缺点&#xff1f; 本质区别&#xff1a;进程是操作系统资源分配的基本单位&#xff0c;而线程是任务调度和执行的基本单位 在开销方面&#xff1a;每个进程都有独立的代码和数据空间&#xff08;程序上下文&#xff09;&#xff0c;程序之…

flask_restful规范返回值

使用方法 导入 flask_restful.marshal_with 装饰器 定义一个字典变量来指定需要返回的标准化字段&#xff0c;以及该字段的数据类型 在请求方法中&#xff0c;返回自定义对象的时候&#xff0c; flask_restful 会自动的读 取对象模型上的所有属性。 组装成一个符合标准化参…