【c++】list 模拟

简介: 【c++】list 模拟

> 作者简介:დ旧言~,目前大二,现在学习Java,c,c++,Python等

> 座右铭:松树千年终是朽,槿花一日自为荣。

> 目标:能手撕list模拟

> 毒鸡汤:不为模糊不清的未来过分担忧,只为清清楚楚的现在奋发图强。

> 望小伙伴们点赞👍收藏✨加关注哟💕💕



🌟前言

       前面我们已经学习了list的相关知识点,必然我们要简单的模拟一下,模拟list类比较复杂,里面掺杂了我们学习双链表的知识点,对模板的使用更加复杂,还有对c++类和对象的知识点使用起来更杂。对上面知识点忘了的小伙伴们大家先去巩固巩固,学习list那就更加轻松,那咱们走起。



⭐主体

这里我们就不分解成三文件啦,这里就创建两个文件List.h(头文件),Test.c(测试代码文件)



🌙list基本框架结构

list为任意位置插入删除的容器,底层为带头双向循环链表

模拟实现list,要实现下列三个类:

①、模拟实现结点类

②、模拟实现迭代器的类

③、模拟list主要功能的类



🌙list的结点的实现

list为任意位置插入删除的容器,底层为带头双向循环链表,所以每个结点都需有以下成员:

  1. 前驱指针
  2. 后继指针
  3. data值存放数据

结点类本质上是一个初始化函数,在list类中只需要一个构造函数就行。

// 初始化(结点类)
template<class T> // 模板
struct ListNode
{
  // 定义两个指针
  ListNode<T>* _next;  // 前驱指针 
  ListNode<T>* _prev;  // 后驱指针
  // 存储节点值
  T _data; 
 
  // 初始化列表(构造函数)
  ListNode(const T& x = T())
    :_next(nullptr)
    ,_prev(nullptr)
    ,_data(x)
  {}
};

问题解剖:为什么是ListNode<T>???


首先,C++中用struct定义时可不加struct,重点是这里用了一个类模板,类模板的类名不是真正的类型且类模板不支持自动推类型,即ListNode不是真正的类型,定义变量时ListNode<T>这种才是真正的类型,也就是用类模板定义变量时必须 指定对应的类型 。 注:结构体模板或类模板在定义时可以不加 <T>,但使用时必须加 <T>。


🌙list的迭代器类的实现

  1. 链表的物理空间是不连续的,是通过结点的指针顺次链接
  2. 不能像先前的string和vector一样直接解引用去访问其数据,
  3. 结点的指针解引用还是结点,结点指针++还是结点指针,
  4. 在string和vector的物理空间是连续的,所以这俩不需要实现迭代器类,可以直接使用。


为了能让list像vector一样解引用后访问对应节点中的值,++访问到下一个数据,我们需要单独写一个迭代器类的接口实现,在其内部进行封装补齐相应的功能,而这就要借助运算符重载来完成。


💫基本框架

框架结构:

template<class T,class Ref,class Ptr>
struct __list_iterator
{
    typedef __list_node<T> Node;
    typedef __list_iterator<T, Ref, Ptr> Self;
    Node* _node;
}


①、迭代器类模板为什么有三个参数?

  1. 普通迭代器的话,一个class T参数就够了,由于const迭代器原因,需要加两个参数
  2. 两个参数名Ref(reference:引用)和Ptr(pointer:指针)


②、迭代器类是什么?

  • 迭代器类就一个节点的指针变量_node,但是因为我们要运算符重载等一系列操作,不得不把list的迭代器写成类,完成那些操作,list的迭代器才能正确的++到下一位置,解引用访问节点的值


③、节点指针和迭代器的区别?

  1. Node* cur(节点指针)  和 iterator it(迭代器),它们指向同一个节点,它们的物理地址是一样的,但是它们的类型不同。
  2. *cur是一个指针的解引用,取到的值是节点。
  3. *it是去调用迭代器的operator*,返回值是节点中存的值。
  4. 类型决定了对空间的解释权。


💫构造函数

迭代器的构造函数只需要一个指针构造就行:

// 构造函数(初始化)
_list_iterator(Node* x)
  :_node(x)
{}


💫operator*

