list的常用接口底层实现与介绍

目录

概念:

list的基本结构:

list的迭代器⭐❤: 

自定义类型的完善: 

const的迭代器:

insert 

erase:

size 

empty 

push_back 、push_front 、pop_back、pop_front 

swap 、operator=

析构函数、clear() 


 

概念:
  1. list是序列容器,允许在序列中的任何位置执行固定O(1)时间复杂度的插入和删除操作,并在两个方向进行迭代。
  2. list容器使用双链表实现;双链表将每个元素存储在不同的位置,每个节点通过next,prev指针链接成顺序表。
  3. list与其他标准序列容器(array,vector和deque)相比,list通常可以在容器内的任何位置插入、提取和移动元素。
  4. list与其他标准序列容器(array,vector和deque)相比,list和forward_list(单链表实现)的主要缺点是他们不能通过位置直接访问元素;例如,要访问列表中的第五个元素,必须从已知位置(开始或结束)迭代到该位置,需要哦线性时间开销。
  5. 存储密度低,list要使用一些额外的内容空间(next,prev)来保持与每个元素相关联(前后续的线性)的链接信息,从而导致存储小元素类型(如char,short,int等)的列表的存储密度低。

list的基本结构:
#include<iostream>
#include<assert.h>
using namespace std;
namespace bit {
	template <class T>
	struct ListNode
	{
		ListNode<T>* _next;
		ListNode<T>* _prev;
		T _data;
		ListNode(const T& x = T())
			:_next(nullptr)
			,_prev(nullptr)
			,_data(x)
		{}
	};
	template<class T>
	class list {
		typedef ListNode<T> Node;
	public:
		list()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			_size = 0;
		}
	private:
		Node* _head;
		size_t _size;
	};
}

通过list的概念我们可以得知,list在本质上就是一种带头的双向链表结构,所以在创建list类之前,我们首先要构建一个节点的结构体,其次在list的构造函数中,为了让list遵循它本质上的数据结构,以及通过之后的插入删除等等操作,利用带头双向链表结构的特点,我们将成员变量特地设置为头节点以及长度。

list的迭代器⭐❤: 

list的迭代器特殊之处: 

  • 迭代器的本质就是不管是什么类型的数据都可以进行访问!所以该如何使用迭代器遍历链表内的数据呢?是否能使用前驱、后继指针充当迭代器?答案是不能!
  • 如果使用前驱后继指针充当迭代器,那么就需要遇到一个问题,链表的各个节点是不同位置不同地址的!
  • 所以如果使用指针+1的方式并不能正确的遍历链表!
  • 其次,如果使用前驱后继指针当迭代器,那么解引用能得到节点内的数据吗?不能!因为Node * 解引用后上一个Node 是一个strcut结构体

所以对于list的迭代器,正确的解决方案是另外在设置一个类对迭代器进行分装操作! 

	template<class T>
	struct ListIterator
	{
		typedef ListNode<T> Node;
		typedef ListIterator<T> self;
		Node* _node;
		ListIterator(Node*node)
			:_node(node)
		{}
	};

在原先的原身指针 prev和next下,并不能解决迭代器遍历操作,以及解引用操作,因为节点的空间问题并不能使用指针+1的操作,以及解引用prev和next并不能直接获取到节点内部存储的数值元素,所以我们需要在这个封装操作中进行。

至于这个封装为什么是结构体呢?

在C++中,struct和class是非常相似的,唯一的区别是默认的访问权限不同。在struct中,成员默认是公共的(public),而在class中,默认是私有的(private)。因此,在C++中,struct内部是可以定义函数的。 

在这个结构体的内部我们需要解决迭代器的遍历、解引用、遍历修改、不等于 等运算符重载问题! 

		//*
		T& operator*()
		{
			return _node->_data;
		}
		//前置++ 因为这里是链表所以++应该是指向下一个节点!
		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;
		}
		slef& operator--(int)
		{
			self tmp(*this);
			_node = _node->_prev;
			return tmp;
		}
		bool operator!=(const self& it)
		{
			return _node != it._node;
		}

我们定义这些重载运算符的意义就在于为了之后我们在定义begin时,可以直接使用封装的重载运算符!

		typedef ListIterator<T> iteator;
		iterator begin()
		{
			iterator it(_head->_next);
			return it;
		}
		iterator end()
		{
			iterator it(_head);
			return it;
		}

自定义类型的完善: 

自定义类型的完善 ,因为我们写的是一个自定义类型,所以读取*it后解决完后还是一个自定义类型的数据结构,所以需要在使用数据结构的写法,表明其中内部的数据即可!

 

		T* operator->()
		{
			return &_node->_data;
		}

it->是需要进行拆解的,可以变成it.operator()->,这里其实是编译器的一种隐藏操作,之前的it++也是一种隐藏操作,可以变化为it.operator++()

