【C++】list的模拟实现(下)

简介: 【C++】list的模拟实现(下)

4. list的构造函数


在文章的最开始,我们为了让自己的list类能够跑起来,简易的实现了一个list的构造函数,现在我们对这个构造进行一个修改

我们在后面会经常用到空初始化,参照库里面的实现,我们也实现一个empty_initialize

void empty_initialize()
{
    _head = new node(T());
    _head->_next = _head;
    _head->_prev = _head;
}


接下来就要正式实现构造函数了

4c1b056b2cce21460e8be55f1e5e66e2.png


  • 默认构造,默认构造的功能和我们刚刚实现的empty_initialize的功能刚好一模一样,所以直接复用即可。
  • 指定值与长度的构造,首先复用empty_initialize,然后再尾插新元素即可,这里的尾插也可以复用push_back。
  • 迭代器区间构造,同样的复用empty_initialize,然后遍历迭代器区间,尾插新元素。
  • 对于拷贝构造,复用empty_initialize构造一个空链表,然后遍历参数中的链表,拿到每个元素依次尾插到空链表中(也可以使用现代写法:使用参数中链表的迭代器构造一个对象,然后使用swap交换临时对象和this指针指向的对象)
list()//默认构造
{
    empty_initialize();
}
list(size_t n, const T& val = T())//指定值与长度的构造
{
    empty_initialize();
    for (size_t i = 0; i < n; ++i)
    {
        push_back(val);
    }
}
template<class InputIterator>
list(InputIterator first, InputIterator last)//迭代器区间构造
{
    empty_initialize();
    while (first != last)
    {
        push_back(*first);
        ++first;
    }
}
list(const list<T>& lt)//拷贝构造的经典写法
{
    empty_initialize();
    for (const auto& e : lt)
    {
        push_back(e);
    }
}
void swap(list<T>& lt)//拷贝构造的现代写法
{
    std::swap(_head, lt._head);
}
list(const list<T>& lt)
{
    empty_initialize();
    list<T> tmp(lt.begin(), lt.end());
    swap(tmp);
}


5. 析构函数和赋值重载


关于默认的成员函数,现在还剩下析构函数和复制重载

1.赋值重载

和之前的vector一样,赋值重载也有经典写法和现代写法两种实现方式。

1. 经典写法

首先做一个判断,防止自己给自己赋值,这情况在经典写法下是致命的。然后清空this指向的list对象,然后遍历右操作数对象,然后尾插到this指向的对象。

list<T>& operator=(list<T>& lt)//经典写法
{
    if (this != *lt)//防止自己给自己赋值
    {
        clear();
        for (auto& e : lt)
        {
            push_back(e);
        }
    }
}


2. 现代写法

同样的通过传参的过程中,利用传值传参的方式调用拷贝构造,构造出一个形参lt,然后使用swap交换this指针和lt。

list<T>& operator=(list<T> lt)//现代写法
{
    swap(lt);
    return *this;
}


2.析构函数和clear

1. 析构函数

对于list,析构函数要做的事情就是释放list中的每个节点的空间,然后释放头节点的空间,这个功能让我们想到了clear函数,它的功能是释放除了头节点之外的所有节点,所以我们可以考虑在析构函数中复用clear函数,然后释放头节点的空间即可。


2. clear

对于clear删除节点,我们直接调用erase删除即可,可以删除任意位置节点,由于list结构的原因,任意位置的节点删除效率相同,所以可以任意删除,直到list为空为止。

bool empty()//这里顺手实现一下empty函数,当list中头尾节点都是自己的时候,就说明list已经空了。当_head的next或prev中的任意一个指向_head本身即可判定list已经空了。
{
    return _head->_next == _head;
}
void clear()
{
    while (!empty())
    {
        erase(--end());
    }
}
~list()
{
    clear();
    delete _head;
    _head = nullptr;
}


6.size

对于list的结构,按照我们之前实现过的情况,如果我们想知道list的元素个数,我们只能通过遍历计数得到,那么size的时间复杂度就是O(n)了,这样复杂度太高了,所以这里准备在list的成员变量中再增加一个size,表示元素个数。

class list
{
public:
    size_t size()
    {
        return _size;
    }
private:
    node* _head;
    size_t _size;
}

增加这个成员变量之后,我们整个结构中可能要大改了,要增加size的初始化和操作


这个时候,我们复用的优点就显现出来了,由于所有的构造我们都复用了empty_initialize,所有的插入都复用了insert,删除都复用了erase,所以只需要更改empty_initialize,insert,erase中的实现即可,这就是代码复用的有点:提高了代码后期的可维护性


