初识《list》及手搓模拟《list》

目录

前言:

1. list的介绍及使用

list的介绍:

list的使用:

1、list的构造​编辑

2、list iterator的使用

3、list capacity

4、list element access

5、list modifiers

2.list的模拟实现

1、关于迭代器:

2、迭代器类的封装:

 3、模板为类的时候:

4、关于const迭代器:

一:而额外封装一个const迭代器。const_iterator

二:利用模板

3.vector与list的区别:

总结:


前言:

现阶段我们已经逐渐熟悉了各个STL库中的容器,对于他们的各个接口都大差不差,在我们学习完vector之后我们就可以陆陆续续接触一些算法题。我们的《好题分析》这一专栏也会不断的进行更新!下面我们先来熟悉以下list这个容器。

1. list的介绍及使用

list的介绍:

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


list的使用:

1、list的构造

2、list iterator的使用

此处,大家可暂时将迭代器理解成一个指针,该指针指向list中的某个节点。

 

注意!!

1. begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动
2. rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动

3、list capacity

4、list element access

5、list modifiers

 6、list的迭代器失效

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

void TestListIterator1()
{int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };list<int> l(array, array + sizeof(array) / sizeof(array[0]));auto it = l.begin();while (it != l.end()){// erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,// 必须先给其赋值l.erase(it);++it;}
}// 改正
void TestListIterator()
{int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };list<int> l(array, array + sizeof(array) / sizeof(array[0]));auto it = l.begin();while (it != l.end()){l.erase(it++); // it = l.erase(it);}
}

2.list的模拟实现

1、关于迭代器:

我们对list的迭代器的理解与我们之前对于string 和 vector中的iterator的理解有十分大的区别。string和vector的迭代器我们可以替换理解为指针,在我们遍历vector或者string时,仅需要对++运算符进行重载,我们就可以拿到下一个位置的值,而operator++()也很好理解,就是指针+1指向下一个下标的位置。

但是这次我们的list就大有不同,我们都知道list是一个带头的双向链表,而想要获得下一个结点的数据,应当是node = node -> next; 如果我们将运算符++重载为这个代码,那对于其它的代码想要运用++操作符,就纯粹扯淡。

这个时候的唯一解决方法就是————————封装一个类!!!

2、迭代器类的封装:

// 一个结点的结构!!!
template<class T>
struct ListNode
{T _data;ListNode<T>* _next;ListNode<T>* _prev;ListNode(const T& x = T()):_next(nullptr), _prev(nullptr), _data(x){}
};// 将迭代器封装成一个类
template<class T>
struct ListIterator
{typedef ListIterator<T> Self;typedef ListNode<T> Node;Node* _node;// iteratorListConstIterator(Node* node):_node(node){}bool operator != (const Self& it){return _node != it._node;}Self& operator++(){_node = _node->_next;return *this;}Self operator++(int){Self tmp(*this);_node = _node->_next;return *this;}Self& operator--(){_node = _node->_prev;return *this;}Self operator--(){Self tmp(*this);_node = _node->_prev;return tmp;}T& operator*(){return _node->_data;}T* operator->(){return &_node->_data;}
};

我们在之后的项目中,如果发现我们目前处理的数据中的内置类型不满足我们的需求,不妨我们可以将其封装成一个类!!!

首先我们先通过画图得方式来理解代码:


 因为这个iterator类我们并不会定义私有成员,所以我们这里用的struct来定义。

