当前位置: 首页 > news >正文

list底层原理

一.结构体的构建

        

        这个用结构体更好,因为我们需要不断的访问节点,类中的成员函数一般都是私有的,需要还用友元函数什么的。

        

        这个是我们来实现的类,我们实现的是双向带头循环链表,这个是实用性最高的一个链表的形式。

        这个_head就是一个头的作用,没有实际意义。

二.构造函数

        

        这个就是我们的构造函数了,这个就是给头申请一块空间,让next和prev都指向头节点,这样就不会出现空指针的问题了,更加的方便。

        

三.push_back

        

        这个是我们的插入函数,大家可以结合下面的图理解着看一下。

        

       大家可以结合着上面的代码自己画一下图就很好理解了,这个就是创建一个新节点,然后先找到原来链表的最后一个节点也就是_head->prev,然后再让链表的最后一个元素指向新的节点,再让新的节点的next指向_head,再让新的节点的prev指向指向原链表的最后一个节点,最后让_head的prev指向新节点即可。

        

        

四.迭代器

        下面来实现一下迭代器。

        我们会遇到一些问题,我们难道也要像实现vector的迭代器一样,实现一个指针吗?

        typedef list_node<T>* iterator;//这样实现会面临很多问题,我们的解引用是一个结构体而不是一个值,况且空间不连续,我们的++操作也没法完成,那我们该怎么办呢?

        我们可以封装一个结构体来完成。

        

        这就是我们迭代器的所需的一个结构体,就是通过node来接收我们的list_node<T>的结构体,通过构造初始化,就是传入我们需要的节点,接下来我们来完成迭代器的一些基本操作吧。

        

        4.1 operator*()

        

        通过赋值运算符的重载直接返回这个节点的值就可以了。

        

        4.2 operator++()

        

        这个就是我们的++操作,直接让我们的node=node->_next即可完成,也是很简单的。

        

        4.3 operator!=()

        

        这个就是两个list_iterator<T>对象进行比较的,通过比较它们的node是否相等即可。

        

4.4 operator==()

        

        这个就是把上面的代码的!=改成等于了。

        

4.5 begin()和end()

        

        这就是我们完成的begin和end函数,返回了iterator对象。

        

        接下来我们来测试一下。

        

        我们发现都是符合我们的预期的。

        


五.const的迭代器

        我们都知道迭代器一般都有两种一种是普通版本的,一种则是const版本的。

        const版本的实现起来也是很简单的。

        

        这就是我们的const版本的迭代器,也是很简单的,只需要改一下返回值什么的就行了。

        

        这是它的begin和end方法。

        

        为什么报错了呢?

        因为l1不是const类型的,它会优先匹配自己的迭代器,而不是const类型的迭代器,两个不同的类型都是优先匹配到自己的类型的begin和end方法。

        这个就是it是const_iterator类型的,但是l1.end()是iterator类型的,类型不匹配。

        我们发现出现了问题,无法使用*,因为我们的A类中没有重载。

        

        如图所示就得到了解决,但是又发现了几个常犯的错误,那为什么我这里的l1调用不到const类型的begin和end函数呢?实际上,非 const 对象 l1 是可以调用 const 版本的 begin 和 end 函数的。不过,当非 const 对象调用成员函数时,编译器会优先选择非 const 版本的函数。

        为什么我们的l2调用不到其他的函数呢?当你在qrh::list<A<int>>前面加上const后,该对象就成为了常量对象。对于常量对象,只能调用其const成员函数。

        最后一条语句也可以验证我们上面所说的类型。

        我们来看一下结果。

        

        和我们想的一样,就是list_iterator类型的。

        还有一种方法就是->的运算符,也是可以访问到自定义类型中的变量的。

        5.1 operator->()

        

        就是这种形式,可以大家刚开始看的时候有些迷茫,为什么返回node->_data的地址呢?

        你可以理解为因为要返回一个指针,指针才具有->操作符。

        

        我们来看一下,返回了node->_data的地址,应该是两个箭头的,第一个箭头先找到了_node->_data再来一个箭头才能访问得到其中的变量啊,因为编译器做优化了,两个箭头的可读性太低了,所以优化为一个箭头了,了解知道就行。

        

        

        六.合并两个迭代器

        上面是我们正常思路是要写两个的,但是我们可以观察一下它们的共同之处。

        

        你看它们的这些方法只是返回值的类型不同罢了,一个是T,一个是const T,其他方法都是相同的,只有这两个方法需要区别一下,其实我们就可以使用函数模板,把它弄成一个就行了,接下来看我操作。

        

        我们又加了两个模板参数,我们通过传参的不同可以区别这两个迭代器的类型。

        

        看见没,我们通过控制后面两个参数的不同就可以区别出迭代器了,这是一个非常厉害的方法。

        这样就可以把两个迭代器只写一个结构体就可以了。

        

