解密list的底层奥秘

news2025/1/10 14:16:56

在这里插入图片描述

🎈个人主页:🎈 :✨✨✨初阶牛✨✨✨
🐻强烈推荐优质专栏: 🍔🍟🌯C++的世界(持续更新中)
🐻推荐专栏1: 🍔🍟🌯C语言初阶
🐻推荐专栏2: 🍔🍟🌯C语言进阶
🔑个人信条: 🌵知行合一
金句分享:
✨即使人生还有无数次失望的可能性,✨
✨但我还是想活在理想主义的乌托邦里.✨

目录

  • 一、list底层框架
    • (1) 节点类
    • (2) 迭代器类
    • (3) list类
  • 二、构造函数
    • (1) 无参构造
    • (2) n个value构造
    • (3) 迭代器区间构造
    • (4) 拷贝构造
  • 三、迭代器
    • 迭代器的实现
      • ① 构造
      • ② `*`和`->`
      • ③ 前置++与后置++
      • ④ 前置--与后置--
      • ⑤ 比较运算符
    • list类中的迭代器
    • front和back
  • 四、Modifiers:
    • (1) insert()
    • (2) erase()
    • (3) push_back() 和 push_front()
    • (4) pop_back() 和 pop_front()
    • (5) clear()
    • (6) swap()
  • 结语

本篇通过模拟实现list的构造函数,迭代器,和部分成员函数以帮助大家更加深层的理解list的原理,希望看完这篇文章使得友友们对list有了更加深层的理解.

一、list底层框架

list的底层是一个带头双向循环链表.
在这里插入图片描述

(1) 节点类

因为list中节点可能存储各种类型的值,所以这里使用了一个模板参数T.

list <int>
list<doubel>

 	// List的节点类
    template<class T>
    struct list_node
    {
        list_node(const T& val = T())
            :_val(val)
        {}
        //这里的 T() 表示使用类型 T 的默认构造函数创建一个对象,
        //它将调用 T 类型的默认构造函数来初始化 val。如果类型 T 没有提供默认构造函数,那么代码将无法编译通过。
        list_node<T>* _prev=nullptr;	//指向前驱结点
        list_node<T>* _next=nullptr;	//指向后继结点
        T _val;							//存储数据
    };

(2) 迭代器类

很多小伙伴会疑问,为什么一个迭代器类却使用了三个模板参数,是不是有些多余呢?
在这里插入图片描述

其实不然,牛牛依次为大家解释.

  1. class T: 是结点类的存储不同数据所需要使用的模板参数.该模板参数表示要处理的元素的类型。它可以是任意类型,例如整数、浮点数、自定义类等等。在模板实例化时,需要提供一个具体的类型。

  2. Ref: 该模板参数表示指向元素类型 T引用。它定义了对元素的引用类型,在实例化模板时,将使用指定的引用类型来操作元素。

  3. Ptr: 该模板参数表示指向元素类型 T指针。它定义了指向元素的指针类型,在实例化模板时,将使用指定的指针类型来操作元素。

template<class T, class Ref, class Ptr>
意味着
list_iterator<T, const T&, const T*>;

list的迭代器用来遍历链表中的元素,外部通过迭代器的++--进行链表的元素访问,这是一种封装,隐藏内部list的实现细节,外部只能通过迭代器的方式访问.

	//迭代器类
	template<class T, class Ref, class Ptr>
  	struct list_iterator
  	{
      typedef list_node<T> Node;
      typedef list_iterator<T, Ref, Ptr> self;//self表示自己
     
      list_iterator(Node* node);	//构造
      list_iterator(const self& list);     //拷贝构造
      Ref operator*();
      Ptr operator->();
      //前置++
      self& operator++();  
      //后置++
      self operator++(int);
      //前置--
      self& operator--();
      //后置--
      self& operator--(int);
      bool operator!=(const self& list) const; 
      bool operator==(const self& list);
      
      //成员变量
      Node* _node;
 	 };

