迭代器并不全是指针,list的迭代器与vector和string的有什么不一样,让博主告诉你其底层原理!

news/2024/5/4 5:29:20/文章来源:https://blog.csdn.net/m0_64361907/article/details/127126989

链表的模拟实现

在这里插入图片描述

文章目录

    • 链表的模拟实现
      • 一、list的基本架构🤖
        • _list_node
        • 基本构架--双向带头循环链表
      • 二、list的迭代器--重点🐱‍👤
        • list迭代器的基本架构
        • 构造函数--node*封装
        • operator*()--得到值
        • operator!=()--跟另一个迭代器进行比较
        • operator++()与operator++(int)
        • operator--()与operator--(int)
        • operator->()
        • 迭代器与常量迭代器的完整版
      • 三、实现list的重要接口🙊
        • 构造函数--构建哨兵位
        • begin(),end()
      • 增👹
        • insert--随机插--在pos的前面插入
        • push_back()--尾插
        • push_front()--头插
      • 删🐱‍🚀
        • erase--随机删
        • pop_back()--尾删
        • pop_front()--头删
        • 查改--使用迭代器即可
      • empty()--判断是否为空,可有迭代器
      • size()--返回元素个数--使用迭代器遍历
      • 析构函数~list()
        • clear()
        • ~list()
      • 拷贝构造
      • 赋值运算符重载
        • 传统写法
        • 现代写法
      • 四、总结一下list接口🤡

一、list的基本架构🤖

​ 首先list是很多不同空间上的结点,再将其连接起来的一个结构。为了我们再list类中可以很好地使用结点,所以我们将结点也设计成一个类,一个公开的类。

因为我们要设计成一个公开的类,所以我们使用struct来设计类。

_list_node

template<class T>
struct _list_node
{_list_node(const T& val = T()):_val(val), _prev(nullptr), _next(nullptr){}T _val;_list_node<T>* _prev;//模板这个也是需要加T的_list_node<T>* _next;
};

​ 将结点设计成类,结点的构造函数可以轻松搞定我们以前c语言版本的buynode()功能!

我们稍微解释一下,为什么上述val的缺省参数为什么写成T(),在c++中,其实认为不仅仅是自定义类型有构造函数,像int,double这种内置类型也有他们对应的构造函数。如果val的缺省参数写成0,是不准确的,因为T有可能是string类型,T()就可以解决这种问题,无论是什么类型,都能给出合理的缺省参数。


基本构架–双向带头循环链表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JVGvCpIi-1664530094117)(C:\Users\Cherish\AppData\Roaming\Typora\typora-user-images\image-20220930161139832.png)]

​ 那么list的框架我们可以大致确定下来。