而it->_a1这里隐藏的是另一个-> ,也就是图中第二个->,第一个->是属于it的重载运算符标识!

const的迭代器:

首先要记住const不能调用非const的成员函数!但是非const可以调用const的成员函数

为了让const修饰的类型能够使用迭代器,我们需要根据const不能修改内容的特点进行另外设置一共专门让const使用的迭代器封装,但是由于只是需要变动operator*()和operator->所以我们可以使用模板类解决这类问题! 

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)
		{}

		// *it
		//T& operator*()
		Ref operator*()
		{
			return _node->_data;
		}

		// it->
		//T* operator->()
		Ptr operator->()
		{
			return &_node->_data;
		}

		// ++it
		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;
		}
	};
insert 

 有了迭代器之后了,可以利用迭代器的操作进行insert的底层实现

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

			// prev newnode cur;
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;
			_size++;
		}

erase:

操作和insert一样都是对指针的方向的修改!但是pos会在后面失效,也就是迭代器会失效,所以需要改变类型!然后进行返回!这里的返回变成了的pos节点的后面一个节点的迭代器。

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

			return iterator(next);
		}
size 
        size_t size() const
		{
			return _size;
		}
empty 
        bool empty()
		{
			return _size == 0;
		}
push_back 、push_front 、pop_back、pop_front 
        void push_back(const T& x)//尾插
		{
			insert(end(), x);
		}

		void push_front(const T& x)//头插
		{
			insert(begin(), x);
		}

		void pop_back()//尾删
		{
			erase(--end());
		}

		void pop_front()//头删
		{
			erase(begin());
		}
swap 、operator=
        void swap(list<T>& lt)
		{
			std::swap(_head, lt._head);
			std::swap(_size, lt._size);
		}

		// lt1 = lt3
		list<T>& operator=(list<T> lt)
		{
			swap(lt);
			return *this;
		}
析构函数、clear() 
		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);
			}
		}

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

 

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

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

相关文章

51单片机实验01-点亮LED小灯

目录 一&#xff0c;软件下载 二&#xff0c;单片机概述 1&#xff0c;单片机内部资源 1&#xff09;flash 2&#xff09;ram 3&#xff09;sfr 2&#xff0c;51单片机 3&#xff0c;单片机最小系统 三&#xff0c;点亮最右边的小灯 1&#xff0c;指出满足小灯点亮的有…

【Qt】:常用控件(三:按钮类)

常用控件&#xff08;三&#xff09; 一.Push Button二.Radio Buttion三.Check Box 一.Push Button 使⽤ QPushButton 表⽰⼀个按钮.这也是当前我们最熟悉的⼀个控件了.QPushButton继承⾃QAbstractButton .这个类是⼀个抽象类.是其他按钮的⽗类. QAbstractButton 中,和 QPushBu…

非关系型数据库-----------探索Redis支持五种数据类型

目录 一、Redis支持五种数据类型 1.String&#xff08;字符串&#xff09; 2.Hash&#xff08;哈希&#xff09; 3.List&#xff08;列表&#xff09; 4.Set&#xff08;集合&#xff09; 5.sorted set(有序集合) 二、Redis的字符串类型string 1、 SET/GET/APPEND/STRL…

通用分布式锁组件

通用分布式锁组件 1 Redisson1.1介绍1.2 为什么要使用Redisson实现分布式锁1.2.1 锁续期的问题1.2.2 获取锁尝试的问题1.2.3 可重入问题 1.3 Wath Dog的自动延期机制1.4 快速了解1.5 项目集成 2 定义通用分布式锁组件2.1 实现思路分析2.2 定义注解2.3 定义切面2.4 使用锁2.5.工…

【面试经典150 | 动态规划】不同路径 II

文章目录 写在前面Tag题目1方法一&#xff1a;动态规划方法二&#xff1a;空间优化 题目2方法一&#xff1a;动态规划空间优化 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法&#xff0c;两到三天更新一篇文章&#xff0c;欢迎催更…… 专栏内容以分析题目为主…

Transformer学习: Transformer小模块学习--位置编码,多头自注意力,掩码矩阵

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 Transformer学习 1 位置编码模块1.1 PE代码1.2 测试PE1.3 原文代码 2 多头自注意力模块2.1 多头自注意力代码2.2 测试多头注意力 3 未来序列掩码矩阵3.1 代码3.2 测试掩码 1 …

【Java设计模式】序:设计模式总体概述

目录 什么是设计模式设计模式的分类1 创建型模式1.1. 单例&#xff08;Singleton&#xff09;1.2 原型&#xff08;Prototype&#xff09;1.3 工厂方法&#xff08;FactoryMethod&#xff09;1.4 抽象工厂&#xff08;AbstractFactory&#xff09;1.5 建造者&#xff08;Builde…

