C++初阶之一篇文章教会你list(模拟实现)(下)

简介: 4.swapvoid swap(list<T>& x){ std::swap(_head, x._head); // 交换两个链表的头结点指针}这是 list 类的成员函数 swap,它用于交换两个链表的内容。

4.swap

void swap(list<T>& x)
{
    std::swap(_head, x._head); // 交换两个链表的头结点指针
}

这是 list 类的成员函数 swap,它用于交换两个链表的内容。

在这个函数中,通过调用标准库函数 std::swap,将当前链表的头结点指针 _head 与另一个链表 x 的头结点指针 _head 进行交换。这个操作会导致两个链表的内容被互相交换,但是实际上并没有复制链表中的元素,只是交换了链表的结构。

这种交换操作通常在需要交换两个对象的内容时使用,它可以在不复制数据的情况下实现两个对象之间的内容互换,从而提高了效率。

需要注意的是,这个函数只是交换了链表的头结点指针,而链表中的元素顺序没有改变。

5.析构函数定义

~list()
{
    clear();         // 清空链表中的所有元素
    delete _head;    // 删除头结点
    _head = nullptr; // 将头结点指针置为空指针
}

这是 list 类的析构函数,用于销毁链表对象。

在这个析构函数中,首先调用了成员函数 clear() 来清空链表中的所有元素,确保在删除链表之前释放所有资源。然后,使用 delete 关键字释放头结点的内存。最后,将头结点指针 _head 置为空指针,以避免出现野指针。

这样,在链表对象被销毁时,它所占用的内存将会被正确地释放,从而防止内存泄漏。

6.clear()

void clear()
{
    iterator it = begin(); // 获取链表的起始迭代器
    while (it != end())    // 遍历链表
    {
        it = erase(it);    // 使用 erase() 函数删除当前元素,并将迭代器指向下一个元素
    }
}

这是 list 类的 clear() 成员函数,用于清空链表中的所有元素。

在这个函数中,首先获取链表的起始迭代器 begin(),然后通过一个循环遍历链表中的所有元素。在循环内部,调用了 erase() 函数来删除当前迭代器指向的元素,并将迭代器更新为指向被删除元素的下一个元素。这样可以确保链表中的所有元素都被逐个删除。

需要注意的是,erase() 函数在删除元素后会返回指向下一个元素的迭代器,因此在每次循环迭代时更新迭代器是必要的,以便继续正确地遍历链表。

总之,这个 clear() 函数用于释放链表中的所有元素,并确保链表变为空链表。

7.push_back

void push_back(const T& x)
{
    Node* tail = _head->_prev; // 获取当前链表的末尾节点
    Node* newnode = new Node(x); // 创建一个新节点,保存新元素 x
    tail->_next = newnode; // 更新末尾节点的下一个指针,指向新节点
    newnode->_prev = tail; // 新节点的前一个指针指向原末尾节点
    newnode->_next = _head; // 新节点的下一个指针指向头节点
    _head->_prev = newnode; // 头节点的前一个指针指向新节点,完成插入操作
}

这是 list 类的 push_back() 成员函数,用于在链表的末尾添加一个新元素。

在这个函数中,首先获取当前链表的末尾节点(尾节点的 _prev 指向链表的最后一个元素),然后创建一个新的节点 newnode,并将新元素 x 存储在新节点中。接着,更新末尾节点的 _next 指针,使其指向新节点,然后更新新节点的 _prev 指针,使其指向原末尾节点。同时,将新节点的 _next 指针指向头节点,完成循环链表的连接。最后,更新头节点的 _prev 指针,使其指向新节点,确保链表的连接是完整的。

需要注意的是,这个函数在链表末尾添加了一个新元素,不影响其他元素的相对位置。

8.push_front

void push_front(const T& x)
{
    insert(begin(), x); // 调用 insert 函数,在头部插入新元素 x
}

这个函数简单地调用了 insert 函数,将新元素 x 插入到链表的头部。

9.insert

iterator insert(iterator pos, const T& x)
{
    Node* cur = pos._node; // 获取当前位置的节点
    Node* prev = cur->_prev; // 获取当前位置节点的前一个节点
    Node* newnode = new Node(x); // 创建一个新节点,保存新元素 x
    prev->_next = newnode; // 更新前一个节点的下一个指针,指向新节点
    newnode->_prev = prev; // 新节点的前一个指针指向前一个节点
    newnode->_next = cur; // 新节点的下一个指针指向当前位置节点
    cur->_prev = newnode; // 当前位置节点的前一个指针指向新节点,完成插入操作
    return iterator(newnode); // 返回指向新节点的迭代器
}

insert 函数中,首先获取当前位置节点 pos 和其前一个节点 prev,然后创建一个新节点 newnode 并将新元素 x 存储在新节点中。接着,更新前一个节点的 _next 指针,使其指向新节点,然后更新新节点的 _prev 指针,使其指向前一个节点。同时,将新节点的 _next 指针指向当前位置节点 cur,完成插入操作。最后,更新当前位置节点的 _prev 指针,使其指向新节点,确保链表的连接是完整的。最后,函数返回一个指向新节点的迭代器,表示插入操作完成后的位置。

10.erase

iterator erase(iterator pos)
{
    assert(pos != end()); // 断言:确保 pos 不等于链表的结束迭代器
    Node* cur = pos._node; // 获取当前位置的节点
    Node* prev = cur->_prev; // 获取当前位置节点的前一个节点
    Node* next = cur->_next; // 获取当前位置节点的后一个节点
    prev->_next = next; // 更新前一个节点的下一个指针,指向后一个节点
    next->_prev = prev; // 更新后一个节点的前一个指针,指向前一个节点
    delete cur; // 删除当前位置的节点
    return iterator(next); // 返回指向后一个节点的迭代器,表示删除操作完成后的位置
}

这部分代码实现了从链表中删除指定位置元素的功能。

erase 函数中,首先使用断言来确保删除位置 pos 不是链表的结束迭代器。然后,获取当前位置节点 pos、其前一个节点 prev 和后一个节点 next。接着,更新前一个节点的 _next 指针,使其指向后一个节点,同时更新后一个节点的 _prev 指针,使其指向前一个节点。这样,当前位置节点就从链表中断开了。最后,释放当前位置节点的内存空间,并返回一个指向后一个节点的迭代器,表示删除操作完成后的位置。

11.pop_back()和pop_front()

void pop_back()
{
    erase(--end()); // 通过 erase 函数删除链表末尾的元素
}

pop_back 函数通过将链表的结束迭代器向前移动一个位置,然后调用 erase 函数来删除该位置的元素,实现了从链表末尾删除一个元素的操作。

void pop_front()
{
    erase(begin()); // 通过 erase 函数删除链表头部的元素
}

pop_front 函数直接调用 erase 函数,删除链表头部的元素,实现了从链表头部删除一个元素的操作。

这两个函数分别对应于从链表的末尾和头部删除元素的操作,通过调用 erase 函数来完成删除操作,从而保持了链表的连接性。

12.empty()、size()、front()和back()

bool list<T>::empty() const
{
    return begin() == end(); // 判断 begin() 是否等于 end() 来确定是否为空
}
typename list<T>::size_t list<T>::size() const
{
    size_t count = 0;
    for (const_iterator it = begin(); it != end(); ++it)
    {
        ++count;
    }
    return count; // 遍历链表来计算元素个数
}
typename list<T>::reference list<T>::front()
{
    assert(!empty()); // 如果链表为空,则抛出断言
    return *begin(); // 返回链表的第一个元素
}
typename list<T>::const_reference list<T>::front() const
{
    assert(!empty()); // 如果链表为空,则抛出断言
    return *begin(); // 返回链表的第一个元素
}
typename list<T>::reference list<T>::back()
{
    assert(!empty()); // 如果链表为空,则抛出断言
    return *(--end()); // 返回链表的最后一个元素
}
typename list<T>::const_reference list<T>::back() const
{
    assert(!empty()); // 如果链表为空,则抛出断言
    return *(--end()); // 返回链表的最后一个元素
}

上述代码实现了 empty() 函数用于判断链表是否为空,size() 函数用于获取链表元素个数,front() 函数用于获取链表第一个元素,以及 back() 函数用于获取链表最后一个元素。注意函数中的断言用于确保在链表为空时不执行非法操作。

13.resize

template<class T>
void list<T>::resize(size_t n, const T& val)
{
    if (n < size()) {
        iterator it = begin();
        std::advance(it, n); // 定位到新的尾部位置
        while (it != end()) {
            it = erase(it); // 从尾部截断,删除多余的元素
        }
    }
    else if (n > size()) {
        insert(end(), n - size(), val); // 插入足够数量的默认值元素
    }
}

如果 n 小于当前链表的大小size(),意味着你想要缩小链表的大小。在这种情况下,函数会迭代遍历链表,从尾部开始删除多余的元素,直到链表的大小等于 n

首先,函数会使用 begin() 获取链表的起始迭代器,然后使用 std::advance 函数将迭代器向前移动 n 个位置,使其指向新的尾部位置。

接下来,函数使用 erase 函数将从新尾部位置到链表末尾的所有元素删除,从而使链表的大小减小到 n

如果 n 大于当前链表的大小,表示你想要增大链表的大小。在这种情况下,函数会插入足够数量的值为 val 的元素到链表的末尾,直到链表的大小等于 n

函数会使用 insert 函数在链表的末尾插入 n - size() 个值为 val 的元素,从而使链表的大小增大到 n

14.赋值运算符重载

list<T>& operator=(list<T> lt)
{
    swap(lt); // 交换当前对象和传入对象的内容
    return *this; // 返回当前对象的引用
}

这是一个赋值运算符重载函数,它采用了拷贝并交换(Copy and Swap)的技巧来实现赋值操作。

这是一个成员函数,用于将一个 list<T> 类型的对象赋值给当前的对象。

参数 lt 是通过值传递的,所以会调用 list<T> 的拷贝构造函数创建一个临时副本。

然后,通过 swap(lt) 调用 swap 函数来交换当前对象和临时副本的内容。在交换后,临时副本的数据会被赋值给当前对象,而临时副本会被销毁。由于交换是高效的操作,可以避免大量的数据复制。

最后,函数返回当前对象的引用,以支持连续赋值操作。

这种方式不仅避免了手动管理内存的麻烦,还确保了异常安全,因为临时副本在函数结束时会被正确销毁。

list模拟实现全部代码

#pragma once
#include <assert.h>
#include <iostream>
namespace xzq
{
  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;
    }
    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;
    typedef __reverse_iterator<iterator, T&, T*> reverse_iterator;
    typedef __reverse_iterator<const_iterator, const T&, const T*> const_reverse_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);
    }
    reverse_iterator rbegin()
    {
      return reverse_iterator(end());
    }
    reverse_iterator rend()
    {
      return reverse_iterator(begin());
    }
    void empty_init()
    {
      _head = new Node;
      _head->_next = _head;
      _head->_prev = _head;
    }
    template <class InputIterator>  
    list(InputIterator first, InputIterator last)
    {
      empty_init();
      while (first != last)
      {
        push_back(*first);
        ++first;
      }
    }
    list()
    {
      empty_init();
    }
    void swap(list<T>& x)
    {
      std::swap(_head, x._head);
    }
    list(const list<T>& lt)
    {
      empty_init();
      list<T> tmp(lt.begin(), lt.end());
      swap(tmp);
    }
    list<T>& operator=(list<T> lt)
    {
      swap(lt);
      return *this;
    }
    ~list()
    {
      clear();
      delete _head;
      _head = nullptr;
    }
    void clear()
    {
      iterator it = begin();
      while (it != end())
      {
        it = erase(it);
      }
    }
    void push_back(const T& x)
    {
      Node* tail = _head->_prev;
      Node* newnode = new Node(x);
      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);
    }
    bool list<T>::empty() const
    {
      return begin() == end(); // 判断 begin() 是否等于 end() 来确定是否为空
    }
    typename list<T>::size_t list<T>::size() const
    {
      size_t count = 0;
      for (const_iterator it = begin(); it != end(); ++it)
      {
        ++count;
      }
      return count; // 遍历链表来计算元素个数
    }
    typename list<T>::reference list<T>::front()
    {
      assert(!empty()); // 如果链表为空,则抛出断言
      return *begin(); // 返回链表的第一个元素
    }
    typename list<T>::const_reference list<T>::front() const
    {
      assert(!empty()); // 如果链表为空,则抛出断言
      return *begin(); // 返回链表的第一个元素
    }
    typename list<T>::reference list<T>::back()
    {
      assert(!empty()); // 如果链表为空,则抛出断言
      return *(--end()); // 返回链表的最后一个元素
    }
    typename list<T>::const_reference list<T>::back() const
    {
      assert(!empty()); // 如果链表为空,则抛出断言
      return *(--end()); // 返回链表的最后一个元素
    }
    void resize(size_t n, const T& val = T())
    {
      if (n < size()) {
        iterator it = begin();
        std::advance(it, n);
        while (it != end()) {
          it = erase(it); // 从尾部截断,删除多余的元素
        }
      }
      else if (n > size()) {
        insert(end(), n - size(), val); // 插入足够数量的默认值元素
      }
    }
  private:
    Node* _head;
  };
}