*it(调用的是函数,返回节点中的值):

// *it,返回节点的值
Ref operator*()
{
  return _node->_data;
}


问题解剖:返回值为什么是 Ref ???

  1. Ref是模板参数,因为迭代器类的模板参数Ref传入的要么是T&要么是const T&
  2. 为了const迭代器和普通迭代器的同时实现,意义就是一个只读,一个可读可写


注:比如之前讲的vector的迭代器,*it(假设it是迭代器变量)就是拿到对应的值,那么list的迭代器也要同理,解引用迭代器就是为了访问对应位置的值,那么list只要通过迭代器返回对应节点的值就好了(*it,我们是就想要对应的值)


💫operator->

// 返回节点值
Ptr operator->()
{
  return &_node->data;
}


问题解剖:为什么需要operator->?

  • list存了个自定义类型的Date类,程序错误,因为我们并没有重载Date类的operator<<


若是内置类型的话才可以正常输出,那写一个operator<<重载吗?不,因为你无法确定要用哪些类,也不能每个类都写operator<<,那怎么办?我们访问Date类本质是想访问它内置类型(int)的_year、_month和_day吧,那我们不妨写个专属于自定义类型的operator->(因为内置类型只需要*it就可以直接输出了,但自定义类型不可以直接输出)


  • 利用operator->直接访问类的成员变量,而内置类型可以直接输出

故从根源上解决问题: 在迭代器中实现个operator->:

(Ptr是迭代器的模板参数,我们用来作为T*或const T*的)


💫operator前置++--与后置++--

代码实现:

// ++it
self& operator++()
{
  _node = _node->_next;
  // 返回节点
  return *this;
}
// it++
self operator++(int)
{
  // 拷贝
  self tmp(*this);
  // 向后走一个节点
  _node = _node->_next;
  // 返回
  return tmp;
}
 
// --it
self& operator--()
{
  _node = _node->_prev;
  // 返回节点
  return *this;
}
// it--
self operator--(int)
{
  // 拷贝
  self tmp(*this);
  // 向前走一个节点
  _node = _node->prev;
  // 返回
  return tmp;
}


问题解剖:迭代器++对于list是什么意思???

  1. 迭代器++的意思就是想让其指向下一个节点
  2. --正好相反,为了区分前置和后置++(--),
  3. 我们会用函数重载,也就是多一个'没用的'参数:int,这个参数没什么用,只是为了区分++与--


💫operator==和operator!=

代码实现:

// 判断
bool operator!=(const self& s)
{
  return _node != s._node;
}
// 判断
bool operator==(const self& s)
{
  return _node == s._node;
}


问题解剖:两个迭代器怎么比较的???

  1. 迭代器中就一个成员变量_node,节点指针,只要比较当前的节点指针是否相同即可
  2. 这个操作在判断迭代器是否走到头有用(比如 it != s.end())


问题:迭代器的拷贝构造和赋值和析构函数需我们自己实现吗?

:不需要

  • 因为迭代器存的就是一个节点指针,而节点是属于链表list的,那么它的释放应由list来实现,那么迭代器的析构也无需我们自己写了。
  • 拷贝构造和赋值就是节点指针的拷贝和赋值,即使指向同一节点了,迭代器也不会析构,而list的析构到时候只会释放这个节点一次,不存在什么问题,即我们无需深拷贝,使用系统提供的浅拷贝即可。


🌙list类的实现

在结点类和迭代器类都实现的前提下,就可实现list主要功能:增删等操作的实现。


💫基本框架

框架结构:

//3、链表类
template<class T>
class list
{
    typedef __list_node<T> Node;
public:
    typedef __list_iterator<T,T&,T*> iterator;
    typedef __list_iterator<T,const T&,const T*> const_iterator;
 
private:
    Node* _head;//头结点
}


问题解剖:const_iterator(const迭代器的介绍)

  1. 我们知道const_iterator在begin()和end()中的返回值是需要用到的
  2. 其主要作用就是当迭代器只读时使用
  3. 普通迭代器和const迭代器的实现区别仅仅在于内部成员函数的返回值不同
  4. 一个是引用class Ref(T&或const T&),一个是指针class Ptr(T*或const T*)
  5. 当Ref时const T&就是const迭代器的调用,当Ref时T& 时就是普通迭代器的调用


