初识《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;// iterator

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

    // ++it
    Self& operator++()
    {
        _node = _node->_next;
        return *this;
    }

    // it++
    Self operator++(int)
    {
        Self tmp(*this); // 无需写拷贝构造函数, 默认的 浅拷贝 即可完成操作
        _node = _node->_next;
        return *this;
    }

    // *it
    const 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)
    {}

    // ++it
    Self& 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

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

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

相关文章

代码质量与可维护性的重要性都有哪些?

目录 一、为了提高代码质量&#xff0c;可以采取以下几种方法&#xff1a; 二、如何制定和执行有效的代码编写规范&#xff1f; 三、设计模式和设计原则在提高代码质量中的具体应用案例有哪些&#xff1f; 四、代码审查的最佳实践和技巧是什么&#xff1f; 五、如何有效地…

CV每日论文--2024.4.24

1、Guess The Unseen: Dynamic 3D Scene Reconstruction from Partial 2D Glimpses 中文标题&#xff1a;猜测未见之景&#xff1a;从部分二维片段进行动态三维场景重建 简介&#xff1a;这篇论文提出了一种方法&#xff0c;可以从单目视频输入中重建世界和多个动态人物的3D模…

猫主食罐要怎么挑?注意这些含胶的罐头!

我曾与专业的宠物医生深入交流&#xff0c;得知猫罐头的种类与选择不可一概而论。主食罐头营养搭配精细&#xff0c;旨在全面满足猫咪健康需求&#xff0c;常添加矿物质和维生素&#xff0c;并针对不同猫咪有特定配方。而零食罐头更重口感与美味&#xff0c;钠含量高&#xff0…

如何提取单片机片内程序的值进行拷贝?

对于许多单片机&#xff0c;其固件是由制造商保护的&#xff0c;并且未经授权的访问、拷贝或修改可能侵犯法律。我这里有一套嵌入式入门教程&#xff0c;不仅包含了详细的视频 讲解&#xff0c;项目实战。如果你渴望学习嵌入式&#xff0c;不妨点个关注&#xff0c;给个评论222…

跨部门协作中的沟通困境与平台建设策略——以软硬件研发为例

一、背景 在科技行业&#xff0c;跨部门合作的重要性不言而喻&#xff0c;然而实际工作中&#xff0c;经常会遭遇沟通不畅的现象。以软件与硬件研发部门为例&#xff0c;两者在产品研发过程中经常需要紧密协作&#xff0c;但却时常出现信息传递障碍。当你试图阐述观点时&#…

LangSmith帮助测试大模型系统

LangSmith是评估大模型能力好坏的评估工具,能够量化评估基于大模型的系统的效果。LangSmith通过记录langchain构建的大模型应用的中间过程,从而能够更好的调整提示词等中间过程做优化。想要使用LangSmith首先进入他的设置页面,https://smith.langchain.com/settings注册一个…

多商家AI智能名片商城系统(开源版)——构建高效数字化商业新生态

一、项目概述 1、项目背景 1&#xff09;起源 随着数字化时代的快速发展&#xff0c;传统名片和商城系统已经难以满足企业日益增长的需求。商家需要更高效、更智能的方式来展示自己的产品和服务&#xff0c;与消费者进行互动和交易。同时&#xff0c;开源技术的普及也为开发…

安卓玩机工具推荐----MTK芯片 简单制作线刷包 备份分区 备份基带 去除锁类 推荐工具操作解析

工具说明 在前面几期mtk芯片类玩机工具中解析过如何无官方固件从手机抽包 制作线刷包的步骤&#xff0c;类似的工具与操作有很多种。演示的只是本人片面的理解与一些步骤解析。mtk芯片机型抽包关键点在于..mt*****txt的分区地址段引导和 perloader临时分区引导。前面几期都是需…

在控制台实现贪吃蛇

在控制台实现贪吃蛇 前备知识Win32APICOORD这个结构体的声明如下&#xff1a;GetStdHandle 函数GetConsoleCursorInfo 函数SetConsoleCursorInfo 函数 SetConsoleCursorPosition 函数getAsyncKeyState 函数 控制台窗口的大小以及字符打印介绍控制台中的坐标宽字符及本地化介绍s…

多线程情况下IBMMQ报文丢失原因分析

