【STL】模拟实现list类模板 {深度剖析list迭代器,实现list深拷贝}

一、核心结构

template <class T>
struct list_node{ //[1]
  T _data; //[2]
  list_node *_next; //指向下一个节点
  list_node *_prev; //指向前一个节点
  
  list_node(const T &val = T())
    :_data(val),
    _next(nullptr),
    _prev(nullptr)
  {}
};

template <class T>
class Mylist{
  typedef list_node<T> Node; //[3]
  Node *_phead; //[4] 
  //......
};

解释

  • [1] 使用struct公开成员变量,方便Mylist类中直接访问节点数据。
  • [2] 节点中存储的数据是模版类型,使链表可以存储各种类型包括自定义类型的数据。
  • [3] typedef为类型起别名,简化代码,便于后期维护。
  • [4] Mylist类只有一个成员变量,即指向哨兵位节点的指针(Mylist是一个带头双向循环链表)。

结构图示:
在这里插入图片描述


二、迭代器

2.1 结构及构造

//list_iterator
template <class T, class Ref, class Ptr>
struct list_iterator{ //[1]
  typedef list_node<T> Node;
  typedef list_iterator<T, Ref, Ptr> iterator;
  Node *_pnode; //[2]

  list_iterator(Node *pnode) //[3]
    :_pnode(pnode)
  {}

解释:

  • [1] 使用struct公开成员变量,方便Mylist类中直接访问迭代器指针数据。
  • [2] list_iterator的成员变量只有一个指向节点的指针。但其中封装了list迭代器的方法
    1. 解引用返回数据_data的引用
    2. 箭头返回数据_data的地址
    3. ++操作实现了链表指针的在链表上的移动,等等
  • [3] 用一个指向节点的指针构造list迭代器。
  • [4] list_iterator的拷贝构造,赋值重载,析构使用默认生成的即可(内部只有一个指针,且不涉及资源申请)。
//begin() & end()
template <class T>
class Mylist{
  //......
  public:
  //iterator
  typedef list_iterator<T, T&, T*> iterator;
  typedef list_iterator<T, const T&, const T*> const_iterator; //[2]

  iterator begin(){
    return iterator(_phead->_next);  //[1]
  }

  iterator end(){
    return iterator(_phead);
  }

  const_iterator begin() const{ //[3]
    return const_iterator(_phead->_next);
  }

  const_iterator end() const{
    return const_iterator(_phead);
  //......
  }

解释:

  • [1] 这里用指向节点的指针构造匿名对象做返回值。
  • [2] iterator和const_iterator的后两个模版参数不同是两个不同的实例化类型
    • iterator类的*和->操作返回普通引用和普通指针。
    • const_iterator类的*和->操作返回const引用和const指针。
  • [3] 这里的const用于修饰this指针,即调用函数的对象是const Mylist
    1. const对象返回const迭代器,const迭代器的*和->操作的返回值是只读的const引用和const指针。
    2. 也就是说,const迭代器指向的节点不能修改。(类似于常量指针 const int *ptr)

结构图示:
在这里插入图片描述


2.2 operator* & operator->

  Ref operator*() const{ //[3]
    return _pnode->_data; //[1]
  }

  Ptr operator->() const{ //[3]
    return &(_pnode->_data); //[2]
  }

//下面的代码使用->访问对象的成员:
  struct Pos{
    int _row;
    int _col;
    Pos(int row = 0, int col = 0)
      :_row(row),
      _col(col)
    {}
  };

  void Test7(){
    cout << "Test7: " << endl;
    Mylist<Pos> mlt; //链表存储的数据类型是自定义类型
    mlt.push_back(Pos(10,10));
    mlt.push_back(Pos(20,20));

    for(auto it = mlt.begin(); it != mlt.end(); ++it)
    {
      cout << it->_row << ":" << it->_col << endl; //[4]
    }
  }

解释:

  • [1] 迭代器解引用返回节点数据_data的引用。迭代器解引用后可以直接访问修改节点数据。
  • [2] 迭代器的operator->返回节点数据_data的地址。专为自定义类型的_data使用,可以直接使用迭代器加->的方式访问_data的成员。
  • [3] *和->操作涉及普通对象和const对象的权限问题,需要传入Ref和Ptr两个模版参数,泛型返回值
  • [3] 普通对象传入普通引用和普通指针,*和->操作后返回的引用和指针可读可写;const对象传入const引用和const指针,*和->操作后返回的引用和指针只能读不能写
  • [4] 这里其实应该有两个箭头it->->_row
    1. 第一个箭头调用operator->函数返回_data对象的地址。
    2. 第二个箭头用于访问_data对象的成员_row。
    3. 为了提高可读性,在设计c++语法时省略了一个箭头。这里的操作编译器会做特殊处理。

2.3 operator== & operator!=

