STL中list的基本用法以及模拟实现

简介: STL中list的基本用法以及模拟实现

一、list的介绍以及使用

1、list的介绍

list文档阅读

  1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
  2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
  3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。
  4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好。
  5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)

图示:

2、list的使用

2.1 list的构造

1700807990031.png


#include <iostream>
#include <list>
int main ()
{
  std::list<int> l1; // 构造空的l1
  std::list<int> l2 (4,100); // l2中放4个值为100的元素
  std::list<int> l3 (l2.begin(), l2.end()); // 用l2的[begin(), end())左闭右开的区间构造l3
  std::list<int> l4 (l3); // 用l3拷贝构造l4
  // 以数组为迭代器区间构造l5
  int array[] = {16,2,77,29};
  std::list<int> l5 (array, array + sizeof(array) / sizeof(int) );
  // 用迭代器方式打印l5中的元素
  for(std::list<int>::iterator it = l5.begin(); it != l5.end(); it++)
    std::cout << *it << " ";
  std::cout<<endl;
  // C++11范围for的方式遍历
  for(auto& e : l5)
    std::cout<< e << " ";
  std::cout<<endl;
  return 0;
}

2.2 list iterator的使用

函数声明 接口说明
begin +end 返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器
rbegin + rend 返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的reverse_iterator,即begin位置

图示:

【注意】

  1. begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动
  2. rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动
#include <iostream>
using namespace std;
#include <list>
void print_list(const list<int>& l)
{
// 注意这里调用的是list的 begin() const,返回list的const_iterator对象
for (list<int>::const_iterator it = l.begin(); it != l.end(); ++it)
{
cout << *it << " ";
// *it = 10; 编译不通过
}
cout << endl;
}
int main()
{
  int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
  list<int> l(array, array + sizeof(array) / sizeof(array[0]));
  // 使用正向迭代器正向list中的元素
  for (list<int>::iterator it = l.begin(); it != l.end(); ++it)
    cout << *it << " ";
  cout << endl;
  // 使用反向迭代器逆向打印list中的元素
  for (list<int>::reverse_iterator it = l.rbegin(); it != l.rend(); ++it)
    cout << *it << " ";
  cout << endl;
  return 0;
}

2.3 list 容量函数

函数声明 接口说明
empty 检测list是否为空,是返回true,否则返回false
size 返回list中有效节点的个数

2.4 list 的元素获取

函数声明 接口说明
front 返回list的第一个节点中值的引用
back 返回list的最后一个节点中值的引用

2.5 list 的修改

函数声明 接口说明
push_front 在list首元素前插入值为val的元素
pop_front 删除list中第一个元素
push_back 在list尾部插入值为val的元素
pop_back 删除list中最后一个元素
insert 在list position 位置中插入值为val的元素
erase 删除list position位置的元素
swap 交换两个list中的元素
clear 清空list中的有效元素


测试代码:

#include <list>
void PrintList(list<int>& l)
{
  for (auto& e : l)
  cout << e << " ";
  cout << endl;
}
//=========================================================================================
// push_back/pop_back/push_front/pop_front
void TestList1()
{
  int array[] = { 1, 2, 3 };
  list<int> L(array, array+sizeof(array)/sizeof(array[0]));
  // 在list的尾部插入4,头部插入0
  L.push_back(4);
  L.push_front(0);
  PrintList(L);
  // 删除list尾部节点和头部节点
  L.pop_back();
  L.pop_front();
  PrintList(L);
}
//=========================================================================================
// insert /erase
void TestList3()
{
  int array1[] = { 1, 2, 3 };
  list<int> L(array1, array1+sizeof(array1)/sizeof(array1[0]));
  // 获取链表中第二个节点
  auto pos = ++L.begin();
  cout << *pos << endl;
  // 在pos前插入值为4的元素
  L.insert(pos, 4);
  PrintList(L);
  // 在pos前插入5个值为5的元素
  L.insert(pos, 5, 5);
  PrintList(L);
  // 在pos前插入[v.begin(), v.end)区间中的元素
  vector<int> v{ 7, 8, 9 };
  L.insert(pos, v.begin(), v.end());
  PrintList(L);
  // 删除pos位置上的元素
  L.erase(pos);
  PrintList(L);
  // 删除list中[begin, end)区间中的元素,即删除list中的所有元素
  L.erase(L.begin(), L.end());
  PrintList(L);
}
//=========================================================================================
// resize/swap/clear
void TestList4()
{
  // 用数组来构造list
  int array1[] = { 1, 2, 3 };
  list<int> l1(array1, array1+sizeof(array1)/sizeof(array1[0]));
  PrintList(l1);
  // 交换l1和l2中的元素
  l1.swap(l2);
  PrintList(l1);
  PrintList(l2);
  // 将l2中的元素清空
  l2.clear();
  cout<<l2.size()<<endl;
}

