List 模拟实现

news2024/12/27 9:48:11

前言

本文将会向你介绍如何模拟实现list、iterator迭代器

模拟实现

引入

迭代器是一种用于访问容器中元素的对象,它封装了对容器中元素的访问方式。迭代器提供了一组操作接口,可以让我们通过迭代器对象来遍历容器中的元素。(iterator迭代器内部成员变量和成员函数都是与节点有关的)

先举一个例子,以下是迭代器++成员函数的实现
Self看不懂没关系,我们现在只要知道它是迭代器类名typedef过来的就好了,_pNode是一个指向ListNode结构体的指针。在ListIterator类中,_pNode用于指向链表中的某个节点,通过该指针可以访问节点的成员变量和成员函数。本质上还是对节点做操作,那我们为什么要大费周章还要套一个迭代器类呢?为什么不能大大方方的直接使用节点指针呢?
原因是:虽然写的人麻烦了,但却能方便使用者,使用者直接对迭代器对象++就能完成遍历

我们接下来就要做这个事情了。

        Self& operator++()
        {
            _pNode = _pNode->_pNext;    //让节点指针指向下一个节点    
            //返回迭代器对象
            return *this;
        }
迭代器对象和指向节点的指针之间有以下关系: 迭代器对象封装了指向节点的指针,提供了一组操作接口,使得我们可以通过迭代器对象来访问节点中的元素。 迭代器对象可以通过指针的方式来访问节点中的元素,可以使用指针运算符来获取元素的值,或者使用指针递增/递减来移动到下一个/上一个节点。 迭代器对象可以隐藏底层容器的具体实现细节,使得我们可以通过统一的接口来访问不同类型的容器,提高了代码的可复用性和灵活性。

整体结构

首先先让你熟悉一下整个结构

	//节点类
    template<class T>
    struct ListNode
    {
    
    }
    //迭代器类
    template<class T, class Ref, class Ptr>
    class ListIterator
    {
		  //...
		  //利用结构体指针对节点结构体进行管理
	      PNode _pNode;   //成员变量:结构体指针
     };
	//list类
    template<class T>
    class list
    {
		
		private:		
        PNode _pHead;	//哨兵位头节点(结构体指针)
        size_t _size;	//节点个数
    }

节点类

定义了一个节点结构体,在链表类中,我们需要能够直接访问节点的指针 _pPre 和 _pNext,以及节点的值 _val。如果将节点类定义为class,则需要在节点类中将这些成员变量和成员函数声明为public,或者在链表类中添加友元关系,以便链表类可以访问和操作节点的私有成员。
  template<class T>
  struct ListNode
  {
      //构造函数
      ListNode(const T& val = T())
          : _val(val) //节点的值
          , _pPre(nullptr)
          , _pNext(nullptr)
          {}
      ListNode<T>* _pPre; //节点的前驱
      ListNode<T>* _pNext; //节点的后继
      T _val;
  };

迭代器类

定义一个ListIterator类,在这个迭代器类中使用节点的指针,底层还是使用节点指针,迭代器类可以看作对节点类的封装,它提供了对节点的操作和访问的接口,使得用户可以通过迭代器来遍历和操作链表中的节点。 这里有一个点需要注意一下 这里的Ref指的是reference(引用), Ptr指的是pointer(指针),当然这里用T&、T*替换Ref和Ptr也是可以的 我们可以看到实例化部分,T&就传给了Ref,T * 就传给了Ptr

在这里插入图片描述
在这里插入图片描述
那么我们为什么要多传递两个模板参数呢?原因这两个模板参数可以助我们区分const迭代器类和普通迭代器类
在这里插入图片描述