(3) 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;
        
        list()//(无参构造)
        //n个value构造
        list(int n, const T& value = T());
        //迭代器区间构造
        template <class Iterator>
        list(Iterator first, Iterator last);    
        //拷贝构造
        list(const list<T>& list);
        //各种成员函数
        /...
        //析构函数
        ~list();
        iterator begin();       
        iterator end();
        //常属性迭代器
        const_iterator begin()const;
        const_iterator end()const; 
    private:
        Node* _head;
        size_t _size;
    };

二、构造函数

对于带头双向循环链表,它的初始化操作是必须的,因为必须创建一个头指针.
在这里插入图片描述
对于list的构造函数,它是很多种方式的,例如:无参构造,nval构造,迭代器区间构造等.
对于每个构造,必须前进行最初的初始化操作,为了避免代码冗余,我们将这个部分单独写成一个初始化操作的函数.

如下:

 void  Init_List()
  {
	_head = new Node;	//创建头指针
    _head->_prev = _head;
    _head->_next = _head;
    _size = 0;
  }

(1) 无参构造

调用Init_List();初始化函数即可.

 list()//初始化很重要(无参构造)
  {
      Init_List();
  }

(2) n个value构造

  1. 进行初始化操作.
  2. 尾插nvalue.
	//n个value构造
   list(int n, const T& value = T())
   {
       Init_List();
       while (n--)
       {
           push_back(value);
       }
   }

(3) 迭代器区间构造

  1. 进行初始化操作.
  2. 利用迭代器的特性,一次将区间中的数据尾插入链表.
//迭代器区间构造
  template <class Iterator>
  list(Iterator first, Iterator last)
  {
      Init_List();
      while (first != last)
      {
          push_back(*first);
          ++first;
      }
  }

(4) 拷贝构造

链表在物理空间上是不连续的,所以,对于参数是另一个链表的拷贝构造,只能遍历链表进行依次插入数据.

	//拷贝构造
	list(const list<T>& list)
	{
	    Init_List();
	    auto it = list.begin();
	    while (_size!=list._size)
	    {
	        push_back(*it);
	        it++;
	    }
	}

三、迭代器

迭代器的实现

① 构造

迭代器本质就是一个 Node* _node;结点类的指针.
对于迭代器的构造函数,只需要将结点的地址传过来即可.

list_iterator(Node* node)			//默认构造
 :_node(node) {}
list_iterator(const self& list)     //拷贝构造
 :_node(list._node){}

*->

(1) *
*运算符重载,表示对迭代器进行解引用.
使用场景:

list<int>::iterator it = L1.begin();
int start=*it;

很明显,*运算符是需要获取结点所存储的数据.为了减少拷贝以及对数据进行修改,这里采用传引用(Ref )返回.

 Ref operator*() {
     return _node->_val;//获取该结点的数据
 }

(2) ->

上面链表中的数据是简单的类型int
在这里插入图片描述

Ptr operator->() {
       return &_node->_val;
       // 等价于 return &(_node->_val);
   }

③ 前置++与后置++

对于链表,迭代器++表示向后访问下一个(后继)结点.学过链表的友友们应该知道.
也就是 _node = _node->_next;

前置++,返回++后的结点的迭代器
后置++,返回++前的结点的迭代器

 //前置++
  self& operator++() {
      _node = _node->_next;
      return *this;
  }

  //后置++
  self operator++(int) {
      Node* tmp=_node;        //保存++之前的值
      _node = _node->_next;
      return tmp;             //返回++之前的值
  }

④ 前置–与后置–

同理,返回当前结点迭代器的前驱结点.
也就是:_node = _node->_prev;

前置–,返回–后的结点的迭代器
后置–,返回–前的结点的迭代器

 //前置--
 self& operator--(){
     _node = _node->_prev;
     return *this;
 }

 //后置--
 self& operator--(int){
     Node* tmp = _node;          //保存 -- 之前的值
     _node = _node->_prev;
     return tmp;                 //返回 -- 之前的值
 }

⑤ 比较运算符

比较迭代器是否相等,实际就是比较迭代器所指向的结点是否相等.

 bool operator!=(const self& list) const
  {
      return _node != list._node;
  }
  
  bool operator==(const self& list){
      return _node == list._node;
  }