7. 数据操作

1.inset

首先,我们首先insert,这个是整个list中需要实现插入操作都要复用的函数,所以非常重要。


首先new出来一个存放执行值的节点,然后将这个节点连接到list的指定位置上,insert操作就完成啦。当然insert之后,迭代器就失效了,所以我们要返回指向newnode节点的迭代器。别忘了++_size

iterator insert(iterator pos, const T& val = T())
{
    node* newnode = new node(val);
    //   prev   newnode   cur
    node* prev = pos._pnode->_prev;
    node* cur = pos._pnode;
    prev->_next = newnode;
    newnode->_prev = prev;
    newnode->_next = cur;
    cur->_prev = newnode;
    ++_size;
    return iterator(newnode);
}


2.push_front和push_back

这两个接口都是可以复用insert的,只是传入插入的位置不同,我们直接指定插入位置即可

由于insert是在pos位置之前插入,所以这里注意一下,end返回的是最后一个元素的下一个位置,所以push_back穿的位置为end即可。

void push_back(const T& val = T())
{
    insert(end(), val);
}
void push_front(const T& val = Y())
{
    insert(begin(), val);
}


3.erase

找到pos位置的前后两个节点,然后将两个节点连接起来,最后释放pos指向的节点即可。注意别忘了–_size

iterator erase(iterator pos)
{
    assert(pos != end());
    node* prev = pos._pnode->_prev;
    node* next = pos._pnode->_next;
    //prev    cur    next
    prev->_next = next;
    next->_prev = prev;
    delete pos._pnode;
    --_size;
    return iterator(next);
}


4.pop_back和pop_front

这里复用erase即可

void pop_back()
{
    erase(--end());
}
void pop_front()
{
    erase(begin());
}


5.resize

resize的实现原则和vector类似。但是对于list只有两种情况

  • 当n小于size时,尾删节点直到size==n
  • 当n大于size时,尾插节点直到size==n
void resize(size_t n, const T& val = T())
{
    while (n < size())
    {
        pop_back();
    }
    while (n > size())
    {
        push_back(val);
    }
}


6.整体代码


最后,附上整体的代码