值得一提的是重载->
当我们定义一个AA类型的对象的时候,并传入二元的数据,但是<<是不支持的(cout << *it << endl;) 我们可以想到用 cout << (*it)._a1 << " " << (*it)._a2 << endl, 这当然可以
但是迭代器模拟的是指针的行为,用->似乎更恰当一些,于是我们就重载了->运算符
operator->() 返回了 &_pNode->_val,这是什么意思呢?
&_pNode->_val这是一个指向AA类型数据的指针(AA *),对象指针再加上一个->就可以访问成员变量啦
cout << it.operator->() ->_a1 << " " << it.operator->()->_a2 << endl;

    struct AA
    {
        AA(int a1 = 0, int a2 = 0)
            :_a1(a1)
            ,_a2(a2)
        {}
        int _a1;
        int _a2;
    };
    void test_list4()
    {
        Fan::list<AA> lt;
        lt.push_back(AA(1, 1));
        lt.push_back(AA(2, 2));
        lt.push_back(AA(3, 3));
        Fan::list<AA>::iterator it = lt.begin();
        while (it != lt.end())
        {
            //AA对象不支持流插入
          	//error:cout << *it << endl;
            //cout << (*it)._a1 << " " << (*it)._a2 << endl;
            cout << it->_a1 << " " << it->_a2 << endl;
            //cout << it.operator->() ->_a1 << " " << it.operator->()->_a2 << endl;
            it++;
        }
        cout << endl;
    }
    ```

 ```c
 template<class T, class Ref, class Ptr>
    class ListIterator
    {
        //typedef结点类
        typedef ListNode<T>* PNode;
        //typedef迭代器类
        typedef ListIterator<T, Ref, Ptr> Self;
    public:
        //构造函数
        ListIterator(PNode pNode = nullptr)
            :_pNode(pNode) //初始化指向节点的指针
        {}
        //拷贝构造函数
        //l2(l1)
        ListIterator(const Self& l)
        {
            _pNode = l._pNode;  
        }
        //重载*
        //从泛型的角度来说我们并不知道operator*()的返回值是什么
        Ref operator*()
        {
            return _pNode->_val;   //节点指针指向的值
        }
        //重载->(为自定义类型的数据准备)
        Ptr operator->()
        {
            return &_pNode->_val;   
        }
        //重载++
        Self& operator++()
        {
            _pNode = _pNode->_pNext;    //让节点指针指向下一个节点    
            //返回迭代器对象
            return *this;
        }
        //重载后置++
        Self operator++(int)
        {
            Self tmp(*this);    //保存当前迭代器对象(当前指针指向的节点)
            _pNode = _pNode->_pNext;    
            return tmp;         //返回修改前的迭代器对象
        }
        //重载--
        Self& operator--()
        {
            _pNode = _pNode->_pPre; //让节点指针指向前一个节点
            //返回迭代器对象
            return *this;
        }
        //重载后置--
        Self& operator--(int)
        {
            Self tmp(*this);    //保存当前迭代器对象(当前指针指向的节点)
            _pNode = _pNode->_pPre;
            return tmp;         //返回修改前的迭代器对象
        }
        //重载!=
        bool operator!=(const Self& l)
        {
            return _pNode != l._pNode;  //判断节点指针是否相等
        }
        //重载==
        bool operator==(const Self& l)
        {
            return _pNode == l._pNode;  //判断节点指针是否相等
        }
        PNode _pNode;   //成员变量:节点指针
    };

list类

实例化类模板