template<class T>
class list()
{typedef _list_node<T> node;//重命名之后写listnode类型就很方便public://…………成员函数private:node* _head;}

​ 每个结点之间的连接关系,由list里面的成员函数来处理,所以list的成员数据是要由一个_head头节点即可。


二、list的迭代器–重点🐱‍👤

​ 我们学习了stringvectorstring的数据是char,它的迭代器是char*vector的数据是T,它的迭代器是T*,都是数据的地址。在c语言中,就支持地址的++,–,==,!=。

​ 而list的数据是很多的node,按道理来说它的迭代器应该是node*,可是自定义类型的地址不支持++,–,==,!=的,所以要想使node*也能像char*,T*那样操作,我们要将其进行封装,设计成类,然后再设计运算符重载,这样子可以像内置类型一样进行操作了。

list迭代器的基本架构


struct _list_iterator
{typedef _list_node<T> node;//为了方便使用结点类typedef _list_iterator self;//成员函数…………node* _pnode;};

​ 一般在一个类中要使用另一个类,那个这个类使类模板的话,通常会typedef,不然每次使用都要加上模板,很不方便

构造函数–node*封装

_list_iterator(node* pnode):_pnode(pnode)
{}

operator*()–得到值

T& operator*()
{return _pnode->_val;
}

operator!=()–跟另一个迭代器进行比较

bool operator!=(const self& s) const
{return (_pnode != s._pnode);//比较他们的node*地址即可
}

operator++()与operator++(int)

self& operator++()
{_pnode = _pnode->_next;return *this;
}self& operator++(int)//编译器默认使后置++
{self tmp(*this);_pnode = _pnode->_next;return tmp;
}

​ ++之后,变成下一个结点,通过结点的next即可,达到需求。

operator–()与operator–(int)

self& operator++()
{_pnode = _pnode->_next;return *this;
}self& operator++(int)
{self tmp(*this);_pnode = _pnode->_next;return tmp;
}

operator->()

​ 有些同学同能会很异或,operator->是什么意思,当node里面的数据是自定义类型时,自定义类型的地址是支持->的用法的

T* operator->()
{return &_pnode->_val;//返回自定义类型的地址
}

​ 返回数据的地址,这样就可以使用->了

其实这里编译器进行了一层优化,导致这个地方不太好理解,例如我用list创建了一个对象l, l->会去调用operator->得到地址,得到地址再

->才可以得到自定义类型的内容。

所以正确写法应该是l->->,但是这样使得可读性非常差,所以编译器优化成了一个->


​ 那么迭代器的的基本接口就实现完成了,但是我们要实现常量带迭代器怎么办?常规来说,是不是也要设计成一个自定义类型对不对。但是我们思考一下,常量迭代器与迭代器很多逻辑都是一致的,不同的地方是:operator*的返回值是const T&,operator->返回的是const T*,其他地方都是相同的所以,我们在写模板的时候就要这样子写了。

迭代器与常量迭代器的完整版

template<class T,class Ref,class Ptr>
struct _list_iterator
{typedef _list_node<T> node;typedef _list_iterator self;_list_iterator(node* pnode):_pnode(pnode){}Ref operator*()//T&/const T&{return _pnode->_val;}bool operator!=(const self& s) const{return (_pnode != s._pnode);}self& operator--(){_pnode = _pnode->_prev;return *this;}self& operator--(int){self tmp(*this);_pnode = _pnode->_prev;return tmp;}self& operator++(){_pnode = _pnode->_next;return *this;}self& operator++(int){self tmp(*this);_pnode = _pnode->_next;return tmp;}Ptr operator->()//T*/const T*{return &_pnode->_val;}node* _pnode;};

​ 模板类型Ref用来解决operator*的问题,模板类型Ptr用来解决operator->的问题。这样子就很好地解决了代码冗余。

三、实现list的重要接口🙊

​ 从前有一个这样的问题说:你是否能在15分钟之内,将链表的增删查改的给写成来。遇到这种情况你会怎么办?

当然了这个问题肯定不会是让你将list的代码实现背下来,然后疯狂敲,在15分钟之内敲完。那样就纯纯的码农了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Md9KWljk-1664530094120)(D:\gitee仓库\博客使用的表情包\u_1145001478_3378656280&fm_253&fmt_auto&app_138&f_JPEG.png)]

这个问题是想看你的list实现时的代码复用。

我们再把list的架构给总结一遍

template<class T>class list{typedef _list_node<T> node;public:typedef _list_iterator<T,T&,T*> iterator;typedef _list_iterator<T, const T&,const T*> const_iterator;private:node* _head;
}

构造函数–构建哨兵位

list()//list的构造是构造头节点,list初始化
:_head(nullptr)
{_head = new node(T());_head->_next = _head;_head->_prev = _head;
}

begin(),end()

iterator begin()
{return iterator(_head->_next);
}const_iterator begin() const
{return const_iterator(_head->_next);
}iterator end()
{return iterator(_head);
}const_iterator end() const
{return const_iterator(_head);
}

const知识点,这里不做太多说明了。

注意begin()返回的是第一个结点(头节点的下一个),而end()返回的不是最后一个结点,而是头节点,这样子就满足了左闭右开

增👹

insert–随机插–在pos的前面插入

iterator insert(iterator pos,const T& val)
{assert(pos._pnode);node* cur = pos._pnode;node* prev = cur->_prev;node* newnode = new node(val);//连接关系prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return pos;
}

​ 其实就是考一个链接关系,大家画画图就可以很清晰地解决。

注意insert有返回值,大家可以思考一下链表insert还会不会使迭代器失效。不清楚迭代器失效的可以看看博主的这篇文章:

(223条消息) C++迭代器失效你真的理解了吗,看博主用vector的代码实现给你讲清楚迭代器失效以及解决方案!(超详细)_龟龟不断向前的博客-CSDN博客


push_back()–尾插

​ 这里我们就可以insert进行复用了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oyK3IEXi-1664530094121)(C:\Users\Cherish\AppData\Roaming\Typora\typora-user-images\image-20220930170745264.png)]