背景 最近工作中&#xff0c;使用IBMMQ&#xff0c;重启服务时有偶发性的报文丢失情况&#xff0c;应用从队列中获取到了消息&#xff0c;但是线程停止没有处理。 分析 消息处理线程流程&#xff1a; 判断线程状态是否可用&#xff0c;如果不可用直接返回。使用MQQueue.get…

Seurat -- Introduction to scRNA-seq integration 跟随学习记录

文章目录 数据是如何转换的原始ifnb数据对象Splits object后的数据对象数据对象构建完成后的标准流程Normalization后的数据对象scale 后的数据对象 不同的样本进行整合JoinLayers干了什么 数据是如何转换的 seurat object 中assays R N A l a y e r s RNAlayers RNAlayersco…

卡尔曼滤波器(一):卡尔曼滤波器简介

观看MATLAB技术讲座笔记&#xff0c;该技术讲座视频来自bilibili账号&#xff1a;MATLAB中国。 一、什么是卡尔曼滤波器 卡尔曼滤波器是一种优化估计算法&#xff0c;是一种设计最优状态观测器的方法&#xff0c;其功能为&#xff1a; 估算只能被间接测量的变量&#xff1b;通…

​漏电继电器JHOK-ZBLφ150mm 0.03-3A 0.2-2S导轨安装JOSEF约瑟

系列型号&#xff1a; JHOK-ZBL多档切换式漏电&#xff08;剩余&#xff09;继电器&#xff08;导轨&#xff09; JHOK-ZBL1多档切换式漏电&#xff08;剩余&#xff09;继电器 JHOK-ZBL2多档切换式漏电&#xff08;剩余&#xff09;继电器 JHOK-ZBM多档切换式漏电&#xff08;…

深入理解分布式事务① ---->分布式事务基础(四大特性、五大类型、本地事务、MySQL并发事务问题、MySQL事务隔离级别命令设置)详解

目录 深入理解分布式事务① ---->分布式事务基础&#xff08;四大特性、五大类型、本地事务、MySQL并发事务问题、MySQL事务隔离级别命令设置&#xff09;详解事务的基本概念1、什么是事务&#xff1f;2、事务的四大特性2-1&#xff1a;原子性&#xff08;Atomic&#xff09…

STM32点灯大师(中断法)

一、使用CubeMX配置 新增加了RCC进行配置 二、代码 需要重写虚函数&#xff0c;给自己引用

Python打怪升级(4)

在计算机领域常常有说"合法"和"非法"指的是:是否合理&#xff0c;是否有效&#xff0c;并不是指触犯了法律。 random.randint(begin,end) 详细讲解一下这个random是指模板&#xff0c;也就是别人写好的代码直接来用&#xff0c;在Python当中&#xff0c;…

《R语言与农业数据统计分析及建模》学习——ggplot2绘图基础

一、农业科研数据可视化常用图形及用途 1、数据可视化的重要性 通过可视化&#xff0c;我们可以更直观地理解和分析数据的特征和趋势。 2、常用图表类型及其概述 散点图&#xff1a;用于展示两个变量之间的关系&#xff0c;可用于观察数据的分布、趋势和异常值。 折线图&…

网络安全之CSRFSSRF漏洞(上篇)(技术进阶)

目录 一&#xff0c;CSRF篇 二&#xff0c;认识什么是CSRF 三&#xff0c;实现CSRF攻击的前提 四&#xff0c;实战演练 【1】案例1 【2】案例2 【3】案例3 【4】案例4&#xff08;metinfo&#xff09; 一&#xff0c;CSRF篇 二&#xff0c;认识什么是CSRF CSRF&#x…

YesPMP众包平台最新项目

YesPMP一站式互联网众包平台&#xff0c;最新外包项目&#xff0c;有感兴趣的用户可进入平台参与竞标。 &#xff08;竞标后由项目方直接与服务商联系&#xff0c;双方直接对接&#xff09; 1.查看项目&#xff1a;个人技术-YesPMP平台 2.查看项目&#xff1…

【003_音频开发_基础篇_Linux进程通信(20种你了解几种?)】

003_音频开发_基础篇_Linux进程通信&#xff08;20种你了解几种&#xff1f;) 文章目录 003_音频开发_基础篇_Linux进程通信&#xff08;20种你了解几种&#xff1f;)创作背景Linux 进程通信类型fork() 函数fork() 输出 2 次fork() 输出 8 次fork() 返回值fork() 创建子进程 方…