【C++ STL】 list 模拟实现

简介: 本篇将学习 list 的模拟实现,它的主要难点在与迭代器的模拟。

📍前言

本篇将学习 list 的模拟实现,它的主要难点在与迭代器的模拟。


🕺作者: 迷茫的启明星

专栏:《C++初阶》

😘欢迎关注:👍点赞🙌收藏✍️留言


🏇码字不易,你的👍点赞🙌收藏❤️关注对我真的很重要,有问题可在评论区提出,感谢阅读!!!


持续更新中~


🌈STL之list的模拟实现

🎈list_node节点的定义

这里需要使用结构体

在这里需要使用模板,因为数据类型是不确定的

双向链表的结构包括:两个指针和数据

我们在定义节点时还需要初始化

template<class T>
struct list_node
{
    T _data;
    list_node<T>* _next;
    list_node<T>* _prev;
  //初始化
    list_node(const T& val = T())
        //为什么传引用?
        //传引用减少拷贝
        //初始化的值是T的默认构造函数,不能传0
        //T可能是string类型或其他类型
    :_data(val)
    ,_next(nullptr)
    ,_prev(nullptr)
    {}
};


🎈iterator迭代器

迭代器可以理解成指针,但是它比指针复杂多了,指针无外乎地址,而迭代器则是不是指针,却要让它实现指针的功能:++、–、*等操作


🕯️构造函数

我们构造好了一个_node,但是没有给他赋值,就等着外面传过来一个node,把node给给里面的_node即可。


__list_iterator(Node* node)

   :_node(node)

   {}


🕯️*it

我们把迭代器比作指针,方便理解,这个函数就是对“指针”解引用,拿到它的值。注意结果返回引用,减少拷贝


Ref operator*()
{
   return _node->_data;
}


🕯️->

拿到节点的值的地址,因为返回的是一个地址,所以要用指针接收。结果返回一个指针。


Ptr operator->()
{ 
   return &(operator*());
}

🕯️it++/++it

为了实现类似指针的效果,迭代器前置++和后置++分别就要返回没改变的值和改变了的值。


++it

迭代器向后走一步,返回加过的值(还是一个迭代器)

it++

迭代器向后走一步,返回没加过的值(还是一个迭代器)


//++it
iterator& operator++()
{
   _node = _node->_next;
   return *this;
}
// it++
iterator operator++(int)
{
   iterator tmp(*this);//保存原来的值
   _node = _node->_next;
   return tmp;
}


🕯️it–/–it

原因与上同

// --it
iterator& operator--()
{
   _node = _node->_prev;
   return *this;
}
// it--
iterator operator--(int)
{
   iterator tmp(*this);
   _node = _node->_prev;
   return tmp;
}


🕯️!= / ==

仅需判断里面的this是否一样即可


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

🎈list类

🕯️begin()/end()

iterator begin()
{
   return iterator(_head->_next);
}
iterator end()
{
   return iterator(_head);
}


🕯️const_begin()/const_end()

