C++ STL list

简介: 上次我们详细的介绍了vector,今天我们继续来介绍一下TSTL中的另外一个容器list。list在基础的功能和结构上就是一个双向带头的循环链表,实现起来基本不难,但是list迭代器的封装是非常值得学习的。

 

✅<1>主页:我的代码爱吃辣

📃<2>知识讲解:C++之 STL list介绍和模拟实现

☂️<3>开发环境:Visual Studio 2022

💬<4>前言:上次我们详细的介绍了vector,今天我们继续来介绍一下TSTL中的另外一个容器list。list在基础的功能和结构上就是一个双向带头的循环链表,实现起来基本不难,但是list迭代器的封装是非常值得学习的。

目录

一.认识list

二.list的使用

1.构造函数

2.增删查改

3.list 迭代器

四.list 模拟实现

1.链表结点

2.list整体结构

3.list构造函数

4.push_back(),pop_back()

5.迭代器

5.1迭代器结构

5.2迭代器操作

5.2.1operator*()

5.2.2 operator ++(),operator --()

5.2.3operator==(),operator!=()

5.2.4operator->()

5.2.5begin(),end()

5.3 const 迭代器

5.4 insert(),erase()

5.5 析构函数

5.6再看构造函数

五.list模拟实现整体代码


image.gif编辑

一.认识list