用typedef重命名,隐藏底层的细节,提供统一的访问修改方式

 //list类
    template<class T>
    class list
    {
    	//实例化节点类模板
        typedef ListNode<T> Node;   
        typedef Node* PNode;        //重命名Node*指针类型
    public:
    	//实例化类模板
        typedef ListIterator<T, T&, T*> iterator;   
        //实例化const版本
        typedef ListIterator<T, const T&, const T&> const_iterator;
默认成员函数
// List的构造
        list()
        {
        	//初始化头节点
            CreateHead();
        }
        //n个value的构造
        list(int n, const T& value = T())
        {
            CreateHead();  
            while (n--)
            {
                push_back(value);   //尾插
            }
        }
        template <class Iterator>
        //一段迭代器空间的构造
        list(Iterator first, Iterator last)
        {
            CreateHead(); 
            while (first != last)
            {
                push_back(*first);  //尾插    
                first++;
            }
        }
        //拷贝构造
        //lt2(lt1)
        list(const list<T>& l)
        {
            CreateHead();   
            for (auto e : l)
            {
                push_back(e);
            }
        }
        //赋值重载
        //lt2 = lt1
        list<T>& operator=(list<T> l)
        {
            clear();    //清除数据(不包括头节点)
            for (auto e : l)
            {
                push_back(e);
            }
            return *this;
        }
        //析构函数
        ~list()
        {
            clear();
            //释放头节点
            delete _pHead;
            _pHead = nullptr;
        }
获取指向头尾节点的指针
 // List Iterator
        //返回第一个节点的指针(不是头节点)
        iterator begin()
        {
            //return iterator(_pHead->_pNext);
            return _pHead->_pNext;
        }
        //返回最后一个节点的下一个位置的节点指针
        iterator end()
        {
            //return iterator(_pHead)
            return _pHead;
        }
        //const:返回第一个节点的指针(不是头节点)
        const_iterator begin()  const
        {
            return const_iterator(_pHead->_pNext);
            //return _pHead->_pNext;  
        }
        //const://返回最后一个节点的下一个位置的节点指针
        const_iterator end()  const
        {
            return const_iterator(_pHead);
                //return _pHead;
        }
容量
        // List Capacity
        size_t size()
        {
            return _size;
        }
        bool empty()const
        {
            return _size == 0;
        }
获取头尾结点的值

        //返回第一个节点的值(不是头节点)
        T& front()
        {
            return _pHead->_pNext->_val;
        }
        //const版本:返回第一个节点的值(不是头节点)
        const T& front()const
        {
            return _pHead->_pNext->_val;
        }
        //返回最后一个节点的值(注意与end()区别)
        T& back()
        {
            return _pHead->_pPre->_val;
        }
        //const版本:返回最后一个节点的值
        const T& back()const
        {
            return _pHead->_pPre->_val;
        }
修改
  // List Modify
        void push_back(const T& val)
        {
            insert(end(), val); //复用
        }
        void pop_back()
        {
            erase(--end()); //复用
        }
        void push_front(const T& val)
        {
            insert(begin(), val); //复用
        }
        void pop_front()
        {
            erase(begin()); //复用
        }
        // 在pos位置前插入值为val的节点
        iterator insert(iterator pos, const T& val)
        {
            ++_size;
            //结构体指针
            PNode cur = pos._pNode;
            PNode prev = cur->_pPre;

            PNode newnode = new Node(val);
            prev->_pNext = newnode;
            cur->_pPre = newnode;
            newnode->_pPre = prev;
            newnode->_pNext = cur;
            //返回新节点位置处的迭代器
            return iterator(newnode);
        }
        // 删除pos位置的节点,返回该节点的下一个位置
        iterator erase(iterator pos)
        {
            --_size;
            PNode cur = pos._pNode;
            PNode prev = cur->_pPre;
            PNode next = cur->_pNext;

            prev->_pNext = next;
            next->_pPre = prev;
            delete cur;
            return iterator(next);
        }

测试

void test_list1()
{
   Fan::list<int> lt;
   lt.push_back(1);
   lt.push_back(2);
   lt.push_back(3);
   lt.push_back(4);
   lt.push_back(5);
   lt.push_back(6);
   Fan::list<int>::iterator it = lt.begin();
   while (it != lt.end())
   {
       cout << *it << " ";
       ++it;
   }
   cout << endl;
   for (auto e : lt)
   {
       cout << e << " ";
   }
   cout << endl;
}
void test_list2()
{
   Fan::list<int> lt1;
   lt1.push_back(1);
   lt1.push_back(2);
   lt1.push_back(3);
   lt1.push_back(4);
   lt1.push_back(5);
   Fan::list<int>::iterator it1 = lt1.begin();
   while (it1 != lt1.end())
   {
       cout << *it1 << " ";
       ++it1;
   }
   cout << endl;
   Fan::list<int> lt2(lt1.begin(), lt1.end());
   Fan::list<int>::iterator it2 = lt2.begin();
   while (it2 != lt2.end())
   {
       cout << *it2 << " ";
       ++it2;
   }
   cout << endl;
   Fan::list<int> lt3(lt2);
   Fan::list<int>::iterator it3 = lt3.begin();
   while (it3 != lt3.end())
   {
       cout << *it3 << " ";
       ++it3;
   }
}
//打印const对象
   void print_list(const Fan::list<int>& lt)
   {
       Fan::list<int>::const_iterator it = lt.begin();
       while (it != lt.end())
       {
           cout << *it << " ";
           ++it;
       }
       cout << endl;
       for (auto e : lt)
       {
           cout << e << " ";
       }
   }
   void test_list3()
   {
       Fan::list<int> lt;
       lt.push_back(1);
       lt.push_back(2);
       lt.push_back(3);
       lt.push_back(4);
       lt.push_back(5);
       lt.push_back(6);
       print_list(lt);
   }

小结

本篇文章对于初步接触模板的朋友们是有一定难度的,看完这篇文章,希望你能有所收获,在对模板的理解上更进一步,如果本文存在疏漏或错误的地方,还请您能够指出!

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

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

相关文章

机器学习-无监督算法之降维

降维&#xff1a;将训练数据中的样本从高维空间转换到低维空间&#xff0c;降维是对原始数据线性变换实现的。为什么要降维&#xff1f;高维计算难&#xff0c;泛化能力差&#xff0c;防止维数灾难优点&#xff1a;减少冗余特征&#xff0c;方便数据可视化&#xff0c;减少内存…

《动手学深度学习 Pytorch版》 8.6 循环神经网络的简洁实现

import torch from torch import nn from torch.nn import functional as F from d2l import torch as d2lbatch_size, num_steps 32, 35 train_iter, vocab d2l.load_data_time_machine(batch_size, num_steps)8.6.1 定义模型 num_hiddens 256 rnn_layer nn.RNN(len(voca…

1 两数之和

解题思路&#xff1a; \qquad 对每个数nums[i]&#xff0c;仅需在数组中搜索target-nums[i]是否存在。 优化思路&#xff1a; \qquad 首先能想到&#xff0c;利用哈希表O(1)查询target-nums[i]。 \qquad 建立map<int, vector<int>>的表能够处理重复元素&#x…

基于Eigen的位姿转换

位姿中姿态的表示形式有很多种&#xff0c;比如&#xff1a;旋转矩阵、四元数、欧拉角、旋转向量等等。这里基于Eigen实现四种数学形式的相互转换功能。本文利用Eigen实现上述四种形式的相互转换。我这里给出一个SE3&#xff08;4*4&#xff09;(先平移、再旋转)的构建方法&…

Ubuntu - 安装Docker

在Ubuntu上安装Docker分为以下几个步骤&#xff1a; 更新包列表&#xff1a; sudo apt update 安装依赖包&#xff0c;以便允许apt使用HTTPS&#xff1a; sudo apt install apt-transport-https ca-certificates curl software-properties-common 添加Docker官方GPG密钥&a…

在命令行下使用Apache Ant

Apache Ant的帮助文档 离线帮助文档 在<ant的安装目录>/manual下是离线帮助文档 双击index.html可以看到帮助文档的内容&#xff1a; 在线帮助文档 最新发布版本的帮助文档https://ant.apache.org/manual/index.html Apache Ant的命令 ant命令行格式 ant [opt…

在 Windows 平台上启动 MATLAB

目录 在 Windows 平台上启动 MATLAB 选择 MATLAB 图标 从 Windows 系统命令行调用 matlab 从 MATLAB 命令提示符调用 matlab 打开与 MATLAB 相关联的文件 从 Windows 资源管理器工具中选择 MATLAB 可执行文件 在 Windows 平台上启动 MATLAB 选择以下一种方式启动 MATLAB…

6.串口、时钟

预备知识 CC2530在正常运行的时候需要一个高频时钟信号和一个低频的时钟信号 高频时钟信号&#xff0c;主要供给CPU&#xff0c;保证程序的运行。 低频时钟信号&#xff0c;主要供给看门狗、睡眠定时器等偏上外设。 CC2530时钟信号的来源&#xff1a; 高频信号有2个&#xff0…

【C++】:初阶模板

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下有关Linux的基础知识点&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门到精通 数…

Stirling-PDF:一款优秀的开源PDF处理工具

最近我的朋友大雄需要将一个PDF转换为Word文档。于是他在网上尝试了多个PDF转换的在线工具&#xff0c;但要么需要会员&#xff0c;要么需要登录等繁琐操作&#xff0c;而且我们的文件也存在泄漏等安全隐患。因此&#xff0c;他向我咨询是否有可私有化部署且易于使用的PDF在线工…

字符函数和字符串函数2(C语言进阶)

字符函数和字符串函数2 三.长度受限制的字符串函数介绍1.strncpy2.strncat3.strncmp 四.字符串查找1.strstr2.strtok 五.错误信息报告1.strerror 六.字符操作七.内存操作函数1.memcpy2.memmove3.memset4.memcmp 三.长度受限制的字符串函数介绍 1.strncpy char * strncpy ( ch…

8.简易无线通信

预备知识 Zigbee无线通信&#xff0c;需要高频的载波来提供发射效率&#xff0c;Zigbee模块之间要可以正常的收发&#xff0c;接收模块必须把接收频率设置和发射模块的载波频率一致。Zigbee有27个载波可以进行通信&#xff0c;载波叫做信道&#xff08;无线通信的通道&#xf…

UE4 EQS环境查询 学习笔记

EQS环境查询对应Actor的范围 EQS环境查询查询对应的类 查询到即有一个蓝色的球在Actor上&#xff0c;里面有位置信息等等 在行为树运行EQS&#xff0c;按键&#xff08;‘&#xff09;可以看到Player的位置已经被标记 运行对应的EQS在这里放如EQS就可以了 Generated Point&…

2023年中国分布式光纤传感产量、需求量及行业市场规模分析[图]

分布式光纤传感器中的光纤能够集传感、传输功能于一体&#xff0c;能够完成在整条光纤长度上环境参量的空间、时间多维连续测量&#xff0c;具有结构简单、易于布设、性价比高、易实现长距离等独特优点&#xff0c;常用的分布式光纤传感器有光时域反射仪、布里渊分析仪、喇曼反…

【AI视野·今日Robot 机器人论文速览 第五十四期】Fri, 13 Oct 2023

AI视野今日CS.Robotics 机器人学论文速览 Fri, 13 Oct 2023 Totally 45 papers &#x1f449;上期速览✈更多精彩请移步主页 Interesting: &#x1f4da;AI与机器人安全, 从攻击界面、伦理法律和人机交互层面进行了论述。(from 密西西比大学) &#x1f4da;机器人与图机器学…

Windows端口号被占用的查看方法及解决办法

Windows端口号被占用的查看方法及解决办法 Error starting ApplicationContext. To display the conditions report re-run your application with debug enabled. 2023-10-14 22:58:32.069 ERROR 6488 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter : ***…

Qt 布局(QLayout 类QStackedWidget 类) 总结

一、QLayout类(基本布局) QLayout类是Qt框架中用于管理和排列QWidget控件的布局类。它提供了一种方便而灵活的方式来自动布局QWidget控件。QLayout类允许您以一种简单的方式指定如何安排控件&#xff0c;并能够自动处理控件的位置和大小&#xff0c;以使其适应更改的父窗口的大…

【HCIA】静态路由综合实验

实验要求&#xff1a; 1、R6为ISP&#xff0c;接口IP地址均为公有地址&#xff0c;该设备只能配置IP地址之后不能再对其进行任何配置 2、R1-R5为局域网&#xff0c;私有IP地址192.168.1.0/24&#xff0c;请合理分配 3、R1、R2、R4&#xff0c;各有两个环回IP地址;R5,R6各有一…

基于 Kubernetes 的 Serverless PaaS 稳定性建设万字总结

作者&#xff1a;许成铭&#xff08;竞霄&#xff09; 数字经济的今天&#xff0c;云计算俨然已经作为基础设施融入到人们的日常生活中&#xff0c;稳定性作为云产品的基本要求&#xff0c;研发人员的技术底线&#xff0c;其不仅仅是文档里承诺的几个九的 SLA 数字&#xff0c…

MyBatis的缓存,一级缓存,二级缓存

10、MyBatis的缓存 10.1、MyBatis的一级缓存 一级缓存是SqlSession级别的&#xff0c;通过同一个SqlSession对象 查询的结果数据会被缓存&#xff0c;下次执行相同的查询语句&#xff0c;就 会从缓存中&#xff08;缓存在内存里&#xff09;直接获取&#xff0c;不会重新访问…