list 和 vector的区别

通过模拟实现 listvector,你可以更好地理解它们之间的区别和特点。这两者是 C++ 标准库中的序列式容器,但它们在内部实现和使用场景上有很大的区别。

底层实现:

list 通常是一个双向链表,每个节点都包含了数据和指向前一个和后一个节点的指针。这使得在任何位置进行插入和删除操作都是高效的,但随机访问和内存占用可能相对较差。

vector 是一个动态数组,元素在内存中是连续存储的。这使得随机访问非常高效,但插入和删除操作可能需要移动大量的元素,效率较低。

插入和删除操作:

list 中,插入和删除操作是高效的,无论是在容器的任何位置还是在开头和结尾。这使得 list 在需要频繁插入和删除操作时非常适用。

vector 中,插入和删除操作可能需要移动元素,特别是在容器的中间或开头。因此,当涉及大量插入和删除操作时,vector 可能不如 list 效率高。

随机访问:

list 不支持随机访问,即不能通过索引直接访问元素,必须通过迭代器逐个遍历。

vector 支持随机访问,可以通过索引快速访问元素,具有良好的随机访问性能。

内存占用:

由于 list 使用链表存储元素,每个节点都需要额外的指针来指向前后节点,可能会导致更多的内存占用。

vector 在内存中是连续存储的,通常占用的内存较少。

迭代器失效:

list 中,插入和删除操作不会导致迭代器失效,因为节点之间的关系不会改变。

vector 中,插入和删除操作可能导致内存重新分配,从而使原来的迭代器失效。

综上所述,如果你需要频繁进行插入和删除操作,而对于随机访问性能没有特别高的要求,那么使用 list 是一个不错的选择。而如果你更注重随机访问性能,可以选择使用 vector。当然,在实际开发中,还需要根据具体情况权衡使用哪种容器。

结语

有兴趣的小伙伴可以关注作者,如果觉得内容不错,请给个一键三连吧,蟹蟹你哟!!!

制作不易,如有不正之处敬请指出

感谢大家的来访,UU们的观看是我坚持下去的动力

在时间的催化剂下,让我们彼此都成为更优秀的人吧!!!

(创作128天纪念日,暂时还没有想好怎么分享,下次再写喽)


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