我们接下来来完成剩下的一些常用的方法吧。

七.insert()

        

        这个就是通过迭代器插入。

        我们的思路是我们首先先找到我们所要插入的位置,然后再找到它的前一个位置,然后让前一个位置的_next指向newnode,newnode的_prev指向newnode,再让newnode的_next指向cur,再让cur的_prev指向newnode即可。

        size表示链表的长度。

        通过这个我们也可以简化一下我们的push_back了。

        

     


八.erase

        

        这个就是通过迭代器位置删除该位置。

        我们还是先找到我们需要删除的位置,然后再找到它的前一个位置和后一个位置,然后让前一个位置的_next指向下一个位置,让下一个位置的_prev指向前一个位置,再把当前位置的空间释放了,再把size--即可,最后的返回值的作用就是防止迭代器失效的,一会儿下面有样例。

九.push_front 和pop_back 和 pop_feont

        

        我们等会儿再写--的操作,这些都很简单,看一下就行了。

        我们来测试一下我们写的代码。

        

        发现是符合我们预期的。


十.operator--

        

        这个就很简单了。

十一.后置--和后置++

        

        这个我们在之前的运算符重载都讲过,这里就不在详细展开讲了,不懂的可以看之前的博客。

         


十二.clear

        

        这就是我们的clear函数了,这里也体现了我们上面说的迭代器失效的问题了,如果我们不给返回值的话,此时it这个迭代器就会失效了,由于指向了一块被释放的空间,导致迭代器失效了。


十三.拷贝构造

        

        不写的话就默认是浅拷贝了,可能会出现同一块空间被释放两次的场景。

        举个浅拷贝的例子吧。

        

        我们先跑一下看看。

        

        我们发现程序崩溃了,就是发生了浅拷贝,两个对象指向了同一块空间,当其中一个对象被clear了,导致另一个对象指向了一块野指针的空间,导致打印不出来结果,此时程序就会崩溃了。

        我们在写了深拷贝之后就不会出现这样的问题了。

        

        没有问题。


十四.operator=()

        

        这个是最新版的函数,想要详细的讲解可以看vector底层的那篇博客,里面有详细的讲解。        

        如果我们没有重载这个的话,默认还是浅拷贝,我们再来演示一下。

        

我们来看一下。

     

        虽然打印出来结果了,但是程序还是崩溃了。原因就是浅拷贝使它俩指向了同一块空间,所以同一块空间被释放了两次,导致了程序的崩溃。

        所以我们就必须写。

        


十五.结束语

        感谢大家的查看,希望可以帮助到大家,做的不是太好还请见谅,其中有什么不懂的可以留言询问,我都会一一回答。  感谢大家的一键三连。

相关文章:

  • Python基础知识语法归纳总结(数据类型-2)
  • 调和平均数通俗易懂的解释以及为什么这样定义,有什么用
  • Git ——提交至github,Vercel拉取,更新不了项目的问题解决
  • redis数据类型-基数统计HyperLogLog
  • 典籍知识问答典籍查询界面前端界面设计效果实现
  • C# byte[]字节数组常用的一些操作。
  • 实战交易策略 篇十七:翻倍黑马交易策略
  • npm的基本使用安装所有包,安装删除指定版本的包,配置命名别名
  • 解决方案 | 晶尊微智能马桶着座感应模块
  • nodejs的包管理工具介绍,npm的介绍和安装,npm的初始化包 ,搜索包,下载安装包
  • Git远程操作
  • Java MCP客户端SDK实现
  • Unity 带碰撞的粒子效果
  • Linux 系统监控进阶:htop 命令详解与高效运维
  • 已安装爱思助手和Apple相关驱动,但仍无法有线连接iPhone热点,且网络适配器没有Apple Mobile Device Ethernet,问题解决
  • 比特币三种扩容路径Nubit、Babylon、Bitlayer分析
  • java的反编译命令
  • 【Hive入门】Hive架构与组件深度解析:从核心组件到生态协同
  • 关于RPC
  • 物联网 (IoT) 安全简介
  • 不朽诗篇的现代重生,意大利音乐剧《神曲》将来华15城巡演
  • 金发科技去年净利增160%,机器人等新领域催生材料新需求
  • 内蒙古已评出280名“担当作为好干部”,186人提拔或晋升
  • 李家超率团访问浙江
  • 智飞生物一季度营收下滑79%,连续三个季度亏损,称业绩波动与行业整体趋势一致
  • 上海与丰田汽车签署战略合作协议,雷克萨斯纯电动汽车项目落子金山