List模拟实现

list,链表容器是一种能够高效进行数据存储的容器,与前面已经提到的一些容器相似,list也是用迭代器进行遍历和控制数据。但是list的迭代器实现起来较为复杂,因为其不是原生指针的typedef,如果我们要模拟实现一个list类型,需要自己重新定义一个类来实现。STL原文件实现的是双向带头节点的链表,此次我们实现的也相似。

组成部分类

组成部分类分为两块,一块是节点类,一块是迭代器类。节点类无从多讲,就与之前的数据结构类似,存储了数值,前一个链表的指针,后一个链表的指针。多的只有一个构造函数。

list的迭代器类实现起来便较为复杂了。首先,他有三个模版参数,这三个模版参数分别是元素类型,引用类型,指针类型。这样写有利于我们后面的const使用,后续细聊。我们在类中主要需要重载运算符,使其和一些由原生指针作为迭代器的容器功能保持一致。其中类似于++和--的运算符重载,因为链表的物理空间不连续,所以++和--使用的是直接把节点改成上一个节点和下一个节点。

复制代码
    // List的节点类
    template<class T>
    struct ListNode
    {
        ListNode<T>* _pPre;
        ListNode<T>* _pNext;
        T _val;
        ListNode(const T& val = T())
            :_val(val),
            _pPre(nullptr),
            _pNext(nullptr)
        {}
    };
    //List的迭代器类
    template<class T, class Ref, class Ptr>
    struct ListIterator
    {
        typedef ListNode<T>* PNode;
        typedef ListIterator<T, Ref, Ptr> Self;
        ListIterator(PNode pNode = nullptr)
        :_pNode(pNode)
        {}

        ListIterator(const Self& l)
        :_pNode(l->_pNode)
        {}

        T& operator*()
        {
            return _pNode->_val;
        }
        T* operator->()
        {
            return _pNode;
        }
        Self& operator++()
        {
            _pNode = _pNode->_pNext;
            return _pNode;
        }
        Self operator++(int)
        {
            _pNode = _pNode->_pNext;
            return _pNode->_pPre;
        }
        Self& operator--()
        {
            _pNode = _pNode->_pPre;
            return _pNode;
        }
        Self& operator--(int)
        {
            _pNode = _pNode->_pNext;
            return _pNode;
        }
        bool operator!=(const Self& l)
        {
            return l._pNode != _pNode;
        }
        bool operator==(const Self& l)
        {
            return l._pNode == _pNode;
        }
        PNode _pNode;
    };

typedef与类参数

我们在list类中主要是使用前面定位过的节点Node,代表链表中的单一部分,还有迭代器,用于数据的遍历和查改。在这里分为两个类型,iterator是普通迭代器,const_iterator是限定的迭代器,使用const_iterator迭代器获取的数据无法被修改,只读不写。

复制代码
        typedef ListNode<T> Node;
        typedef Node* PNode;


       PNode _pHead;
       size_t _size;
    public:
        typedef ListIterator<T, T&, T*> iterator;
        typedef ListIterator<T, const T&, const T*> const_iterator;

构造,赋值重载和析构函数

第一个函数是头节点创造函数,这个函数创造一个节点,接着将_pNext和_pPre都指向_Phead.这里有个地方曾经一直犯错,_pHead是一个指针,因此他接受的也是一个指针,而new函数创造的便是相关类型的指针,当时在写这个代码,我一直把Node写成PNode,导致new出来的是二级指针,二级指针不能赋给1级指针,就一直出错。析构函数中,先调用clear()函数,(后续提及),接着再删除哨兵位节点。剩下的代码多是根据一个数据去创建一个节点,接着依据数据结构中链表的知识点进行插入,不予赘述。

复制代码
       void CreateHead()
       {
           _pHead = new Node;
           _pHead->_pNext = _pHead;
           _pHead->_pPre = _pHead;
           _pHead->_val = 0;
       }

      list()
      {
          CreateHead();
      }

      list(int n, const T& value = T())
      {
          int i = n;
          while (i--)
          {
              PNode p = new PNode();
              p->_val = n;

              p->_pPre = _pHead;
              _pHead->_pNext->_pPre = p;
              p->_pNext = _pHead->_pNext;
              _pHead->_pNext = p;
          }
      }

      template <class Iterator>
      list(Iterator first, Iterator last)
      {
          CreateHead();
          auto it = first;
          while (it != last)
          {
              push_back(*it);
              it++;
          }
      }

      list(const list<T>& l)
      {
          CreateHead();
          auto it = l.begin();
          while (it != l.end())
          {
              push_back(*it);
              it++;
          }
      }

      list<T>& operator=(const list<T> l)
      {
          swap(l);
          return *this;

      }
      ~list()
      {
          clear();
          delete[] _pHead;
          _pHead = nullptr;

      }