list类中的迭代器

在这里插入图片描述

iterator begin(): 返回第一个有效元素位置的迭代器
iterator end(): 返回最后一个有效元素位置的迭代器

	typedef list_iterator<T, T&, T*> iterator;
	typedef list_iterator<T, const T&, const T*> const_iterator;
	iterator begin()
	 {
      return _head->_next;
      //也可以强转一下
      //return iterator(_head->_next);
 	 }
 	 
	iterator end()
	{
      return _head;
      //也可以强转一下
     // return iterator(_head);
     }
     
	//常属性迭代器
	const_iterator begin()const
	{
	    return _head->_next;
	}
	const_iterator end()const
	{
	    return _head;
	}

front和back

在这里插入图片描述

 T& front()
 {
     return _head->_next->_val;//返回值
 }
 const T& front()const
 {
     return _head->_next->_val;
 }
 T& back()
 {
     return _head->_prev->_val;
 }
 const T& back()const
 {
     return _head->_prev->_val;
 }

四、Modifiers:

其实带头双向循环链表的增删改查较于单链表,更加简单,我们画图分析还是很容易实现的.

(1) insert()

在这里插入图片描述
(图片为博主原创,不得随意截图使用)


特殊情况:这是尾插:
在这里插入图片描述(图片为博主原创,不得随意截图使用)


代码示例

 // 在pos位置前插入值为val的节点
 iterator insert(iterator pos, const T& val)
 {
     //pos.node  而不是pos->node
     Node* newnode = new Node(val);
     Node* _prev_node = pos._node->_prev;      //pos位置结点的 原前置 结点
     Node* _cur_node = pos._node;             //pos位置的结点

     _prev_node->_next = newnode;
     newnode->_prev = _prev_node;

     _cur_node->_prev = newnode;
     newnode->_next = _cur_node;

     ++_size;//有效数据的个数+1.
     
     return newnode;
 }

(2) erase()

在这里插入图片描述

  // 删除pos位置的节点,返回该节点的下一个位置
  iterator erase(iterator pos)
  {
      
      Node* _prev_node = pos._node->_prev;             //pos位置结点的 原前置(prev) 结点
      Node* _next_node = pos._node->_next;            //pos位置结点的 原后置(next) 结点

      _next_node->_prev = _prev_node;
      _prev_node->_next = _next_node;

      delete pos._node;
      --_size;
      return _next_node;
  }

(3) push_back() 和 push_front()

 //尾插
 //void push_back(const T& val)
 //{
 //    Node* newnode = new Node(val);
 //    Node* _tail_node = _head->_prev;      // 原尾结点
 //    _tail_node->_next = newnode;
 //    newnode->_prev = _tail_node;

 //    _head->_prev = newnode;
 //    newnode->_next = _head;
 //    
 //    ++_size;//有效数据的个数+1.
 //}
 
 //复用insert更加方便
 void push_back(const T& val){
     insert(end(),val);//在头结点前面插入,即为尾插
 }
 
 //头插
 void push_front(const T& val) { 
     insert(begin(), val);
 }

(4) pop_back() 和 pop_front()

复用即可,不过多介绍了.

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

(5) clear()

clear:清除list中的有效数据
遍历链表进行依次删除结点,并将size置为0.

  void clear()
  {
      iterator it = begin();
      while (it != end())
      {
          it = erase(it);
      }

      _size = 0;
  }

(6) swap()

交换两个链表的成员变量即可.

 void swap(list<T>& list)
 {
     swap(_head, list._head);
     swap(_size, list._size);
 }

结语

看完这篇文章,相信大家对list有了更加深层的理解,对于list的迭代器,它并不像前面的stringvector那种原生指针,而是封装成了类,使得链表的迭代器也可以执行++--等操作,因为迭代器类重载了这些运算符.

今天就分享到这里了,如果觉得有帮助的话,可以给牛牛来一个一键三连吗?谢谢支持!
在这里插入图片描述

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

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

相关文章

AS中部署NCNN