list - C++ Reference

    1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
    2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
    3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。
    4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好。
    5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)。

    结构:list使用的是双向的循环带头结点的链表。

    image.gif编辑

    二.list的使用

    list的使用非常简单,有了我们之前vector和string的基础。上手list基本就是小菜一碟。

    1.构造函数

    构造函数( (constructor)) 接口说明
    list (size_type n, const value_type& val = value_type()) 构造的list中包含n个值为val的元素
    list() 构造空的list
    list (const list& x) 拷贝构造函数
    list (InputIterator first, InputIterator last) 用[first, last)区间中的元素构造list

    2.增删查改

    list<int> li;
      //尾插
      li.push_back(10);
      //头插
      li.push_front(20);
      //尾删
      li.pop_back();
      //头删
      li.pop_front();
      //迭代器位置插入数据
      li.insert(li.begin(), 100);
      //删除迭代器位置数据
      li.erase(li.begin());

    image.gif

    3.list 迭代器

    函数声明 接口说明
    begin +end 返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器
    rbegin +
    rend
    返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的
    reverse_iterator,即begin位置

    list的迭代器使用与string和vector一模一样,在此不多介绍。

    四.list 模拟实现

    1.链表结点

    template<class T>
    struct _list_node
    {
      _list_node( const T& val = T())
        :_val(val),
        _next(nullptr),
        _prev(nullptr)
      {
      }
      T _val;           //存储数据
      _list_node* _next;//后一个结点的指针
      _list_node* _prev;//前一个结点的指针
    };

    image.gif

    注意:我们在此处的struct定义了一个类,我们在定义_next和_prev的时候就不用像C语言那样加上struct。

    例如:C语言写法

    template<class T>
    struct _list_node
    {
      _list_node(const T& val = T())
        :_val(val),
        _next(nullptr),
        _prev(nullptr)
      {
      }
      T _val;           //存储数据
      struct _list_node* _next;//后一个结点的指针
      struct _list_node* _prev;//前一个结点的指针
    };

    image.gif

    2.list整体结构

    template<class T>
    class List
    {
    public:
        typedef _list_node<T> node;
        //...成员方法 增删查改
    private:
      node* _head;//头节点
    };

    image.gif

    3.list构造函数

    因为我们的list是一个带哨兵位的双向循环链表。所以这里我们将new出来的结点,设置为循环双向结构。

    List()
        :_head(new node)
      {
        _head->_next = _head;
        _head->_prev = _head;
      }

    image.gif

    4.push_back(),pop_back()

    push_back()尾插一个数据,pop_back()尾删一个数据。

    //push_back
        void push_back(const T& val )
      {
            //创建结点
        node* newnode = new node(val);
        node* tail = _head->_prev;
            //改变指向
        tail->_next = newnode;
        newnode->_next = _head;
        _head->_prev = newnode;
        newnode->_prev = tail;
      }

    image.gif

    //pop_back
        void pop_back()
      {
        //判空
        assert(!empty());
        node* tail = _head->_prev;
        node* newtail = tail->_prev;
        //改变指向
        _head->_next = newtail;
        newtail->_prev = _head;
        //释放结点
        delete tail;
      }

    image.gif

    image.gif编辑

    5.迭代器

    list的迭代器不同于string和vector,因为list的物理存储是不连续的,所以我们不能像list和vector一样仅仅使用原生指针来作为list的迭代器。

    迭代器的实际就是一种模仿指针的行为来为容器提供一种通用的访问方法的设计。虽然list的迭代器不能使用原生指针来替代,但是我们可以对原生的指针进行类级别的封装来构造迭代器,并且使得迭代器具有指针的行为。

    迭代器有两种实现方式,具体应根据容器底层数据结构实现:

      1.  原生态指针,比如:vector
      2.  将原生态指针进行封装,因迭代器使用形式与指针完全相同,因此在自定义的类中必须实现以下方法:
        • 指针可以解引用,迭代器的类中必须重载operator*()
        • 指针可以通过->访问其所指空间成员,迭代器类中必须重载oprator->()
        • 指针可以++向后移动,迭代器类中必须重载operator++()与operator++(int)
        • 至于operator--()/operator--(int)释放需要重载,根据具体的结构来抉择,双向链表可以向前移动,所以需要重载,如果是forward_list就不需要重载--
        • 迭代器需要进行是否相等的比较,因此还需要重载operator==()与operator!=()

        5.1迭代器结构

        迭代器的整体结构如下。

        template<class T>
        class _list_iterator
        {
        public:
          typedef _list_node<T> node;    //结点类
          typedef _list_iterator<T> self;//迭代器本身
            //使用原生指针构造迭代器
          _list_iterator(node* n)
            :_node(n)
          {
          }
            //operator*()
            //operator++()
            //operator--()
            //operator->()
            //operator==()
            //operator!=()
        private:
          node* _node;//迭代器是对原生指针的封装
        };

        image.gif

        5.2迭代器操作

        5.2.1operator*()

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

        image.gif

        返回该结点的值的引用。

        5.2.2 operator ++(),operator --()

        ++的操作其实就是让迭代器挪到下一个位置,--操作其实就是让迭代器挪到上一个位置。迭代器++或者--操作的返回值还是一个迭代器。

        //后置++
          self operator++(int)
          {
            self tmp(*this);
            _node = _node->_next;
            return tmp;
          }
            //前置++
          self operator++()
          {
            _node = _node->_next;
            return *this;
          }
            //前置--
            self operator--()
          {
            _node = _node->_prev;
            return *this;
          }
            //后置--
            self operator--(int)
          {
            self tmp(*this);
            _node = _node->_prev;
            return tmp;
          }

        image.gif

        5.2.3operator==(),operator!=()

        比较两个迭代器是否相等直接比较迭代器封装的两个指针是否相等就可以了。

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

        image.gif

        5.2.4operator->()

        我们知道一般的结构体,类指针都是支持使用->访问一些成员的。当list存储的数据是结构体,或者是类试迭代器也应该支持->去访问。

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

        image.gif

        那么这样我们在外边的访问会不会很奇怪呢?

        struct AA
            {
              AA(int aa = 10)
                :_aa(aa)
              {
              }
                int _aa;
            };
            list<AA> lli(10, AA()); 
            list<AA>::iterator lit = lli.begin();
            //1.调用:
            lit.operator->()->_aa;
            //2.调用:
            lit->_aa;

        image.gif

        image.gif编辑

        在实际调用的时候我们可以直接写成调用2。

        5.2.5begin(),end()

        typedef _list_iterator<T> iterator;
            iterator begin()
          {
            //使用哨兵位的下一个结点指针,构造begin
            return iterator(_head->_next);
          }
          iterator end()
          {
            //使用哨兵位结点指针,构造end
            return iterator(_head);
          }

        image.gif

        可以构成 [ begin,end ).这种左闭右开的结构。

        5.3 const 迭代器

        const迭代器与普通迭代器最大的不同,就是const迭代器不允许修改迭代器指向的数据。在整个迭代器操作中只有,operator* 和 operator->是对指向数据操作的。我们仅需要将普通迭代器的这两个函数返回值修改即可。并且区分开普通迭代器与const迭代器。

        template<class T>
        class const_list_iterator
        {
        public:
          typedef _list_node<T> node;
          typedef const_list_iterator self;
          const_list_iterator(node* n)
            :_node(n)
          {
          }
            //返回值加const,不允许修改
          const T& operator*()
          {
            return _node->_val;
          }
          self operator++(int)
          {
            self tmp(*this);
            _node = _node->_next;
            return tmp;
          }
          self operator++()
          {
            _node = _node->_next;
            return *this;
          }
          self operator--(int)
          {
            self tmp(*this);
            _node = _node->_prev;
            return tmp;
          }
          self operator--()
          {
            _node = _node->_prev;
            return *this;
          }
          bool operator!=(const self& it)
          {
            return _node != it._node;
          }
          bool operator==(const self& it)
          {
            return _node == it._node;
          }
            //返回值加const,不允许修改
          const T*  operator->()
          {
            return &_node->_val;
          }
        private:
          node* _node;
        };

        image.gif

        但是这种设计让人觉得很不舒服,代码的重复太多。从代码的角度来看,const和普通迭代器仅仅只是返回值不同而已。我们只要给在给迭代器不同的类型,我们只需要给模板多架两个参数,如果是 T& 和 T*就实例化出普通迭代器,如果是 const T& 和 const T*就实例化出const迭代器。

        list类内部:

        template<class T>
        class List
        {
        public:
          typedef _list_node<T> node;
          typedef _list_iterator<T,T&,T*> iterator;//普通迭代器
          typedef _list_iterator<T,const T&,const T*> const_iterator;//const迭代器
          List()
            :_head(new node)
          {
            _head->_next = _head;
            _head->_prev = _head;
          }
          iterator begin()
          {
            //使用哨兵位的下一个结点指针,构造begin
            return iterator(_head->_next);
          }
          iterator end()
          {
            //使用哨兵位结点指针,构造end
            return iterator(_head);
          }
          const_iterator begin()const
          {
            return const_iterator(_head->_next);
          }
          const_iterator end()const
          {
            return const_iterator(_head);
          }
        private:
          node* _head;
        };

        image.gif

        迭代器:

        //T:数据类型,Ref:T&/const T&, Ptr: T*/const T*
        template<class T,class Ref,class Ptr>
        class _list_iterator
        {
        public:
          typedef _list_node<T> node;
          typedef _list_iterator<T,Ref,Ptr> self;
          _list_iterator(node* n)
            :_node(n)
          {
          }
          Ref operator*()
          {
            return _node->_val;
          }
          self operator++(int)
          {
            self tmp(*this);
            _node = _node->_next;
            return tmp;
          }
          Ptr operator->()
          {
            return &_node->_val;
          }
          bool operator!=(const self& it )
          {
            return _node != it._node;
          }
          bool operator==(const self& it)
          {
            return _node == it._node;
          }
            //返回迭代器内部的结点指针
          node* GetNodePtr()
          {
            return _node;
          }
        private:
          node* _node;
        };

        image.gif

        根据不同的模板参数从而实例化出不同的代码。

        5.4 insert(),erase()

        insert()可以在某一迭代器位置插入数据,erase可以删除某一迭代器位置的数据。

        void insert(iterator pos,const T& val)
          {
                //创建结点
            node* newnode = new node(val);
            node* posnode = pos.GetNodePtr();
            node* prevnode = posnode->_prev;
                //改变指向
            newnode->_next = posnode;
            newnode->_prev = prevnode;
            prevnode->_next = posnode;
            posnode->_prev = newnode;
          }
            iterator erase(iterator pos)
          {
            node* posnode = pos.GetNodePtr();
            node* prevnode = posnode->_prev;
            node* nextnode = posnode->_next;
                //修改指向
            prevnode->_next = nextnode;
            nextnode->_prev = prevnode;
                //释放结点
            delete posnode;
                return iterator(nextnode);
          }

        image.gif

        erase在返回的删除位置的下一个结点的迭代器,也是为了绝迭代器失效的问题。

        5.5 析构函数

        //清除数据,但是头节点需要保留
          void clear()
          {
            iterator it = begin();
            while (it != end())
            {
              erase(it++);
            }
          }
          ~List()
          {
            //连同头节点一起释放
            clear();
            delete _head;
            _head = nullptr;
          }

        image.gif

        5.6再看构造函数

        迭代器初始化

        void emptyInit()
          {
            _head->_next = _head;
            _head->_prev = _head;
          }
            template<class InputIterator>
          List(InputIterator frist, InputIterator lest)
            :_head(new node)
          {
            emptyInit();
            while (frist != lest)
            {
              push_back(*frist);
              frist++;
            }
          }

        image.gif

        五.list模拟实现整体代码

        #pragma once
        #include<iostream>
        #include<cassert>
        using namespace std;
        template<class T>
        struct _list_node
        {
          _list_node( const T& val = T())
            :_val(val),
            _next(nullptr),
            _prev(nullptr)
          {
          }
          T _val;           //存储数据
          _list_node* _next;//后一个结点的指针
          _list_node* _prev;//前一个结点的指针
        };
        /*
        List 的迭代器
        迭代器有两种实现方式,具体应根据容器底层数据结构实现:
          1. 原生态指针,比如:vector
          2. 将原生态指针进行封装,因迭代器使用形式与指针完全相同,因此在自定义的类中必须实现以下方法:
           1. 指针可以解引用,迭代器的类中必须重载operator*()
           2. 指针可以通过->访问其所指空间成员,迭代器类中必须重载oprator->()
           3. 指针可以++向后移动,迭代器类中必须重载operator++()与operator++(int)
            至于operator--()/operator--(int)释放需要重载,根据具体的结构来抉择,双向链表可以向前移动,所以需要重载,如果是forward_list就不需要重载--
           4. 迭代器需要进行是否相等的比较,因此还需要重载operator==()与operator!=()
        */
        //普通迭代器1.0版本
        //template<class T>
        //class _list_iterator
        //{
        //public:
        //  typedef _list_node<T> node;
        //  typedef _list_iterator self;
        //
        //  _list_iterator(node* n)
        //    :_node(n)
        //  {
        //  }
        //
        //  T& operator*()
        //  {
        //    return _node->_val;
        //  }
        //
        //  self operator++(int)
        //  {
        //    self tmp(*this);
        //    _node = _node->_next;
        //    return tmp;
        //  }
        //
        //  self operator++()
        //  {
        //    _node = _node->_next;
        //    return *this;
        //  }
        //
        //  self operator--(int)
        //  {
        //    self tmp(*this);
        //    _node = _node->_prev;
        //    return tmp;
        //  }
        //
        //  self operator--()
        //  {
        //    _node = _node->_prev;
        //    return *this;
        //  }
        //
        //  bool operator!=(const self& it)
        //  {
        //    return _node != it._node;
        //  }
        //
        //  bool operator==(const self& it)
        //  {
        //    return _node == it._node;
        //  }
        //
        //  T*  operator->()
        //  {
        //    return &_node->_val;
        //  }
        //private:
        //  node* _node;
        //};
        //const迭代器1.0版本
        //template<class T>
        //class const_list_iterator
        //{
        //public:
        //  typedef _list_node<T> node;
        //  typedef const_list_iterator self;
        //
        //  const_list_iterator(node* n)
        //    :_node(n)
        //  {
        //  }
        //
        //  const T& operator*()
        //  {
        //    return _node->_val;
        //  }
        //
        //  self operator++(int)
        //  {
        //    self tmp(*this);
        //    _node = _node->_next;
        //    return tmp;
        //  }
        //
        //  self operator++()
        //  {
        //    _node = _node->_next;
        //    return *this;
        //  }
        //
        //  self operator--(int)
        //  {
        //    self tmp(*this);
        //    _node = _node->_prev;
        //    return tmp;
        //  }
        //
        //  self operator--()
        //  {
        //    _node = _node->_prev;
        //    return *this;
        //  }
        //
        //  bool operator!=(const self& it)
        //  {
        //    return _node != it._node;
        //  }
        //
        //  bool operator==(const self& it)
        //  {
        //    return _node == it._node;
        //  }
        //
        //  const T*  operator->()
        //  {
        //    return &_node->_val;
        //  }
        //private:
        //  node* _node;
        //};
        //迭代器2.0版本
        //T:数据类型,Ref:T&/const T&, Ptr: T*/const T*
        template<class T, class Ref, class Ptr>
        class _list_iterator
        {
        public:
          typedef _list_node<T> node;
          typedef _list_iterator<T, Ref, Ptr> self;
          _list_iterator(node* n)
            :_node(n)
          {
          }
          Ref operator*()
          {
            return _node->_val;
          }
          self operator++(int)
          {
            self tmp(*this);
            _node = _node->_next;
            return tmp;
          }
          Ptr operator->()
          {
            return &_node->_val;
          }
          bool operator!=(const self& it)
          {
            return _node != it._node;
          }
          bool operator==(const self& it)
          {
            return _node == it._node;
          }
          node* GetNodePtr()
          {
            return _node;
          }
        private:
          node* _node;
        };
        template<class T>
        class List
        {
        public:
          typedef _list_node<T> node;
          typedef _list_iterator<T,T&,T*> iterator;
          typedef _list_iterator<T,const T&,const T*> const_iterator;
          void emptyInit()
          {
            _head->_next = _head;
            _head->_prev = _head;
          }
          List()
            :_head(new node)
          {
            emptyInit();
          }
          template<class InputIterator>
          List(InputIterator frist, InputIterator lest)
            :_head(new node)
          {
            emptyInit();
            while (frist != lest)
            {
              push_back(*frist);
              frist++;
            }
          }
          void push_back(const T& val )
          {
            node* newnode = new node(val);
            node* tail = _head->_prev;
            tail->_next = newnode;
            newnode->_next = _head;
            _head->_prev = newnode;
            newnode->_prev = tail;
          }
          bool empty()
          {
            return _head->_next == _head;
          }
          void pop_back()
          {
            //判空
            assert(!empty());
            node* tail = _head->_prev;
            node* newtail = tail->_prev;
            //改变指向
            _head->_next = newtail;
            newtail->_prev = _head;
            //释放结点
            delete tail;
          }
          void insert(iterator pos,const T& val)
          {
            node* newnode = new node(val);
            node* posnode = pos.GetNodePtr();
            node* prevnode = posnode->_prev;
            newnode->_next = posnode;
            newnode->_prev = prevnode;
            prevnode->_next = posnode;
            posnode->_prev = newnode;
          }
          iterator erase(iterator pos)
          {
            node* posnode = pos.GetNodePtr();
            node* prevnode = posnode->_prev;
            node* nextnode = posnode->_next;
            prevnode->_next = nextnode;
            nextnode->_prev = prevnode;
            delete posnode;
            return iterator(nextnode);
          }
          iterator begin()
          {
            //使用哨兵位的下一个结点指针,构造begin
            return iterator(_head->_next);
          }
          iterator end()
          {
            //使用哨兵位结点指针,构造end
            return iterator(_head);
          }
          const_iterator begin()const
          {
            return const_iterator(_head->_next);
          }
          const_iterator end()const
          {
            return const_iterator(_head);
          }
          //清除数据,但是需要保留
          void clear()
          {
            iterator it = begin();
            while (it != end())
            {
              it = erase(it);
            }
          }
          ~List()
          {
            //连同头节点一起释放
            clear();
            delete _head;
            _head = nullptr;
          }
        private:
          node* _head;
        };

        image.gif

        相关文章
        |
        4天前
        |
        存储 算法 C++
        详解C++中的STL(标准模板库)容器
        【4月更文挑战第30天】C++ STL容器包括序列容器(如`vector`、`list`、`deque`、`forward_list`、`array`和`string`)、关联容器(如`set`、`multiset`、`map`和`multimap`)和容器适配器(如`stack`、`queue`和`priority_queue`)。它们为动态数组、链表、栈、队列、集合和映射等数据结构提供了高效实现。选择合适的容器类型可优化性能,满足不同编程需求。
        |
        10天前
        |
        存储 算法 程序员
        C++从入门到精通:2.2.1标准库与STL容器算法深度解析
        C++从入门到精通:2.2.1标准库与STL容器算法深度解析
        |
        11天前
        |
        存储 搜索推荐 C++
        【C++高阶(二)】熟悉STL中的map和set --了解KV模型和pair结构
        【C++高阶(二)】熟悉STL中的map和set --了解KV模型和pair结构
        |
        11天前
        |
        设计模式 C语言 C++
        【C++进阶(六)】STL大法--栈和队列深度剖析&优先级队列&适配器原理
        【C++进阶(六)】STL大法--栈和队列深度剖析&优先级队列&适配器原理
        |
        11天前
        |
        存储 缓存 编译器
        【C++进阶(五)】STL大法--list模拟实现以及list和vector的对比
        【C++进阶(五)】STL大法--list模拟实现以及list和vector的对比
        |
        11天前
        |
        算法 C++ 容器
        【C++进阶(四)】STL大法--list深度剖析&list迭代器问题探讨
        【C++进阶(四)】STL大法--list深度剖析&list迭代器问题探讨
        |
        2月前
        |
        存储 安全 Java
        java集合框架及其特点(List、Set、Queue、Map)
        java集合框架及其特点(List、Set、Queue、Map)
        |
        1月前
        |
        Java
        Java使用List去重的四中方式
        Java使用List去重的四中方式
        19 6
        |
        2月前
        |
        Java
        JAVA——List中剔除空元素(null)的三种方法汇总
        JAVA——List中剔除空元素(null)的三种方法汇总
        |
        2月前
        |
        安全 Java API
        Java并发 - J.U.C并发容器类 list、set、queue
        Queue API 阻塞是通过 condition 来实现的,可参考 Java 并发 - Lock 接口 ArrayBlockingQueue 阻塞 LinkedBlockingQueue 阻塞 ArrayQueue 非阻塞 LinkedQueue 非阻塞