ajax教程

文章目录 一、原生ajax1、AJAX 简介2、特点1&#xff09;优点2&#xff09;缺点 二、http协议1、概念2、Cookie和Session机制1&#xff09;Cookie2&#xff09;Session3&#xff09;报文 二、请求头1、概念2、常见请求头&#xff1a;3、Content-Type 三、AJAX使用1、详细操作2、…

竞赛 Yolov安全帽佩戴检测 危险区域进入检测 - 深度学习 opencv

1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; Yolov安全帽佩戴检测 危险区域进入检测 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;3分工作量&#xff1a;3分创新点&#xff1a;4分 该项目较为新颖&am…

代码随想录阅读笔记-二叉树【二叉搜索树中的搜索】

题目 给定二叉搜索树&#xff08;BST&#xff09;的根节点和一个值。 你需要在BST中找到节点值等于给定值的节点。 返回以该节点为根的子树。 如果节点不存在&#xff0c;则返回 NULL。 例如&#xff0c; 在上述示例中&#xff0c;如果要找的值是 5&#xff0c;但因为没有节点…

Sybase ASE中的char(N)的坑以及与PostgreSQL的对比

1背景 昨天,一朋友向我咨询Sybase ASE中定长字符串类型的行为,说他们的客户反映,同样的char类型的数据,通过jdbc来查,Sybase库不会带空格,而PostgreSQL会带。是不是这样的?他是PostgreSQL的专业大拿,但因为他手头没有现成的Sybase ASE环境,刚好我手上有,便于一试。 …

PostgreSQL 表膨胀原因和解决方案

在 PostgreSQL 中&#xff0c;表膨胀是一个常见的问题&#xff0c;它会导致数据库性能下降&#xff0c;甚至在极端情况下会耗尽磁盘空间。了解表膨胀的原因及其解决方案&#xff0c;对于维护数据库性能和稳定性至关重要。 表膨胀的原因 MVCC (多版本并发控制) PostgreSQL 使…

Kotlin:for循环的几种示例

一、 打印 0 到 2 1.1 方式一&#xff1a;0 until 3 /*** 打印 0 到 2*/ fun print0To2M1(){for (inex in 0 until 3){// 不包含3print("$inex ")} }运行结果 1.2 方式二&#xff1a;inex in 0 …2 /*** 打印 0 到 2*/ fun print0To2M2(){for (inex in 0 ..2){//…

A First Course in the Finite Element Method【Daryl L.】|PDF电子书

专栏导读 作者简介&#xff1a;工学博士&#xff0c;高级工程师&#xff0c;专注于工业软件算法研究本文已收录于专栏&#xff1a;《有限元编程从入门到精通》本专栏旨在提供 1.以案例的形式讲解各类有限元问题的程序实现&#xff0c;并提供所有案例完整源码&#xff1b;2.单元…

MySQL-逻辑架构:逻辑架构分析、SQL执行流程、数据库缓冲池

逻辑架构 1. 逻辑架构剖析 1.1 第1层&#xff1a;连接层 系统&#xff08;客户端&#xff09;访问MySQL服务器前&#xff0c;做的第一件事就是建立TCP连接。 经过三次握手建立连接成功后&#xff0c;MySQL服务器对TCP传输过来的账号密码做身份认证、权限获取。 用户名或密码…

【go】模板展示不同k8s命名空间的deployment

gin模板展示k8s命名空间的资源 这里学习如何在前端单页面&#xff0c;调用后端接口展示k8s的资源 技术栈 后端 -> go -> gin -> gin模板前端 -> gin模板 -> html jsk8s -> k8s-go-client &#xff0c;基本资源(deployment等) 环境 go 1.19k8s 1.23go m…

Windows程序设计课程作业-1

文章目录 1. 作业内容2. 设计思路分析与难点3. 代码实现3.1 接口定义3.2 工厂类实现3.3 委托和事件3.4 主函数3.5 代码运行结果 4. 代码地址5. 总结&改进思路6. 阅读参考 1. 作业内容 使用 C# 编码&#xff08;涉及类、接口、委托等关键知识点&#xff09;&#xff0c;实现…

nssm 工具把asp.net core mvc变成 windows服务,使用nginx反向代理访问

nssm工具的作用&#xff1a;把项目部署成Windows服务&#xff0c;可以在系统后台运行 1.创建一个asp.net core mvc的项目weblication1 asp.net core mvc项目要成为windows服务需要安装下面的nuget包 <ItemGroup><PackageReference Include"Microsoft.Extension…

Python卷积网络车牌识别系统(V2.0)

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

【随笔】Git 高级篇 -- 相对引用1(十二)

&#x1f48c; 所属专栏&#xff1a;【Git】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#x1f496; 欢迎大…