2.6 list 的迭代器失效

迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。

例如:

void TestListIterator1()
{
  int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
  list<int> l(array, array+sizeof(array)/sizeof(array[0]));
  auto it = l.begin();
  while (it != l.end())
  {
    // erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给其赋值
    l.erase(it);
    ++it;
  }
}
// 改正
void TestListIterator()
{
  int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
  list<int> l(array, array+sizeof(array)/sizeof(array[0]));
  auto it = l.begin();
  while (it != l.end())
  {
    l.erase(it++); // it = l.erase(it);
  }
}

二、list的模拟实现

1、模拟实现list的迭代器类以及结点类

结点类:

template<class T>
  struct list_node
  {
    list_node<T>* _next;
    list_node<T>* _prev;
    T _data;
    list_node(const T& val = T())
      :_next(nullptr)
      ,_prev(nullptr)
      ,_data(val)
    {}
  };

迭代器类:

  template<class T, class Ref, class Ptr>
  struct _list_iterator
  {
    typedef list_node<T> Node;
    typedef _list_iterator<T, Ref, Ptr> self;
    Node* _node;
    _list_iterator(Node* node)
      :_node(node)
    {}
    // 析构函数  -- 节点不属于迭代器,不需要迭代器释放
    // 拷贝构造和赋值重载 -- 默认生成的浅拷贝就可以
    Ref operator*()
    {
      return _node->_data;
    }
    Ptr operator->()
    {
      return &_node->_data;
    }
    //前置++
    self& operator++()
    {
      _node = _node->_next;
      return *this;
    }
    //后置++
    self operator++(int)
    {
      self tmp(*this);
      _node = _node->_next;
      return tmp;
    }
    //前置--
    self& operator--()
    {
      _node = _node->_prev;
      return *this;
    }
    //后置--
    self operator--(int)
    {
      self tmp(*this);
      _node = _node->_prev;
      return tmp;
    }
    bool operator!=(const self& it)
    {
      return _node != it._node;
    }
    bool operator==(const self& it)
    {
      return _node == it._node;
    }
  };

2、list的模拟实现代码