const_iterator begin() const
{
   return const_iterator(_head->_next);
}
const_iterator end() const
{
   return const_iterator(_head);


🕯️构造函数

list()
{
   _head = new Node;
   _head->_next = _head;
   _head->_prev = _head;
}

🕯️insert函数

双向链表的插入,是不是非常简单呢?只需要将新的节点的指针与插入位置前后指针相连即可。

iterator insert(iterator pos, const T& x)
{
   Node* cur = pos._node;
   Node* prev = cur->_prev;
   Node* newnode = new Node(x);
   prev->_next = newnode;
   newnode->_prev = prev;
   newnode->_next = cur;
   cur->_prev = newnode;
   return iterator(newnode);


🕯️erase函数

与Insert函数一样,把需要删除的节点的前后节点绑在一起就完成了。

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 iterator(next);
}


🕯️push_back函数

它有两种方式,既可以自己来实现每一步,也可以直接复用insert函数在末尾插入

void push_back(const T& x)
{
   //Node* tail = _head->_prev;
   //Node* newnode = new Node(x);
    _head          tail  newnode
   //tail->_next = newnode;
   //newnode->_prev = tail;
   //newnode->_next = _head;
   //_head->_prev = newnode;
   insert(end(), x);
}


🕯️push_front函数

它和上面push_back函数讲的一样,不过这是在开头插入


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


🕯️pop_back函数

复用erase函数
void pop_back()
{
   erase(--end());
}

🕯️pop_front函数

复用erase函数

void pop_front()
{
   erase(begin());
}

🎈源码

namespace hxq
{
   template<class T>
   struct list_node
   {
      T _data;
      list_node<T>* _next;
      list_node<T>* _prev;
      list_node(const T& x = T())
         :_data(x)
         , _next(nullptr)
         , _prev(nullptr)
      {}
   };
   template<class T, class Ref, class Ptr>
   struct __list_iterator
   {
      typedef list_node<T> Node;
      typedef __list_iterator<T, Ref, Ptr> iterator;
      typedef bidirectional_iterator_tag iterator_category;
      typedef T value_type;
      typedef Ptr pointer;
      typedef Ref reference;
      typedef ptrdiff_t difference_type;
      Node* _node;
      __list_iterator(Node* node)
         :_node(node)
      {}
      bool operator!=(const iterator& it) const
      {
         return _node != it._node;
      }
      bool operator==(const iterator& it) const
      {
         return _node == it._node;
      }
      Ref operator*()
      {
         return _node->_data;
      }
      //T* operator->() 
      Ptr operator->()
      { 
         return &(operator*());
      }
      // ++it
      iterator& operator++()
      {
         _node = _node->_next;
         return *this;
      }
      // it++
      iterator operator++(int)
      {
         iterator tmp(*this);
         _node = _node->_next;
         return tmp;
      }
      // --it
      iterator& operator--()
      {
         _node = _node->_prev;
         return *this;
      }
      // it--
      iterator operator--(int)
      {
         iterator tmp(*this);
         _node = _node->_prev;
         return tmp;
      }
   };
   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;
      const_iterator begin() const
      {
         return const_iterator(_head->_next);
      }
      const_iterator end() const
      {
         return const_iterator(_head);
      }
      iterator begin()
      {
         return iterator(_head->_next);
      }
      iterator end()
      {
         return iterator(_head);
      }
      list()
      {
         _head = new Node;
         _head->_next = _head;
         _head->_prev = _head;
      }
      void push_back(const T& x)
      {
         //Node* tail = _head->_prev;
         //Node* newnode = new Node(x);
          _head          tail  newnode
         //tail->_next = newnode;
         //newnode->_prev = tail;
         //newnode->_next = _head;
         //_head->_prev = newnode;
         insert(end(), x);
      }
      void push_front(const T& x)
      {
         insert(begin(), x);
      }
      iterator insert(iterator pos, const T& x)
      {
         Node* cur = pos._node;
         Node* prev = cur->_prev;
         Node* newnode = new Node(x);
         prev->_next = newnode;
         newnode->_prev = prev;
         newnode->_next = cur;
         cur->_prev = newnode;
         return iterator(newnode);
      }
      void pop_back()
      {
         erase(--end());
      }
      void pop_front()
      {
         erase(begin());
      }
      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 iterator(next);
      }
   private:
      Node* _head;
   };
}


📍后记

本文主要介绍了STL中双向链表list的模拟实现。


✨通过结构体定义list_node节点,通过模板实现数据类型的不确定性,并对节点进行初始化。


✨利用迭代器iterator实现指针的功能,包括构造函数、解引用、*it、->、前后置++/–、!=和==等操作。


✨在list类中,通过双向链表实现了begin()/end()、const_begin()/const_end()、insert、erase、push_back、push_front、pop_back和pop_front等函数。


✨其中,push_back和push_front函数可以复用insert函数,在末尾和开头插入元素。


✨pop_back和pop_front函数可以复用erase函数,删除末尾和开头的元素。


感谢大家支持!!!


respect!


下篇见!