而我们在整个链表的总体类中,我们需要先找到两个 头 和 尾 结点的位置,即begin 和 end.

	iterator begin(){return iterator(_head->_next); // 匿名对象// iterator it(_head->_next); // 调用 iterator类 里面的 构造函数// return it;}iterator end(){return iterator(_head); // 匿名对象// iterator it(_head); // 调用 iterator类 里面的 构造函数// return it;}

 

 3、模板为类的时候:

想要去到_a1, 若是不进行函数重载, 则代码为:

list<A>::iterator it = ls.begin();
std::cout << it._node->_data._a1 << " " << it._node->_data._a2 << std::endl;
it++;
std::cout << it._node->_data._a1 << " " << it._node->_data._a2 << std::endl;

很明显代码非常的冗长和麻烦, 因此我们可以利用函数重载:

T* operator->()
{return &_node->_data;
}
list<A>::iterator it = ls.begin();
std::cout << it->_a1 << " " << it->_a2 << std::endl;
it++;
std::cout << it->_a1 << " " << it->_a2 << std::endl;

it.operator->()->_a1 
在这里编译器会自动优化代码,将代码的可读性提高。
it->_a1    <==>   it.operator->()->_a1     <==>      it->->_a1
通过公式推导,我们不难发现 it->_a1  <==>    it->->_a1    这两个式子是等价的

4、关于const迭代器:

const迭代器,需要的是迭代器指向的内容不能被修改而const iteratror 作返回值时,代表了迭代器的指向不可被修改。

一:而额外封装一个const迭代器。const_iterator

在我们实施这个方法后,我们会发现仅仅只有Self& operator*() 和 Self* operator->()的返回值是需要加const,其它的都不变

 //对于每一个容器来说,都有存在const的类型接口,因此我们也需要创建一个const迭代器。//(运用const主要还是 防止 在进行 拷贝构造 或者 ++ -- 等出现 修改一个 const链表的情况。)
template<class T>
struct List_const_iterator
{typedef ListNode<T> Node;typedef List_const_iterator<T> Self;Node* _node;// 最重要的一行代码,代表在 List_iterator 类中 封装一个 _node 用来指向结点,通过在类里面// 控制运算符重载来操纵迭代器List_const_iterator(Node* node) // 传值构造:_node(node){}// ++itSelf& operator++(){_node = _node->_next;return *this;}// it++Self operator++(int){Self tmp(*this); // 无需写拷贝构造函数, 默认的 浅拷贝 即可完成操作_node = _node->_next;return *this;}// *itconst T& operator*(){return _node->_data;}// it->_data,此时的 _data 是结构体时才调用const T* operator->(){return &_node->_data;}bool operator!=(const Self& it){return _node != it._node;}};

 

因此我们没必要多此一举。

二:利用模板

// 我们在创建 const_iterator 迭代器时发现在整个类中,仅仅只是对 operator*() 与 operator->() 的返回值进行了修改
// 为了尽可能的减少代码量,利用模板是一个不错的选择。
// Ref == reference    ,    Prt == pointer
//                      T&         T* 
template<class T, class Ref, class Ptr> 
//template<class T>
struct List_iterator
{typedef ListNode<T> Node;typedef List_iterator<T, Ref, Ptr> Self;Node* _node;// 最重要的一行代码,代表在 List_iterator 类中 封装一个 _node 用来指向结点,通过在类里面// 控制运算符重载来操纵迭代器List_iterator(Node* node) // 传值构造:_node(node){}// ++itSelf& operator++(){_node = _node->_next;return *this;}// it++Self operator++(int){Self tmp(*this); // 无需写拷贝构造函数, 默认的 浅拷贝 即可完成操作_node = _node->_next;return *this;}// *it 返回类型为T&Ref operator*(){return _node->_data;}// it->_data,此时的 _data 是结构体时才调用, 返回类型为T*Ptr operator->(){return &_node->_data;}bool operator!=(const Self& it){return _node != it._node;}};

 在list类中:

 template<class T>class list{public:typedef ListNode<T> Node;typedef List_iterator<T, T&, T*> iterator;typedef List_iterator<T, const T&, const T*> const_iterator;//typedef List_const_iterator<T> const_iterator;// 构造函数创建 哨兵位 的头结点

 利用模板是最高效的方法!!!

3.vector与list的区别:

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

总结:

本文的代码思路与之前大为不同,本次首先接触到了利用类封装一个迭代器,其次是对结点里的类进行分类讨论,从而引出对->运算符的重载,再然后又对const的迭代器进行了扩展,发现利用模板可以有效的解决出现的一系列问题。

代码在我的Gitee:
​​​​​​​my_list_practices_2/my_list_practices_2/list.h · 无双/C_Plus_Plus_Acer - Gitee.com

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

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

相关文章

ScriptableObject数据容器讲解

概述 是Unity提供的一个用于创建可重用的数据容器或逻辑的基类。 ScriptableObject 是继承自 UnityEngine.Object 的一个类&#xff0c;但与普通的 MonoBehaviour 不同&#xff0c;它不能附加到GameObject上作为组件。 相反&#xff0c;ScriptableObject 通常用于存储和管理…

操作系统的作用

操作系统的作用 硬件资源管理 进程管理:操作系统负责创建、调度、同步、通信和销毁进程&#xff0c;即管理多个程序的并发执行。通过进程调度算法&#xff0c;它决定哪个进程在何时获得处理器执行时间&#xff0c;实现多任务处理和资源共享&#xff0c;确保系统响应性和公平性。…

ThingsBoard实战教程(七):模拟设备遥测

tb做为一个多租户的物联网平台,文档也很齐全。后端使用的是java语言编写的,可以用swagger来导出文档。tb的所有服务都可以通过接口来看到。接口主要分为两部分,一部分是设备API,另一部分是用于服务端API。 在我们启动tb之后,可以通过http://ip +port/swagger-ui.html 来…

linux,从零安装nginx,并且部署vue应用程序

前言&#xff1a;系统使用龙蜥&#xff08;8.5&#xff09;的最小化安装&#xff0c;服务器安装这里不在赘述。 nginx 的版本&#xff1a;1.22.0 软件已经放在系统/home/software/ 一、安装nginx 进入路径/home/software/ 1》执行命令&#xff1a;rpm -ivh nginx-1.22.0-1.el7…

uboot大致流程总结

文章目录 一、uboot介绍二、uboot的配置编译过程2.1 make xxx_defconfig2.2 make 一、uboot介绍 uboot是一个bootloader&#xff0c;用于在嵌入式设备中引导linux内核启动&#xff0c;在嵌入式设备中常见的组织结构如下&#xff1a; 芯片内部固化代码 -> bootloader -> …

Docker NetWork (网络)

Docker 为什么需要网络管理 容器的网络默认与宿主机及其他容器都是相互隔离的&#xff0c;但同时我们也要考虑下面的一些问题&#xff0c; 比如 多个容器之间是如何通信的容器和宿主机是如何通信的容器和外界主机是如何通信的容器中要运行一些网络应用(如 nginx、web 应用、数…

第66天:API攻防-接口安全阿里云KEYPostmanDVWSXXE鉴权泄漏

案例一&#xff1a;安全问题-Dvws泄漏&鉴权&XXE 靶场地址&#xff1a;https://github.com/snoopysecurity/dvws-node 利用docker命令去启动 首先先注册一个账户 注册后登录点击admin area 发现点不进去 这里把bp打开但是不抓包&#xff0c;只做流量转发&#xff0c;进…

LLM学习笔记-3

温度缩放 概念 1&#xff09;在机器学习中&#xff0c;"温度缩放"通常指的是一种技术&#xff0c;用于调整神经网络输出的概率分布。这个技术通常在 softmax 函数的输出上进行操作。 2&#xff09;在 softmax 函数中&#xff0c;神经网络会输出一个概率分布&#x…

探索 虚拟化技术+Docker部署与操作

目录 一、你知道哪些云 1.1国内云 1.2国外云 二、Iaas、 Paas、SaaS三种云服务区别 2.1第一层叫做IaaS 2.2第二层就是所谓的PaaS 2.3第三层也就是所谓SaaS 三、虚拟化架构 3.1寄居架构 3.2源生架构 3.3操作系统虚拟化架构 3.4混合虚拟化架构 四、虚拟化特点及优势…

php常见图片处理方法

在PHP中&#xff0c;处理图片通常涉及对图像进行创建、打开、修改和保存等操作。以下是一些常见的PHP图片处理方法&#xff1a; 创建和打开图像 使用GD库或Imagick扩展可以创建或打开图像。 * GD库&#xff1a;imagecreate()、imagecreatefromjpeg()、imagecreatefrompng()等…

鼠标手辅助器

鼠标发生移动后 &#xff0c;静止在某位置指定时间后即可触发点击事件 支持多种点击事件&#xff0c;支持快捷键触发&#xff0c;支持自定义配置 有其他更好的思路 &#xff0c;支持有偿定制&#xff0c;留言留下联系方式&#xff0c;看到会加你 # !/usr/bin/python3 # -*- c…

服务网关GateWay基础

1. 网关基础介绍1.1 网关是什么1.2 为啥要用网关1.3 常见的网关组件NginxNetflix ZuulSpring Cloud GatewayKongAPISIX综合比较 2. gateWay的使用2.1 springCloud整合gateway2.2 GateWay的相关用法2.3 GateWay路由使用示例基本用法转发/重定向负载请求动态路由 2.5 断言(Predic…

SourceInsight中文编码格式乱码

参考文章&#xff1a;https://blog.csdn.net/m0_53754590/article/details/135594860 file–>Reload as Encoding —>选择编码格式UTF-8&#xff0c;或者GB2312&#xff1b;哪个显示不乱码设置那个&#xff1b; 我这里选择GB2312

DAY32|1005.K次取反后最大化的数组和,55. 跳跃游戏,45.跳跃游戏II

122.买卖股票的最佳时机II 文字讲解&#xff1a;买卖股票的最佳时机II 状态&#xff1a;这题ok 思路&#xff1a; 代码&#xff1a; class Solution {public int maxProfit(int[] prices) {if (prices.length 0 || prices.length 1) {return 0;}int maxProfit 0;for (int …

线程池 ThreadPoolExecutor 参数详解

一、引言 提到 Java 线程池&#xff0c;就不得不说 ThreadPoolExecutor&#xff0c;它是 Java 并发包 java.util.concurrent 中的一个类&#xff0c;提供一个高效、稳定、灵活的线程池实现&#xff0c;用于实现多线程并发执行任务&#xff0c;提高应用程序的执行效率。 在《任…

4-22 算法刷题思路总结

leetcode46 全排列 使用回溯思想 for循环遍历每次选取的数 递归遍历下一次选取的数 选取完回溯 将暂时保存的path删除尾部 将used重置为0 class Solution {List<List<Integer>> res;List<Integer> path;public List<List<Integer>> permute(in…

【ARM 裸机】C 语言 led 驱动

前面刚学习了汇编 led 驱动的编写和验证&#xff0c;现在开始就要进入 C 语言 led 驱动编写与验证了 ! 1、C 语言运行环境构建 1.1、设置处理器模式 使 6ULL 处于 SVC 模式下&#xff0c;之前已经提到了处理器的九种模式&#xff0c;参考&#xff1a;【ARM 裸机】汇编 led 驱…

Java中String为什么不可变,这样有什么好处

简介 在Java中&#xff0c;字符串&#xff08;String&#xff09;是不可变的&#xff0c;这意味着一旦创建了一个字符串对象&#xff0c;它的内容就不能再被修改。这个设计决策是由Java的创始人詹姆斯高斯林&#xff08;James Gosling&#xff09;做出的&#xff0c;主要是出于…

Docker 的基本管理

一. 云的相关知识 1. 关于云 云端服务器都有哪些提供商&#xff1a; 国内&#xff1a; 阿里云&#xff08;Alibaba Cloud&#xff09;&#xff1a; 提供ECS&#xff08;Elastic Compute Service&#xff09;弹性计算服务&#xff0c;包括通用型、计算型、内存型等多种实例…

模拟电子技术实验(九)

单选题 1. 设计性实验完整准确的定义是什么&#xff1f; A. 设计性实验是根据电路结构和元件参数测试验证系统参数。 B. 设计性实验是根据系统参数确定电路结构和元件参数。 C. 设计性实验是根据系统参数 、电路原理和验证积累确定符合系统参数的 电路结构和元件参数。 D. …