void push_back(const T& val)//必须加上const,否则报错
{insert(end(),val);//在头节点后面的那个节点进行头插,正好就是begin
}

push_front()–头插

​ 继续复用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WLBdxfKA-1664530094123)(C:\Users\Cherish\AppData\Roaming\Typora\typora-user-images\image-20220930170815455.png)]

void push_front(const T& val)
{insert(begin(),val);
}

删🐱‍🚀

erase–随机删

iterator erase(iterator pos){assert(pos._pnode);//判断迭代器的节点不是空节点assert(pos != end());//内容为空了,就不能再删了node* cur = pos._pnode;node* prev = cur->_prev;node* next = cur->_next;//连接关系prev->_next = next;next->_prev = prev;delete cur;return (iterator(next));}

注意erase的返回值的写法,使用iterator构造了一个匿名对象进行了返回

返回值–删除元素的下一个元素的迭代器。

pop_back()–尾删

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5rhQDSsv-1664530094125)(C:\Users\Cherish\AppData\Roaming\Typora\typora-user-images\image-20220930170536062.png)]

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

pop_front()–头删

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J5yEmNLT-1664530094127)(C:\Users\Cherish\AppData\Roaming\Typora\typora-user-images\image-20220930170857259.png)]

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

查改–使用迭代器即可

empty()–判断是否为空,可有迭代器

bool empty()
{return begin() == end();
}

size()–返回元素个数–使用迭代器遍历

size_t size()
{iterator it = begin();size_t sz = 0;while (it != end()){++it;++sz;}return sz;
}

析构函数~list()

​ 我们要清楚结点,以及头节点。那么我们可以设计一个clear将结点清掉,在析构函数里面再清掉头节点即可。

clear()

void clear()
{iterator it = begin();while (it != end()){it = erase(it);//erase里面已经有了清除功能了,所以咱们直接复用//而且erase的返回值让我们可以做到,就算节点释放了,我们也可以找到下一个节点}
}

​ 我们复用了erase,因为erase也有清理功能。而且erase的返回值可以实现,就先清掉了结点,我也找到了下一个结点。

~list()

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

拷贝构造

list(const list<T>& lt)//构造函数要传引用
{//this构建头节点_head = new node;_head->_next = _head;_head->_prev = _head;//开始赋值--用push_back复用for (auto& x : lt){push_back(x);}
}

​ 复用了迭代器(范围for的原理就是迭代器)和push_back。

思路:1.构建头节点 2.将元素尾插进去

赋值运算符重载

传统写法

list<T>& operator=(const list<T>& lt)
{if (this != &lt)//排除自己给自己赋值{//先clear()--留下一个头节点,然后赋值--用push_back()来复用clear();for (auto& x : lt){push_back(x);}}return *this;
}

​ 先受用clear清空数据,只留下一个头节点,再将数据尾插进去。

现代写法

可以写一个list自己的swap函数

void swap(list<T>& lt)//交换链表很简单,直接交换头节点即可
{::swap(_head, lt._head);
}
list<T>& operator=(list lt)
{swap(lt);return *this;
}

那么list的一些重要接口就实现完成啦。

四、总结一下list接口🤡

其实大家写完就可以发现:list的接口实现有大量的代码复用,除了迭代器比vector要复杂一点,其实他的代码实现是比vector要简单的。

比如:

  1. push_back()
  2. push_front()
  3. pop_back()
  4. pop_front()
  5. size()
  6. clear()
  7. 拷贝构造
  8. 赋值运算符重载

这些都有很多代码复用,当你实现了他们的基础,这些接口就非常简单了。

但是我们实现的list是无法配合库里面的find()进行使用的,这个等到大家c++越往后学就会理解,看了STL源码剖析就会理解。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U54sF4vI-1664530094129)(D:\gitee仓库\博客使用的表情包\给点赞吧.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b9EjinbD-1664530094130)(D:\gitee仓库\博客使用的表情包\要赞.jpg)]

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

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