参考链接 http://681314.com/A/Clzr6Q2OBO https://blog.csdn.net/xs1997/article/details/131747372 一、文章背景&#xff1a;公司再进行一个项目时&#xff0c;使用PyTorch框架&#xff0c;python语言及opencv工具进行神经网络深度学习算法进行训练。生成ONNX模型&#xff…

RocketMQ 消费者分类与分组

文章目录 消费者分类PushConsumerPushConsumer 内部原理使用注意事项 SimpleConsumerinvisibleDuration 消息不可见时间 消费者分组&#xff08;消费者负载均衡&#xff09;广播消费和共享消费负载均衡策略多个消费者消费顺序消息多消费者消费顺序消息示例 消费者分组管理关闭自…

八股文死记硬背打脸记

背景 我们都知道&#xff0c;再编程领域数据结构的重要性&#xff0c;常见的数据结构包括 List、Set、Map、Queue、Tree、Graph、Stack 等&#xff0c;其中 List、Set、Map、Queue 可以从广义上统称为集合类数据结构。而Java也提供了很多的集合数据结构以供开发者开箱即用&…

左神高级提升班2 约瑟夫环结构

目录 【案例1】 【题目描述】 【输入描述&#xff1a;】 【输出描述&#xff1a;】 【输入】 【输出】 【思路解析】 【代码实现】 【案例1】 【题目描述】 某公司招聘&#xff0c;有n个人入围&#xff0c;HR在黑板上依次写下m个正整数A1、A2、……、Am&#xff0c;然后…

alist windows桌面版下载安装

官网 Desktop | AList Docs 点击下载windows版本 安装 双击exe 修改安装路径 太可惜了&#xff0c;需要收费

LeetCode刷题---Add Two Numbers(一)

文章目录 &#x1f352;题目&#x1f352;解法一 迭代&#x1f352;解法二 递归&#x1f352;递归小案例&#x1f352;迭代 VS 递归 &#x1f352;题目 给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只…

聚观早报 | 飞书签约韵达速递;蔚来首颗自研芯片“杨戬”量产

【聚观365】9月22日消息 飞书签约韵达速递 蔚来首颗自研芯片“杨戬”10月量产 靳玉志接任华为车 BU CEO 亚马逊发布全新Alexa语音助手 OpenAI推出图像生成器DALL-E 3 飞书签约韵达速递 近日&#xff0c;国内物流服务公司韵达快递宣布全员上飞书。飞书解决方案副总裁何斌表…

【大数据】Doris 构建实时数仓落地方案详解(一):实时数据仓库概述

本系列包含&#xff1a; Doris 构建实时数仓落地方案详解&#xff08;一&#xff09;&#xff1a;实时数据仓库概述Doris 构建实时数仓落地方案详解&#xff08;二&#xff09;&#xff1a;Doris 核心功能解读Doris 构建实时数仓落地方案详解&#xff08;三&#xff09;&#…

云原生Kubernetes:K8S集群list-watch机制与 pod调度约束

目录 一、理论 1.K8S的list-watch 机制 2.亲和性 二、实验 1. 指定调度节点 2.节点亲和性 3.亲和性和反亲和 三、问题 1.新生成pod一直为pending 2.如何一次性删除pod和deployment 3.pod亲和性资源报错 4.pod反亲和性资源报错 四、总结 一、理论 1.K8S的list-wat…

zookeeper + kafka

Zookeeper 概述 Zookeeper是一个开源的分布式服务管理框架。存储业务服务节点元数据及状态信息&#xff0c;并负责通知再 ZooKeeper 上注册的服务几点状态给客户端 Zookeeper 工作机制 Zookeeper从设计模式角度来理解: 是一个基于观察者模式设计的分布式服务管理框架&…

vector的扩容机制—为何是1.5倍或者是2倍

文章目录 前言一、Vector 扩容过程二、为什么是1.5倍或者2倍&#xff1f; 前言 在 C 编程中&#xff0c;Vector 是一种常用的动态数组容器。其大小是可以动态调整的&#xff0c;而在扩容操作中&#xff0c;Vector 通常会将容量增加为原来的两倍。本篇博客将详细介绍 Vector 扩…