  bool operator!=(const iterator &it) const{ //[1]
    return _pnode!=it._pnode;
  }

  bool operator==(const iterator &it) const{ //[1]
    return _pnode==it._pnode;
  }

解释:

  • [1] const iterator表示其成员变量_pnode不能改变,也就是说迭代器的指向不能变。(类似于指针常量 int *const ptr)
  • [1] const iterator可以进行解引用,->访问成员变量,进行==/!=的条件判断等。但不能进行++等操作。

2.4 前置++ & 后置++

  iterator& operator++(){
    _pnode = _pnode->_next; //[1]
    return *this;
  }

  iterator operator++(int){
    iterator tmp(*this); //[2]
    _pnode = _pnode->_next;
    return tmp;
  }

解释:

  • [1] list迭代器的++操作不同于vector迭代器(原生指针直接++),需要访问节点的_next将指针指向下一个节点。
  • [2] 后置++需要先拷贝一个临时迭代器,用于返回++之前的结果。因此需要传值返回。

提示:还有前置--和后置--,只需要访问节点的_prev将指针指向前一个节点即可。


三、默认成员函数

3.1 构造

  void empty_initialize(){ //[1]
    _phead = new Node;
    _phead->_next = _phead;
    _phead->_prev = _phead;
  }

  Mylist(){
    empty_initialize();  
  }

  template <typename InputIterator>
  Mylist(InputIterator first, InputIterator last){
    empty_initialize(); //[2]
    while(first!=last)
    {
      push_back(*first);
      ++first;
    }
  }

解释:

  • [1] 这里将空初始化单独拿出来,是为了方便下面多处进行复用
  • [2] 插入数据前必须要进行空初始化,否则会访问野指针崩溃

3.2 拷贝构造 & 赋值重载

  //void swap(Mylist<T> &mlt)
  void swap(Mylist &mlt){ //[1]
    std::swap(_phead, mlt._phead);
  }
  //Mylist<T>(const Mylist<T> &mlt)
  Mylist(const Mylist &mlt){ //[1]
    empty_initialize(); //交换前必须要进行空初始化,否则会访问野指针崩溃
    Mylist tmp(mlt.begin(), mlt.end());
    swap(tmp);
  }

  Mylist& operator=(Mylist mlt){
    swap(mlt);
    return *this;
  }

解释:

  • [1] 定义类成员函数时同类类型的类型名可以省略模版参数,但要注意:
    1. 不同类的类型名不可以省略模版参数。
    2. 在类外定义函数必须写明模版参数。

3.3 析构

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

  ~Mylist(){
    clear(); //清除所有数据节点
    delete _phead; //释放哨兵位节点
  }

注意:一定记得释放哨兵位节点哦!


四、插入删除

4.1 insert

  iterator insert(iterator pos, const T &val){
    Node *cur = pos._pnode; //[1]
    Node *prev = cur->_prev;
    Node *newnode = new Node(val);
    prev->_next = newnode;
    newnode->_prev = prev;
    newnode->_next = cur;
    cur->_prev = newnode;
    return iterator(newnode); //[2]
  }

解释:

  • [1] list_iterator使用struct,Mylist类可以直接访问其成员。
  • [2] 返回新插入节点的迭代器。

4.2 push_back & push_front

  void push_back(const T &val){
    insert(end(), val);
  }

  void push_front(const T &val){
    insert(begin(), val);
  }

4.3 erase

  iterator erase(iterator pos){
    assert(pos != end()); //[1]
    Node *cur = pos._pnode;
    Node *prev = cur->_prev;
    Node *next = cur->_next;
    next->_prev = prev;
    prev->_next = next;
    delete cur;
    return iterator(next); //[2]
  }

解释:

  • [1] 不能删除哨兵位节点。
  • [2] 返回删除节点的下一个节点的迭代器。

4.4 pop_back & pop_front

  void pop_back(){
    assert(_phead->_next != _phead); //[1]
    erase(--end());
  }

  void pop_front(){
    assert(_phead->_next != _phead);
    erase(begin());
  }

注意:pop之前要判空!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

芥末虾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值