#pragma once
#include<assert.h>
#include<iostream>
namespace my_list
{
  template<class T>
  struct list_node
  {
    list_node<T>* _next;
    list_node<T>* _prev;
    T _data;
    list_node(const T& val = T())
      :_next(nullptr)
      ,_prev(nullptr)
      ,_data(val)
    {}
  };
  template<class T, class Ref, class Ptr>
  struct _list_iterator
  {
    typedef list_node<T> Node;
    typedef _list_iterator<T, Ref, Ptr> self;
    Node* _node;
    _list_iterator(Node* node)
      :_node(node)
    {}
    // 析构函数  -- 节点不属于迭代器,不需要迭代器释放
    // 拷贝构造和赋值重载 -- 默认生成的浅拷贝就可以
    Ref operator*()
    {
      return _node->_data;
    }
    Ptr operator->()
    {
      return &_node->_data;
    }
    //前置++
    self& operator++()
    {
      _node = _node->_next;
      return *this;
    }
    //后置++
    self operator++(int)
    {
      self tmp(*this);
      _node = _node->_next;
      return tmp;
    }
    //前置--
    self& operator--()
    {
      _node = _node->_prev;
      return *this;
    }
    //后置--
    self operator--(int)
    {
      self tmp(*this);
      _node = _node->_prev;
      return tmp;
    }
    bool operator!=(const self& it)
    {
      return _node != it._node;
    }
    bool operator==(const self& it)
    {
      return _node == it._node;
    }
  };
  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);
      //return _head->_next;
    }
    const_iterator end()const
    {
      return const_iterator(_head);
    }
    iterator begin()
    {
      return iterator(_head->_next);
      //return _head->_next;
    }
    iterator end()
    {
      return iterator(_head);
    }
    void swap(list<T>& lt)
    {
      std::swap(_head, lt._head);
    }
    void empty_init()
    {
      _head = new Node();
      _head->_next = _head;
      _head->_prev = _head;
    }
    list()
    {
      empty_init();
    }
    template<class InputIterator>
    list(InputIterator first, InputIterator last)
    {
      empty_init();
      while (first != last)
      {
        push_back(*first);
        ++first;
      }
    }
    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->_next = tail;
      //newnode->_next = _head;
      //_head->_prev = newnode;
      insert(end(), x);
    }
    void push_front(const T& x)
    {
      insert(begin(), x);
    }
    void pop_back()
    {
      erase(--end());
    }
    void pop_front()
    {
      erase(begin());
    }
    //pos前插入x
    iterator insert(iterator pos, const T& x)
    {
      Node* newnode = new Node(x);
      Node* cur = pos._node;
      Node* prev = cur->_prev;
      prev->_next = newnode;
      newnode->_prev = prev;
      newnode->_next = cur;
      cur->_prev = newnode;
      return iterator(newnode);
    }
    iterator erase(iterator pos)
    {
      assert(pos != end());
      Node* cur = pos._node;
      Node* next = cur->_next;
      Node* prev = cur->_prev;
      prev->_next = next;
      next->_prev = prev;
      delete cur;
      return iterator(next);
    }
  private:
    Node* _head;
  };
}

三、list与vector的比较

vector与list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同,其主要不同如下:

1700808100052.png

目录
相关文章
|
23天前
|
安全 C#
C# List基本用法
C# List基本用法
|
8天前
|
存储 缓存 编译器
【C++进阶(五)】STL大法--list模拟实现以及list和vector的对比
【C++进阶(五)】STL大法--list模拟实现以及list和vector的对比
|
8天前
|
算法 C++ 容器
【C++进阶(四)】STL大法--list深度剖析&list迭代器问题探讨
【C++进阶(四)】STL大法--list深度剖析&list迭代器问题探讨
|
1月前
|
存储 C语言 C++
C++中STL常用容器(vector、deque、list、map、set)一文带你了解
C++中STL常用容器(vector、deque、list、map、set)一文带你了解
|
2月前
|
编译器 C++ 容器
STL常用之vector,list,stack,queue,deque总结与对比
STL常用之vector,list,stack,queue,deque总结与对比
|
2月前
|
存储 C++
C++STL模板之——list(简化源码,模拟源码)
C++STL模板之——list(简化源码,模拟源码)
|
2月前
|
存储 C++ 容器
【C++修行之道】STL(初识list、stack)
【C++修行之道】STL(初识list、stack)
|
4月前
|
算法 C++ 容器
【C++STL基础入门】list改、查操作
【C++STL基础入门】list改、查操作
459 0
|
4月前
|
算法 C++ 容器
【C++STL基础入门】list的增、删
【C++STL基础入门】list的增、删
|
4月前
|
存储 算法 C++
【C++STL基础入门】list基本使用
【C++STL基础入门】list基本使用