迭代器提取函数

其中创建了若干简单常用的迭代器函数,这些函数能返回限定和非限定的begin(),end()函数

复制代码
        // List Iterator
        iterator begin()
        {
            return _pHead->_pNext;
        }
        iterator end()
        {
            return _pHead;
        }
        const_iterator begin() const
        {
            return _pHead->_pNext;
        }
        const_iterator end()const
        {
            return _pHead;
        }

数据获取函数

这里面包含两大部分,分别是容器数据大小函数和前后端数据返回函数,较为简单,不予赘述。

复制代码
        // List Capacity
        size_t size()const
        {
            return _size;

        }
        bool empty() const
        {
            return _size == 0;
        }

        
        // List Access
        T& front()
        {
            return _pHead->_pNext->_val;
        }
        const T& front()const
        {
            return _pHead->_pNext->_val;
        }
        T& back()
        {
            return _pHead->_pPre->_val;
        }
        const T& back()const
        {
            return _pHead->_pPre->_val;
        }

数据改变函数

数据改变函数里面比较重要的就是insert函数和erase函数,通过这两个函数的复用,我们就能完成头插尾插,头删尾删。insert和erase的逻辑与数据结构中的链表相似,不予赘述。clear函数将哨兵位的下个节点设为cur,接着进入循环,将哨兵位的下一个节点设为cur的下一个节点,删除cur,接着把cur设为原cur的下一个节点。最后保留哨兵位,但是把他的_Pre和_Next函数变成自己。swap通过复用std中的swap函数,把所有数据/指针交换即可。

复制代码
        // 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)
        {
            Node* cur = pos._pNode;
            Node* pre = pos._pNode->_pPre;

            PNode p = new Node(val);

            p->_pNext = cur;
            p->_pPre = pre;
            cur->_pPre = p;
            pre->_pNext = p;
            _size++;
            return iterator(p);
        }
        // 删除pos位置的节点,返回该节点的下一个位置
        iterator erase(iterator pos)
        {
            PNode cur = pos._pNode;
            PNode next = cur->_pNext;
            cur->_pPre->_pNext = cur->_pNext;
            cur->_pNext->_pPre = cur->_pPre;
            delete[]cur;
            
            return next;
        }

        void clear()
        {
            PNode cur = _pHead->_pNext;
            while (cur != _pHead)
            {
                _pHead->_pNext = cur->_pNext;
                delete[] cur;
                cur = _pHead->_pNext;
            }
            _pHead->_pNext = _pHead->_pPre = _pHead;
        }
        void swap(list<T>& l)
        {
            std::swap(l._pHead, _pHead);
            std::swap(l._size, _size);

        }
相关推荐
wuqingshun3141591 小时前
蓝桥杯 11. 打印大X
数据结构·算法·职场和发展·蓝桥杯·深度优先
wuqingshun3141592 小时前
蓝桥杯 2. 确定字符串是否是另一个的排列
数据结构·c++·算法·职场和发展·蓝桥杯
长沙火山3 小时前
9.ArkUI List的介绍和使用
数据结构·windows·list
AAAA劝导tx4 小时前
List--链表
数据结构·c++·笔记·链表·list
格格Code4 小时前
八大排序——冒泡排序/归并排序
数据结构·算法·排序算法
fantasy_45 小时前
LeetCode238☞除自身以外数组的乘积
java·数据结构·python·算法·leetcode
Phoebe鑫6 小时前
数据结构每日一题day12(链表)★★★★★
数据结构·算法·链表
八股文领域大手子7 小时前
深入浅出限流算法(三):追求极致精确的滑动日志
开发语言·数据结构·算法·leetcode·mybatis·哈希算法
新时代苦力工7 小时前
处理对象集合,输出Map<String, Map<String, List<MyObject>>>格式数据,无序组合键处理方法
java·数据结构·list
一捌年7 小时前
java排序算法-计数排序
数据结构·算法·排序算法