💫构造函数

框架结构:

// 初始化函数
void empty_init()
{
  _head = new Node;
  _head->_next = _head;
  _head->_prev = _head;
}
 
// 构造函数
list() // 1.
{
  empty_init();
}


解释:我们开辟一个头结点,这里就是一个循环双向链表。


begin和end

代码实现:

// 找头,这里有哨兵位
iterator begin()
{
  return _head->_next;
}
// 找尾,哨兵位就是尾
iterator end()
{
  return _head;
}
 
//const类
const_iterator begin() const
{
  return _head->_next;
}
const_iterator end() const
{
  return _head;
}


💫拷贝构造

代码实现:

传统写法:

//拷贝构造:传统写法
list(const list<T>& lt)
{
    _head = new Node;//开辟一样的头结点
    _head->_next = _head;
    _head->_prev = _head;
    //1、用迭代器遍历
    /*const_iterator it = lt.begin();
    while (it != lt.end())
    {
        push_back(*it);
        ++it;
    }*/
 
    //2、范围for遍历
    //遍历lt1,把lt1的元素push_back到lt2里头
    for (auto e : lt)
    {
        push_back(e);//自动开辟新空间,完成深拷贝
    }
}


解释:list的拷贝构造跟vector不同,它的拷贝是拷贝一个个节点(因为不连续的物理空间), 那么我们可以用迭代器拿到list的一个个节点【上面是传统写法】

现代写法:

template<class InputIterator>
list(InputIterator first, InputIterator last) {
  _head = new Node();
  _head->_next = _head;
  _head->_prev = _head;
 
  while (first != last) {
    push_back(*first);
    first++;
  }
}
/* 拷贝构造(现代写法):L2(L1) */
list(const list<T>& L) {
  _head = new Node();
  _head->_next = _head;
  _head->_prev = _head;
 
  list<T> tmp(L.begin(), L.end());
  swap(_head, tmp._head);  // 交换两个结点的指针
}


💫clear函数

代码实现:

// 销毁函数
void clear()
{
  // 找头
  iterator it = begin();
  // 循环
  while (it != end())
  {
    it = erase(it);
  }
}


问题解剖:it = erase(it)什么意思???

防止迭代器失效,因为erase返回的是被删除位置的下一位置,比如删除pos位置的,pos位置被删除后,这个位置的迭代器就失效了,那就无法通过++找到后面的位置了,所以我们通过erase返回值来更新一下迭代器位置,即更新到被删除位置的下一位置


💫operator=

代码实现:

        
  //赋值运算符重载(传统写法)
  //lt1 = lt3
  //list<T>& operator=(const list<T>& lt)
  //{
  //  if (this != &lt)
  //  {
  //    clear();//先释放lt1
  //    for (auto e : lt)
  //      push_back(e);
  //  }
  //  return *this;
  //}
 
  //赋值运算符重载(现代写法)
    // 赋值运算重载
    list<T>& operator=(list<T> lt)
    {
      swap(lt);
      return *this;
    }


注释:套用传值传参拷贝构造完成深拷贝


💫析构函数

代码实现:

// 析构函数
~list()
{
  clear();
 
  delete _head;//删除除头结点以外的节点
  _head = nullptr;//删去哨兵位头结点
 
}


💫insert函数

代码实现:

// insert插入函数
iterator insert(iterator pos, const T& x)
{
  Node* cur = pos._node; // 迭代器pos处的结点指针
  Node* prev = cur->_prev;
  Node* newnode = new Node(x); // 创建新的结点 
 
  // prev newnode cur
  prev->_next = newnode;
  newnode->_prev = prev;
  newnode->_next = cur;
  cur->_prev = newnode;
 
  //return iterator(newnode);
  return newnode;
}


问题解剖:返回参数为什么是iterator???本质是为了防止迭代器失效问题

注:insert指的是插入到指定位置的前面


💫push_back 和 push_front

代码实现:

// 调用插入函数,尾插
void push_back(const T& x)
{
 
  insert(end(), x);
}
// 调用插入函数,头插
void push_front(const T& x)
{
  insert(begin(), x);
}


💫erase函数

代码实现:

//erase
iterator erase(iterator pos)
{
  assert(pos != end());
  Node* cur = pos._node;
  Node* prev = cur->_prev;
  Node* next = cur->_next;
  //prev cur next
  //链接prev和next
  prev->_next = next;
  next->_prev = prev;
  //delete删去结点,因为每一个节点都是动态开辟出来的
  delete cur;
  //返回被删除元素后一个元素的迭代器位置
  //return next;
  return iterator(next);
}


问题剖析:返回值问题???

erase的返回值,返回的是被删除位置的下一位置,库里面这么规定的,本质是因为删除元素一定会导致此位置的迭代器失效,故返回被删除位置的下一次位置可以更新迭代器。


💫pop_back 和 pop_front

代码实现:

// 调用删除函数,尾删
void pop_back()
{
  erase(--end());
}
// 调用删除函数,头删
void pop_front()
{
  erase(begin());
}


🌙完整源代码

list.h:

#pragma once
 
#include<assert.h>
#include<iostream>
 
namespace lyk
{
  // 初始化
  template<class T> // 模板
  struct ListNode
  {
  // 定义两个指针
  ListNode<T>* _next;  // 前驱指针 
  ListNode<T>* _prev;  // 后驱指针
  // 存储节点值
  T _data; 
 
  // 初始化列表(构造函数)
  ListNode(const T& x = T())
    :_next(nullptr)
    ,_prev(nullptr)
    ,_data(x)
  {}
  };
 
  // list迭代器
  template<class T,class Ref,class Ptr> // 第二个参数是引用,第三个参数是指针,防止迭代器失效
  struct _list_iterator
  {
    typedef ListNode<T> Node;
    typedef _list_iterator<T, Ref, Ptr> self;
    Node* _node; // 定义节点
 
    // 构造函数(初始化)
    _list_iterator(Node* x)
      :_node(x)
    {}
 
    // ++it
    self& operator++()
    {
      _node = _node->_next;
      // 返回节点
      return *this;
    }
    // it++
    self operator++(int)
    {
      // 拷贝
      self tmp(*this);
      // 向后走一个节点
      _node = _node->_next;
      // 返回
      return tmp;
    }
 
    // --it
    self& operator--()
    {
      _node = _node->_prev;
      // 返回节点
      return *this;
    }
    // it--
    self operator--(int)
    {
      // 拷贝
      self tmp(*this);
      // 向前走一个节点
      _node = _node->prev;
      // 返回
      return tmp;
    }
 
    // *it,返回节点的值
    Ref operator*()
    {
      return _node->_data;
    }
 
    // 返回节点值
    Ptr operator->()
    {
      return &_node->data;
    }
 
    // 判断
    bool operator!=(const self& s)
    {
      return _node != s._node;
    }
    // 判断
    bool operator==(const self& s)
    {
      return _node == s._node;
    }
  };
 
  // list的实现(包含各种函数实现)
  template<class T>
  class list
  {
    typedef ListNode<T> Node;
  public:
    typedef _list_iterator<T, T&, T*> iterator;
    typedef _list_iterator<T, const T&, const T*> const_iterator;
    
    // 找头,这里有哨兵位
    iterator begin()
    {
      return _head->_next;
    }
    // 找尾,哨兵位就是尾
    iterator end()
    {
      return _head;
    }
 
    //const类
    const_iterator begin() const
    {
      return _head->_next;
    }
    const_iterator end() const
    {
      return _head;
    }
 
    // 初始化函数
    void empty_init()
    {
      _head = new Node;
      _head->_next = _head;
      _head->_prev = _head;
    }
 
    // 构造函数
    list() // 1.
    {
      empty_init();
    }
    list(list<T>& It) // 2.拷贝构造 传统写法
    {
      empty_init();
 
      // 尾插
      for (const auto& x : It)
      {
        push_back(x);
      }
    }
    /* 拷贝构造(现代写法):L2(L1) */
    list(const list<T>& L) {
      _head = new Node();
      _head->_next = _head;
      _head->_prev = _head;
 
      list<T> tmp(L.begin(), L.end());
      swap(_head, tmp._head);  // 交换两个结点的指针
    }
 