相关文章

xLua热更新(一)xLua基本使用

一、什么是xLua xLua为Unity、 .Net、 Mono等C#环境增加Lua脚本编程的能力&#xff0c;借助xLua&#xff0c;这些Lua代码可以方便的和C#相互调用。 xLua是用来实现Lua代码与C#代码相互调用的插件。我们可以借助这个插件来实现热更新方案。 那么为什么要选择Lua实现热更新呢&am…

报告分享|数字化转型,从战略到执行报告

报告链接:http://tecdat.cn/?p=28672 如何加速国家、城市、行业、企业数字化进程,激发数字经济新动能。这份报告通过洞察数字化的6大改变、4大载体、4个阶段、20+场景、100+国家/项目案例/数据,全面系统性地阐述了多层次多场景数字化如何落地实施,最终带来经济、社会价值的…

报告分享|2022年企业数字化人才发展白皮书

报告链接:http://tecdat.cn/?p=28670 数字经济时代,企业对数字化人才的需求急剧增长。此报告对数字化人才培养和企业数字化人才发展现状进行梳理和研究,聚焦于金融、零售、能源和制造四个行业,采用定量与定性相结合的研究方法,对数字化人才的发展态势、岗位能力需求、培养…

第八章 常用用类

文章目录8.4 StringBuffer类8.4.1 StringBuffer对象8.4.2 StringBuffer类的常用方法1.append方法2.charAt(int n)和setCharAt(int n, char ch)8.5 Date类与Calendar类8.5.1 Date类8.5.2 Calendar类8.6 日期的格式变化8.6.1 format方法8.6.2 不同区域的星期格式8.7 Math类、BigI…

【算法】【二叉树模块】求一个二叉树“子树“是否包含另一个二叉树的全部拓扑结构

目录前言问题介绍解决方案代码编写java语言版本c语言版本c语言版本思考感悟写在最后前言 当前所有算法都使用测试用例运行过&#xff0c;但是不保证100%的测试用例&#xff0c;如果存在问题务必联系批评指正~ 在此感谢左大神让我对算法有了新的感悟认识&#xff01; 问题介绍 …

三个线程顺序打印ABC?我有十二种做法,彻底掌握多线程同步通信机制

大家好&#xff0c;我是老三&#xff0c;这篇文章分享一道非常不错的题目&#xff1a;三个线程按序打印ABC。 很多读者朋友应该都觉得这道题目不难&#xff0c;这次给大家带来十二种做法&#xff0c;一定有你没有见过的新姿势。 1. synchronizedwaitnotify 说到同步&#xf…

Swift中的内存访问冲突、指针、局部作用域

内存访问冲突&#xff08;Conflicting Access to Memory&#xff09; 1、内存访问冲突会在两个访问满足以下条件时发生&#xff1a; 至少一个是写入操作它们访问的是同一块内存它们的访问时间重叠&#xff08;比如在同一个函数内&#xff09; //无内存访问冲突 func plus(_ n…

PIE-engine 教程 ——利用NDWI加载青海湖三年水域影像和面积计算

这里我们首先画一个自己选择的研究区&#xff0c;用于方便计算NDWI&#xff0c;这里我们将青海湖区域作为我们的研究区&#xff0c;第二步我们就是要设定一个函数&#xff0c;用于在函数中执行循环遍历&#xff0c;这里包括去云和影像筛选过程&#xff0c;最后按照最大值合成&a…

Windows 10 docker 容器添加新端口映射的方法与步骤

在Docker容器已经创建后&#xff0c;需要添加新的端口映射&#xff0c;即对已经存在的Docker容器添加新的端口映射&#xff0c;可以通过以下步骤来添加&#xff0c;即通过修改配置文件的方法。 1、Windows 10 下 Dockers容器的配置文件存在的路径为&#xff1a; 笔者本文是20…