Kindle电子书下载功能关闭怎么办,借助calibre和cpolar搭建私有的网络书库公网访问

Kindle中国电子书店停运不要慌&#xff0c;十分钟搭建自己的在线书库随时随地看小说&#xff01; 文章目录 Kindle中国电子书店停运不要慌&#xff0c;十分钟搭建自己的在线书库随时随地看小说&#xff01;1.网络书库软件下载安装2.网络书库服务器设置3.内网穿透工具设置4.公网…

RT-Thread(学习)

RT-Thread是一款完全由国内团队开发维护的嵌入式实时操作系统&#xff08;RTOS&#xff09;&#xff0c;具有完全的自主知识产权。经过16个年头的沉淀&#xff0c;伴随着物联网的兴起&#xff0c;它正演变成一个功能强大、组件丰富的物联网操作系统。 RT-Thread概述 RT-Threa…

openssl创建CA证书教程

配置生成CA证书 总示意图&#xff1a; (1)&#xff0c;通过openssl创建CA证书 第一步&#xff1a;创建一个秘钥&#xff0c;这个便是CA证书的根本&#xff0c;之后所有的东西都来自这个秘钥 # 通过rsa算法生成2048位长度的秘钥 openssl genrsa -out myCA.key 2048 第二步&#…

Wireshark TS | MQ 传输缓慢问题

问题背景 应用传输慢是一种比较常见的问题&#xff0c;慢在哪&#xff0c;为什么慢&#xff0c;有时候光从网络数据包分析方面很难回答的一清二楚&#xff0c;毕竟不同的技术方向专业性太强&#xff0c;全栈大佬只能仰望&#xff0c;而我们能做到的是在专注于自身的专业方向之…

什么是Selenium?使用Selenium进行自动化测试!

你知道什么是 Selenium 吗&#xff1f;你知道为什么要使用它吗&#xff1f;答案就在本文中&#xff0c;很高兴能够与你共飧。 自动化测试正席卷全球&#xff0c;Selenium 认证是业界最抢手的技能之一。 什么是 Selenium&#xff1f; Selenium 是一种开源工具&#xff0c;用于…

Spring Cloud Alibaba Nacos 2.2.3 (2) - 单机版启动 (winodows 和 linux )

Nacos 2.2.3 (1) - 下载与数据库配置 参考下载与数据库配置 启动服务器 执行 nacos-server-2.2.3\bin 下的startup.sh或者startup.cmd &#xff08;根据不同系统&#xff09; windows 下nacos 单机启动 方式一&#xff1a; 1&#xff0c;打开cmd 2&#xff0c;cd 到nacos-s…

数据库数据恢复-SQL SERVER数据库分区被格式化的数据恢复方案

SQL SERVER数据库故障类型&#xff1a; 1、SQL SERVER数据库文件被删除。 2、SQL SERVER数据库所在分区格式化。 3、SQL SERVER数据库文件大小变为“0”。 4、使用备份还原数据库时覆盖原数据库。 SQL SERVER数据库故障原因&#xff1a; 1、人为误操作。 2、文件系统损坏&#…

基于Yolov8的工业小目标缺陷检测实战(2):动态蛇形卷积(Dynamic Snake Convolution),实现暴力涨点 | ICCV2023

目录 1.工业油污数据集介绍 1.1 小目标定义 1.2 难点 1.3 工业缺陷检测算法介绍 1.3.1 YOLOv8 2.Dynamic Snake Convolution 2.1 Dynamic Snake Convolution加入到yolov8 3.训练结果分析 4.系列篇 1.工业油污数据集介绍 三星油污缺陷类别&#xff1a;头发丝和小黑点&…

AirtestIDE编辑窗内,脚本内容消失,显示一片红色怎么办?

airtest编辑窗内&#xff0c;脚本内容消失&#xff0c;显示一片红色怎么办 如果突然有一天打开脚本&#xff0c;发现脚本全都变成了红色的点&#xff08;因未知错误导致的脚本异常&#xff09;&#xff1a; 原本辛辛苦苦写的三百多行代码全消失&#xff0c;是不是很难受很心慌…