    // 销毁函数
    void clear()
    {
      // 找头
      iterator it = begin();
      // 循环
      while (it != end())
      {
        it = erase(it);
      }
    }
 
    // 析构函数
    ~list()
    {
      clear();
 
      delete _head;
      _head = nullptr;
    }
 
    // 交换
    void swap(list<T>& tmp)
    {
      std::swap(_head, tmp._head);
    }
 
    // 赋值运算重载
    list<T>& operator=(list<T> lt)
    {
      swap(lt);
      return *this;
    }
 
    // 调用插入函数,尾插
    void push_back(const T& x)
    {
 
      insert(end(), x);
    }
    // 调用插入函数,头插
    void push_front(const T& x)
    {
      insert(begin(), x);
    }
 
    // 调用删除函数,尾删
    void pop_back()
    {
      erase(--end());
    }
    // 调用删除函数,头删
    void pop_front()
    {
      erase(begin());
    }
 
    // insert插入函数
    iterator insert(iterator pos, const T& x)
    {
      Node* cur = pos._node;
      Node* prev = cur->_prev;
      Node* newnode = new Node(x);
 
      // prev newnode cur
      prev->_next = newnode;
      newnode->_prev = prev;
      newnode->_next = cur;
      cur->_prev = newnode;
 
      //return iterator(newnode);
      return newnode;
    }
 
    // erase删除函数
    iterator erase(iterator pos)
    {
      assert(pos != end());
 
      Node* cur = pos._node;
      Node* prev = cur->_prev;
      Node* next = cur->_next;
      prev->_next = next;
      next->_prev = prev;
 
      delete cur;
 
      return next;
    }
 
  private:
    Node* _head;  // 头节点
  };
 
  void test_list1()
  {
    list<int> lt;
    lt.push_back(1);
    lt.push_back(2);
    lt.push_back(3);
    lt.push_back(4);
 
    list<int>::iterator it = lt.begin();
    while (it != lt.end())
    {
      //*it += 10;
 
      cout << *it << " ";
      ++it;
    }
    cout << endl;
 
    for (auto e : lt)
    {
      cout << e << " ";
    }
    cout << endl;
  }
 
  void test_list2()
  {
    list<int> lt;
    lt.push_back(1);
    lt.push_back(2);
    lt.push_back(3);
    lt.push_back(4);
 
 
    for (auto e : lt)
    {
      cout << e << " ";
    }
    cout << endl;
 
    lt.push_back(5);
    lt.push_front(0);
 
    for (auto e : lt)
    {
      cout << e << " ";
    }
    cout << endl;
 
    lt.pop_back();
    lt.pop_front();
 
    for (auto e : lt)
    {
      cout << e << " ";
    }
    cout << endl;
 
    lt.clear();
 
    for (auto e : lt)
    {
      cout << e << " ";
    }
    cout << endl;
 
    lt.push_back(10);
    lt.push_back(20);
 
    for (auto e : lt)
    {
      cout << e << " ";
    }
    cout << endl;
  }
 
  void test_list3()
  {
    list<int> lt;
    lt.push_back(1);
    lt.push_back(2);
    lt.push_back(3);
    lt.push_back(4);
 
    for (auto e : lt)
    {
      cout << e << " ";
    }
    cout << endl;
 
    list<int> copy(lt);
    for (auto e : copy)
    {
      cout << e << " ";
    }
    cout << endl;
 
    list<int> lt1;
    lt1.push_back(10);
    lt1.push_back(20);
    lt1.push_back(30);
    lt1.push_back(40);
 
    lt = lt1;
    for (auto e : copy)
    {
      cout << e << " ";
    }
    cout << endl;
  }
 
  void print_list(const list<int>& lt)
  {
    list<int>::const_iterator it = lt.begin();
    while (it != lt.end())
    {
      //*it += 10;
 
      cout << *it << " ";
      ++it;
    }
    cout << endl;
 
    for (auto e : lt)
    {
      cout << e << " ";
    }
    cout << endl;
  }
 
  void test_list4()
  {
    list<int> lt;
    lt.push_back(1);
    lt.push_back(2);
    lt.push_back(3);
    lt.push_back(4);
 
    print_list(lt);
 
 
    list<int>::iterator it = lt.begin();
    while (it != lt.end())
    {
      *it += 10;
 
      cout << *it << " ";
      ++it;
    }
    cout << endl;
  }
}