#pragma once
#include <utility>
namespace my
{
  template<class T>
  struct __list_node//这是一个list的节点类模板
  {
    __list_node* _prev;
    __list_node* _next;
    T _data;
    __list_node(const T& data = T())//__list_node的默认构造函数
      :_data(data)
      , _prev(nullptr)
      , _next(nullptr)
    {}
  };
  template<class T, class Ref, class Ptr>
  struct __list_iterator
  {
    typedef __list_node<T> node;//重命名节点类
    typedef __list_iterator<T, Ref, Ptr> Self;
    node* _pnode;//迭代器的成员变量就是结点指针
    __list_iterator(node* p)//迭代器的构造函数
      :_pnode(p)
    {}
    Ptr operator->()
    {
      return &operator*();
    }
    Ref operator*()//重载*,作用是返回指向的节点的元素值
    {
      return _pnode->_data;
    }
    Self& operator++()//重载++,作用是将迭代器自增1
    {
      _pnode = _pnode->_next;
      return *this;
    }
    Self operator++(int)
    {
      Self tmp(*this);
      _pnode = _pnode->_next;
      return tmp;
    }
    Self& operator--()
    {
      _pnode = _pnode->_prev;
      return *this;
    }
    Self operator--(int)
    {
      Self tmp(*this);
      _pnode = _pnode->_prev;
      return tmp;
    }
    bool operator!=(const Self& it)
    {
      return _pnode != it._pnode;
    }
    bool operator==(const Self& it)
    {
      return _pnode == it._pnode;
    }
  };
  template<class T>
  class list//这是一个list类,其中的成员变量只有一个头节点。
  {
    typedef __list_node<T> node;
  public:
    typedef __list_iterator<T, T&, T*> iterator;//普通迭代器
    typedef __list_iterator<T, const T&, const T*> const_iterator;//const迭代器
    //typedef __list_const_iterator<T> const_iterator;//const迭代器
    iterator begin()
    {
      return _head->_next;
    }
    iterator end()
    {
      return _head;
    }
    const_iterator begin() const
    {
      return _head->_next;
    }
    const_iterator end() const
    {
      return _head;
    }
    void empty_initialize()
    {
      _head = new node(T());
      _head->_next = _head;
      _head->_prev = _head;
      _size = 0;
    }
    list()
    {
      empty_initialize();
    }
    list(size_t n, const T& val = T())
    {
      empty_initialize();
      for (size_t i = 0; i < n; ++i)
      {
        push_back(val);
      }
    }
    template<class InputIterator>
    list(InputIterator first, InputIterator last)
    {
      empty_initialize();
      while (first != last)
      {
        push_back(*first);
        ++first;
      }
    }
    //list(const list<T>& lt)//经典写法
    //{
    //    empty_initialize();
    //    for (const auto& e : lt)
    //    {
    //        push_back(e);
    //    }
    //}
    void swap(list<T>& lt)
    {
      std::swap(_head, lt._head);
    }
    list(const list<T>& lt)//现代写法
    {
      empty_initialize();
      list<T> tmp(lt.begin(), lt.end());
      swap(tmp);
    }
    list<T>& operator=(list<T>& lt)//经典写法
    {
      if (this != *lt)//防止自己给自己赋值
      {
        clear();
        for (auto& e : lt)
        {
          push_back(e);
        }
      }
    }
    //list<T>& operator=(list<T> lt)//现代写法
    //{
    //  swap(lt);
    //  return *this;
    //}
    bool empty()
    {
      return _head->_next == _head;
    }
    void clear()
    {
      while (!empty())
      {
        erase(--end());
      }
    }
    ~list()
    {
      clear();
      delete _head;
      _head = nullptr;
    }
    void push_back(const T& val = T())
    {
      insert(end(), val);
    }
    void push_front(const T& val = Y())
    {
      insert(begin(), val);
    }
    iterator insert(iterator pos, const T& val = T())
    {
      node* newnode = new node(val);
      //   prev   newnode   cur
      node* prev = pos._pnode->_prev;
      node* cur = pos._pnode;
      prev->_next = newnode;
      newnode->_prev = prev;
      newnode->_next = cur;
      cur->_prev = newnode;
      ++_size;
      return iterator(newnode);
    }
    size_t size()
    {
      return _size;
    }
    iterator erase(iterator pos)
    {
      assert(pos != end());
      node* prev = pos._pnode->_prev;
      node* next = pos._pnode->_next;
      //prev    cur    next
      prev->_next = next;
      next->_prev = prev;
      delete pos._pnode;
      --_size;
      return iterator(next);
    }
    void pop_back()
    {
      erase(--end());
    }
    void pop_front()
    {
      erase(begin());
    }
    void resize(size_t n, const T& val = T())
    {
      while (n < size())
      {
        pop_back();
      }
      while (n > size())
      {
        push_back(val);
      }
    }
  private:
    node* _head;
    size_t _size;
  };
}


相关文章
|
1天前
|
调度 C++ 容器
【C++】手搓 list 容器
本文我们实现了STL库中重要的list 的模拟实现,其中最重要莫过于迭代器的封装类的书写,这是前所未有的操作(对于我来说,我是第一次使用这种结构)。通过list 的模拟实现也帮我们巩固了类与对象的知识,也强化了指针操作的思路。欢迎大家讨论分析。
10 1
|
3天前
|
存储 编译器 C++
【C++/STL】list(常见接口、模拟实现、反向迭代器、)
【C++/STL】list(常见接口、模拟实现、反向迭代器、)
5 0
|
16天前
|
存储 缓存 编译器
【C++进阶(五)】STL大法--list模拟实现以及list和vector的对比
【C++进阶(五)】STL大法--list模拟实现以及list和vector的对比
|
16天前
|
算法 C++ 容器
【C++进阶(四)】STL大法--list深度剖析&list迭代器问题探讨
【C++进阶(四)】STL大法--list深度剖析&list迭代器问题探讨
|
17天前
|
C++
c++的学习之路:16、list(3)
c++的学习之路:16、list(3)
12 0
|
17天前
|
C++
c++的学习之路:15、list(2)
c++的学习之路:15、list(2)
14 0
|
17天前
|
存储 C++ 容器
c++的学习之路:14、list(1)
c++的学习之路:14、list(1)
18 0
|
28天前
|
编译器 C++ 容器
【C++初阶】STL详解(八)List的模拟实现
【C++初阶】STL详解(八)List的模拟实现
39 0
|
28天前
|
存储 C++ 容器
【C++初阶】STL详解(五)List的介绍与使用
【C++初阶】STL详解(五)List的介绍与使用
32 0
|
1月前
|
存储 算法 编译器
【C++初阶】11. list的使用及模拟实现
【C++初阶】11. list的使用及模拟实现
52 3