CLIP扩展

Audio CLIP:Extend CLIP to Image,Text and Audio&#xff08;语音&#xff09; 在已有的image、text 的基础上又加上了audio语音模态。 找了一些视频&#xff0c;有视频帧&#xff08;图像&#xff09;、文本、语音三种模态的信息&#xff0c;仿照CLIP的模型结构。三种模态两…

SpringSecurity + JWT(前后端分离)

文章目录一、先来聊聊 SpringSecurity JWT二、简单聊聊SpringSecurity 完整流程1、认证2、授权三、撸代码1、入门案例2、认证-前端端分离 Demo2.1 环境准备2.2 密码加密存储2.3 数据库校验存储2.4 编写自定义登陆接口2.5 JWT 认证过滤器2.6 退出登陆3、授权-前后端分离 Demo3.…

Spring In Action 5 学习笔记 chapter8 RabbitMQ(AMQP)要点

本文记录Sping In Action5 第8章 发送异步消息 RabbitMQ(AMQP)中的踩坑情况。 网搜的Spring In Action5的书籍在线翻译 https://potoyang.gitbook.io/spring-in-action-v5/ 第8章的源码请自行github或gitee搜索&#xff0c;或参考一下。 GitHub - habuma/spring-in-action-5…

web学生网页设计作业源码 HTML+CSS+JS 网上鲜花商城购物网站

&#x1f329;️ 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f482; 作者主页: 【进入主页—&#x1f680;获取更多源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;HTML5网页期末作业 (1000套…

MySQL Workbench 创建用户

创建本机用户 选择权限(增删改查)6个 创建新用户编辑窗口

【Web基础】Web应用体系结构 — 容器 + MVC设计模式

前言&#xff1a;提前祝大家国庆快乐了~~~~ 文章目录1 容器1.1 容器定义1.2 容器功能1.3 容器如何处理请求1.4 URL 映射 servlet2. MVC设计模式2.1 MVC 设计模式定义2.2 为什么要采用 MVC 设计模式&#xff1f;1 容器 1.1 容器定义 Servlet 没有 main() 方法&#xff0c;它们…

Vue再次入门<一>互动教程

两年前入了个门&#xff0c;很久没摸&#xff0c;又忘了。这次再来&#xff0c;改变下只做笔记的方式&#xff0c;改为边学边动手敲。加油&#xff0c;maymay&#xff5e; No one can stop u except yourself&#xff01; Vue再次入门一&#xff0c;互动教程1&#xff0c;声明式…

Windows11 家庭版开启远程桌面解决方案之RDP Wrapper Library,小白全面攻略

Windows11家庭版 首先需要了解的是Windows11是不支持家庭版的&#xff0c;那么既然看到了这里&#xff0c;那一定是具备解决方案的&#xff0c;方案就是通过 RDP Wrapper Library RDP软件下载 一定要下载合适的版本&#xff0c;最新的不一定是最合适的&#xff01; CSDN下载…

python代码学习——递归函数

python代码学习——递归函数函数的调用变量名解析&#xff08;查找&#xff09;原则&#xff1a;LEGB函数的执行流程函数调用的字节码递归&#xff08;Recursion&#xff09;斐波那契数列的递归实现方式递归的要求递归的性能斐波那契数列&#xff0c;递归方式的改进间接递归递归…

使用docker安装nginx笔记

一&#xff1a;查找镜像 docker search 名字 docker search nginx 二&#xff1a; 拉取nginx镜像到本地 (注&#xff1a;默认选取官方最新镜像)&#xff0c;其它版本可以去DockerHub查询 docker pull nginx 三&#xff1a;查看镜像 docker images nginx REPOSITORY&#xff…

Vue再次入门<二>深度指南

深度指南一&#xff0c;创建一个 Vue 应用1&#xff0c;应用实例2&#xff0c;根组件3&#xff0c;挂载应用4&#xff0c;DOM 中的根组件模板5&#xff0c;应用配置6&#xff0c;多个应用实例二&#xff0c;模板语法1&#xff0c;文本插值2&#xff0c;原始 HTML3&#xff0c;A…