相关文章
|
11天前
|
编译器 C语言 C++
【c++丨STL】list模拟实现(附源码)
本文介绍了如何模拟实现C++中的`list`容器。`list`底层采用双向带头循环链表结构,相较于`vector`和`string`更为复杂。文章首先回顾了`list`的基本结构和常用接口,然后详细讲解了节点、迭代器及容器的实现过程。 最终,通过这些步骤,我们成功模拟实现了`list`容器的功能。文章最后提供了完整的代码实现,并简要总结了实现过程中的关键点。 如果你对双向链表或`list`的底层实现感兴趣,建议先掌握相关基础知识后再阅读本文,以便更好地理解内容。
17 1
|
24天前
|
算法 C语言 C++
【c++丨STL】list的使用
本文介绍了STL容器`list`的使用方法及其主要功能。`list`是一种双向链表结构,适用于频繁的插入和删除操作。文章详细讲解了`list`的构造函数、析构函数、赋值重载、迭代器、容量接口、元素访问接口、增删查改操作以及一些特有的操作接口如`splice`、`remove_if`、`unique`、`merge`、`sort`和`reverse`。通过示例代码,读者可以更好地理解如何使用这些接口。最后,作者总结了`list`的特点和适用场景,并预告了后续关于`list`模拟实现的文章。
42 7
|
1月前
|
存储 编译器 C++
C++ initializer_list&&类型推导
在 C++ 中,`initializer_list` 提供了一种方便的方式来初始化容器和传递参数,而右值引用则是实现高效资源管理和移动语义的关键特性。尽管在实际应用中 `initializer_list&&` 并不常见,但理解其类型推导和使用方式有助于深入掌握现代 C++ 的高级特性。
21 4
|
2月前
|
存储 编译器 C语言
【c++丨STL】vector的使用
本文介绍了C++ STL中的`vector`容器,包括其基本概念、主要接口及其使用方法。`vector`是一种动态数组,能够根据需要自动调整大小,提供了丰富的操作接口,如增删查改等。文章详细解释了`vector`的构造函数、赋值运算符、容量接口、迭代器接口、元素访问接口以及一些常用的增删操作函数。最后,还展示了如何使用`vector`创建字符串数组,体现了`vector`在实际编程中的灵活性和实用性。
73 4
|
2月前
|
C语言 C++ 容器
【c++丨STL】string模拟实现(附源码)
本文详细介绍了如何模拟实现C++ STL中的`string`类,包括其构造函数、拷贝构造、赋值重载、析构函数等基本功能,以及字符串的插入、删除、查找、比较等操作。文章还展示了如何实现输入输出流操作符,使自定义的`string`类能够方便地与`cin`和`cout`配合使用。通过这些实现,读者不仅能加深对`string`类的理解,还能提升对C++编程技巧的掌握。
86 5
|
2月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
67 2
|
2月前
|
存储 算法 Linux
【c++】STL简介
本文介绍了C++标准模板库(STL)的基本概念、组成部分及学习方法,强调了STL在提高编程效率和代码复用性方面的重要性。文章详细解析了STL的六大组件:容器、算法、迭代器、仿函数、配接器和空间配置器,并提出了学习STL的三个层次,旨在帮助读者深入理解和掌握STL。
66 0
|
27天前
|
存储 编译器 C语言
【c++丨STL】vector模拟实现
本文深入探讨了 `vector` 的底层实现原理,并尝试模拟实现其结构及常用接口。首先介绍了 `vector` 的底层是动态顺序表,使用三个迭代器(指针)来维护数组,分别为 `start`、`finish` 和 `end_of_storage`。接着详细讲解了如何实现 `vector` 的各种构造函数、析构函数、容量接口、迭代器接口、插入和删除操作等。最后提供了完整的模拟实现代码,帮助读者更好地理解和掌握 `vector` 的实现细节。
36 0
|
7月前
|
安全 Java
java线程之List集合并发安全问题及解决方案
java线程之List集合并发安全问题及解决方案
1084 1
|
6月前
|
Java API Apache
怎么在在 Java 中对List进行分区
本文介绍了如何将列表拆分为给定大小的子列表。尽管标准Java集合API未直接支持此功能,但Guava和Apache Commons Collections提供了相关API。