test.cpp:

#define _CRT_SECURE_NO_WARNINGS 1
 
// 包含头文件
#include<iostream>
#include<list>
#include<vector>
#include<algorithm>
using namespace std;
 
#include"List.h"
 
int main()
{
 
  lyk::test_list4();
 
  return 0;
}


🌟结束语

      今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。


目录
相关文章
|
10天前
|
存储 缓存 C语言
【C++】list介绍以及模拟实现(超级详细)
【C++】list介绍以及模拟实现(超级详细)
29 5
|
6天前
|
存储 缓存 算法
【C++】list的模拟实现
【C++】list的模拟实现
|
6天前
|
存储 C++ 容器
【C++】list的认识与使用
【C++】list的认识与使用
|
1月前
|
存储 Java C++
【c++】list详细讲解
【c++】list详细讲解
20 5
|
1月前
|
存储 编译器 C语言
【C++】list模拟实现
本文档介绍了C++ STL中`list`容器的模拟实现,包括`ListNode`节点类、迭代器类和`list`类的详细设计。`ListNode`模板类存储数据并维护前后指针;`ListIterator`是一个复杂的模板类,提供解引用、自增/自减以及比较操作。`list`类包含了链表的各种操作,如插入、删除、访问元素等,并使用迭代器作为访问接口。实现中,迭代器不再是简单的指针,而是拥有完整功能的对象。此外,文档还提到了迭代器的实现对C++语法的特殊处理,使得`it-&gt;_val`的写法成为可能。文章通过分步骤展示`list`的各个组件的实现,帮助读者深入理解STL容器的内部工作原理。
|
1月前
|
算法 搜索推荐 C++
【C++】list的使用(下)
`C++` 中 `std::list` 的 `merge()`、`sort()` 和 `reverse()` 操作: - `merge(x)` 和 `merge(x, comp)`: 合并两个已排序的`list`,将`x`的元素按顺序插入当前`list`,`x`清空。比较可自定义。 - `sort()` 和 `sort(comp)`: 对`list`元素排序,保持等价元素相对顺序。内置排序基于稳定排序算法,速度较慢。 -reverse(): 反转`list`中元素的顺序。 这些操作不涉及元素构造/销毁,直接移动元素。注意,`sort()`不适合`std::list`,因链表结构不利于快速排序
|
1月前
|
C++ 容器
【C++】list的使用(下)
这篇博客探讨了C++ STL中`list`容器的几个关键操作,包括`splice()`、`remove()`、`remove_if()`和`unique()`。`splice()`允许高效地合并或移动`list`中的元素,无需构造或销毁。`remove()`根据值删除元素,而`remove_if()`则基于谓词移除元素。`unique()`则去除连续重复的元素,可选地使用自定义比较函数。每个操作都附带了代码示例以说明其用法。
|
1月前
|
编译器 C++ 容器
【C++】list的使用(上)
迭代器在STL中统一了访问接口,如`list`的`begin()`和`end()`。示例展示了如何使用正向和反向迭代器遍历`list`。注意`list`的迭代器不支持加减操作,只能用`++`和`--`。容器的`empty()`和`size()`用于检查状态和获取元素数。`front()`和`back()`访问首尾元素,`assign()`重载函数用于替换内容,`push_*/pop_*`管理两端元素,`insert()`插入元素,`erase()`删除元素,`resize()`调整大小,`clear()`清空容器。这些接口与`vector`和`string`类似,方便使用。
|
1月前
|
存储 C++
C++的list-map链表与映射表
```markdown C++ 中的`list`和`map`提供链表和映射表功能。`list`是双向链表,支持头尾插入删除(`push_front/push_back/pop_front/pop_back`),迭代器遍历及任意位置插入删除。`map`是键值对集合,自动按键排序,支持直接通过键来添加、修改和删除元素。两者均能使用范围for循环遍历,`map`的`count`函数用于统计键值出现次数。 ```
20 1
|
2月前
|
编译器 C语言 C++
C++ STL中list迭代器的实现
C++ STL中list迭代器的实现
C++ STL中list迭代器的实现