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天纪念日,暂时还没有想好怎么分享,下次再写喽)


相关文章
|
11天前
|
算法 C语言 C++
【c++丨STL】list的使用
本文介绍了STL容器`list`的使用方法及其主要功能。`list`是一种双向链表结构,适用于频繁的插入和删除操作。文章详细讲解了`list`的构造函数、析构函数、赋值重载、迭代器、容量接口、元素访问接口、增删查改操作以及一些特有的操作接口如`splice`、`remove_if`、`unique`、`merge`、`sort`和`reverse`。通过示例代码,读者可以更好地理解如何使用这些接口。最后,作者总结了`list`的特点和适用场景,并预告了后续关于`list`模拟实现的文章。
27 7
|
19天前
|
存储 编译器 C++
C++ initializer_list&&类型推导
在 C++ 中,`initializer_list` 提供了一种方便的方式来初始化容器和传递参数,而右值引用则是实现高效资源管理和移动语义的关键特性。尽管在实际应用中 `initializer_list&&` 并不常见,但理解其类型推导和使用方式有助于深入掌握现代 C++ 的高级特性。
16 4
|
2月前
|
存储 搜索推荐 C++
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器2
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器
66 2
|
2月前
|
存储 算法 C++
【C++打怪之路Lv10】-- list
【C++打怪之路Lv10】-- list
23 1
|
2月前
|
存储 C++ 容器
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器1
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器
71 5
|
2月前
|
存储 编译器 C++
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
83 2
|
2月前
|
C++
【C++】C++ STL 探索:List使用与背后底层逻辑(三)
【C++】C++ STL 探索:List使用与背后底层逻辑
|
2月前
|
C++
【C++】C++ STL 探索:List使用与背后底层逻辑(二)
【C++】C++ STL 探索:List使用与背后底层逻辑
|
2月前
|
存储 编译器 C++
【C++】C++ STL 探索:List使用与背后底层逻辑(一)
【C++】C++ STL 探索:List使用与背后底层逻辑
|
2月前
|
存储 缓存 C++
C++番外篇——list与vector的比较
C++番外篇——list与vector的比较
28 0