c++领域展开第十八幕——STL(list容器的了解和使用以及手撕模仿)超详细! ...

打印 上一主题 下一主题

主题 1051|帖子 1051|积分 3157



  
前言

   本专栏上一篇博客把vector的有关实现和细节问题都解说完毕
今天我们来学习 stl 库的另外一个容器——list
从认识到使用到手撕实现,我来手把手教你
fellow me
  一、list的先容和使用

1.1 list的先容

list文档

文档链接在上面啦,大家可以翻译看看
双向循环链表图,list就相当于我们数据结构学习的双向循环链表
只不外在stl库里面给它举行了封装

1.2 list的使用

   list中的接口比较多,此处雷同,只需要掌握如何精确的使用,然后再去深入研究背后的原理,已到达可扩展的能力。以下为list中一些常见的重要接口。
  1.2.1 list的构造

构造函数((constructor))
list (size_type n, const value_type& val = value_type())——————构造的list中包罗n个值为val的元素
list() ————————————构造空的list
list (const list& x) ——————拷贝构造函数
list (InputIterator first, InputIterator last)————用[first, last)区间中的元素构造list
代码展示
  1.         list<int> l1;                         // 构造空的l1
  2.     list<int> l2(4, 100);                 // l2中放4个值为100的元素
  3.     list<int> l3(l2.begin(), l2.end());  // 用l2的[begin(), end())左闭右开的区间构造l3
  4.     list<int> l4(l3);                    // 用l3拷贝构造l4
  5.     // 以数组为迭代器区间构造l5
  6.     int array[] = { 16,2,77,29 };
  7.     list<int> l5(array, array + sizeof(array) / sizeof(int));
  8.     // 列表格式初始化C++11
  9.     list<int> l6{ 1,2,3,4,5 };
复制代码
1.2.2 list iterator的使用

此处,大家可临时将迭代器理解成一个指针,该指针指向list中的某个节点
函数声明——————接口说明
begin + end————返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器
rbegin + rend————返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的reverse_iterator,即begin位置
【注意】

  • begin与end为正向迭代器,对迭代器执行++操纵,迭代器向后移动
  • rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操纵,迭代器向前移动
代码展示
  1.         // 注意这里调用的是list的 begin() const,返回list的const_iterator对象
  2.     for (list<int>::const_iterator it = l.begin(); it != l.end(); ++it)
  3.     {
  4.         cout << *it << " ";
  5.         // *it = 10; 编译不通过
  6.     }
  7.         int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
  8.     list<int> l(array, array + sizeof(array) / sizeof(array[0]));
  9.     // 使用正向迭代器正向list中的元素
  10.     // list<int>::iterator it = l.begin();   // C++98中语法
  11.     auto it = l.begin();                     // C++11之后推荐写法
  12.     while (it != l.end())
  13.     {
  14.         cout << *it << " ";
  15.         ++it;
  16.     }
  17.     cout << endl;
  18.     // 使用反向迭代器逆向打印list中的元素
  19.     // list<int>::reverse_iterator rit = l.rbegin();
  20.     auto rit = l.rbegin();
  21.     while (rit != l.rend())
  22.     {
  23.         cout << *rit << " ";
  24.         ++rit;
  25.     }
复制代码
1.2.3 list capacity

函数声明————接口说明
empty ——————检测list是否为空,是返回true,否则返回false
size ———————返回list中有效节点的个数
这两个接口直接使用就行,这里就不展示代码了
1.2.4 list element access

函数声明————接口说明
front ——————返回list的第一个节点中值的引用
back ——————返回list的最后一个节点中值的引用
这两个接口也是直接使用就行
1.2.5 list modifiers

函数声明————————接口说明
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中的有效元素
代码展示
  1.         int array[] = { 1, 2, 3 };
  2.     list<int> L(array, array + sizeof(array) / sizeof(array[0]));
  3.     // 在list的尾部插入4,头部插入0
  4.     L.push_back(4);
  5.     L.push_front(0);
  6.     PrintList(L);
  7.     // 删除list尾部节点和头部节点
  8.     L.pop_back();
  9.     L.pop_front();
  10.     PrintList(L);
  11.        
  12.         int array1[] = { 1, 2, 3 };
  13.     list<int> L(array1, array1 + sizeof(array1) / sizeof(array1[0]));
  14.     // 获取链表中第二个节点
  15.     auto pos = ++L.begin();
  16.     cout << *pos << endl;
  17.     // 在pos前插入值为4的元素
  18.     L.insert(pos, 4);
  19.     PrintList(L);
  20.     // 在pos前插入5个值为5的元素
  21.     L.insert(pos, 5, 5);
  22.     PrintList(L);
  23.     // 在pos前插入[v.begin(), v.end)区间中的元素
  24.     vector<int> v{ 7, 8, 9 };
  25.     L.insert(pos, v.begin(), v.end());
  26.     PrintList(L);
  27.     // 删除pos位置上的元素
  28.     L.erase(pos);
  29.     PrintList(L);
  30.     // 删除list中[begin, end)区间中的元素,即删除list中的所有元素
  31.     L.erase(L.begin(), L.end());
  32.     PrintList(L);
  33.    
  34.         // 用数组来构造list
  35.     int array1[] = { 1, 2, 3 };
  36.     list<int> l1(array1, array1 + sizeof(array1) / sizeof(array1[0]));
  37.     PrintList(l1);
  38.     // 交换l1和l2中的元素
  39.     list<int> l2;
  40.     l1.swap(l2);
  41.     PrintList(l1);
  42.     PrintList(l2);
  43.     // 将l2中的元素清空
  44.     l2.clear();
  45.     cout << l2.size() << endl;
  46.    
复制代码
list中另有一些操纵,需要用到时大家可参阅list的文档说明
1.2.6 list的迭代器失效

前面说过,此处大家可将迭代器临时理解成雷同于指针迭代器失效即迭代器所指向的节点的无
,即该节点被删除了。由于list的底层结构为带头结点的双向循环链表,因此在list中举行插入
时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭
代器,其他迭代器不会受到影响。
  1. void TestListIterator1()
  2. {
  3.         int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
  4.         list<int> l(array, array+sizeof(array)/sizeof(array[0]));
  5.         auto it = l.begin();
  6.         while (it != l.end())
  7.         {
  8.                 // erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给其赋值
  9.                 l.erase(it);
  10.                 ++it;
  11.         }
  12. }
  13. // 改正
  14. void TestListIterator()
  15. {
  16.         int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
  17.         list<int> l(array, array+sizeof(array)/sizeof(array[0]));
  18.         auto it = l.begin();
  19.         while (it != l.end())
  20.         {
  21.                 l.erase(it++); // it = l.erase(it);
  22.         }
  23. }
复制代码
上面有关list的使用和说明就到这里啦,下面我们来模仿实现一下list
二、list的模仿实现

前面在表面的vector的时间就已经模仿实现过,相信大家都有些熟悉了,其实list实现起来也差不多的,都是封装嘛
先用结构体封装一下迭代器
  1.         template<class T, class Ref, class Ptr>
  2.         struct list_iterator
  3.         {
  4.                 typedef list_node<T> Node;
  5.                 typedef list_iterator<T, Ref, Ptr> Self;//  这里模版的是const迭代器  会返回引用或者取地址其他模版参数
  6.                 Node* _node;
  7.                 list_iterator(Node* node)
  8.                         :_node(node)
  9.                 {}
  10.                 Ref operator*()
  11.                 {
  12.                         return _node->_data;
  13.                 }
  14.                 Ptr operator->()  
  15.                 {
  16.                         return &_node->_data;
  17.                 }
  18.                 Self& operator++()
  19.                 {
  20.                         _node = _node->_next;
  21.                         return *this;
  22.                 }
  23.                 Self operator++(int)  //  后置++
  24.                 {
  25.                         Self tmp(*this);
  26.                         _node = _node->_next;
  27.                         return tmp;
  28.                 }
  29.                 Self& operator--()
  30.                 {
  31.                         _node = _node->_prev;
  32.                         return *this;
  33.                 }
  34.                 Self operator--(int)  // 后置--
  35.                 {
  36.                         Self tmp(*this);
  37.                         _node = _node->_prev;
  38.                         return tmp;
  39.                 }
  40.                 bool operator!=(const Self& it)
  41.                 {
  42.                         return _node != it._node;
  43.                 }
  44.                 bool operator==(const Self& it)
  45.                 {
  46.                         return _node == it._node;
  47.                 }
  48.         };
复制代码
然后就是封装list,实现迭代器,各种增删改查函数,另有默认成员函数
这里我就不像vector一样一个一个赘述啦,相信模仿实现过string和vector之后其实这些写起来也就随手啦
  1.         template<class T>
  2.         class list
  3.         {
  4.                 typedef list_node<T> Node;
  5.         public:
  6.                 typedef list_iterator<T, T&, T*> iterator; // 多个模版参数  与前面的迭代器封装相对应
  7.                 typedef list_iterator<T, const T&, const T*> const_iterator;
  8.                 iterator begin()  // 迭代器
  9.                 {
  10.                         //return iterator(_head->_next);
  11.                         iterator it(_head->_next);
  12.                         return it;
  13.                 }
  14.                 iterator end()
  15.                 {
  16.                         return iterator(_head);
  17.                 }
  18.                 const_iterator begin() const
  19.                 {
  20.                         return const_iterator(_head->_next);
  21.                 }
  22.                 const_iterator end() const
  23.                 {
  24.                         return const_iterator(_head);
  25.                 }
  26.                 void empty_init()  // 初始化
  27.                 {
  28.                         _head = new Node;
  29.                         _head->_next = _head;
  30.                         _head->_prev = _head;
  31.                         _size = 0;
  32.                 }
  33.                 list()   // 默认构造
  34.                 {
  35.                         empty_init();
  36.                 }
  37.                 list(initializer_list<T> lt)   // c++11支持  {   }构造
  38.                 {
  39.                         empty_init();
  40.                         for (auto& e : lt)
  41.                         {
  42.                                 push_back(e);
  43.                         }
  44.                 }
  45.                 // lt2(lt1)
  46.                 list(const list<T>& lt)  // 拷贝构造
  47.                 {
  48.                         empty_init();
  49.                         for (auto& e : lt)
  50.                         {
  51.                                 push_back(e);
  52.                         }
  53.                 }
  54.                 // lt1 = lt3
  55.                 list<T>& operator=(list<T> lt)  // 赋值重载
  56.                 {
  57.                         swap(lt);
  58.                         return *this;
  59.                 }
  60.                 void swap(list<T>& lt)    // list内部的  swap
  61.                 {
  62.                         std::swap(_head, lt._head);
  63.                         std::swap(_size, lt._size);
  64.                 }
  65.                 ~list()
  66.                 {
  67.                         clear();
  68.                         delete _head;
  69.                         _head = nullptr;
  70.                 }
  71.                 void clear()   // 清楚函数
  72.                 {
  73.                         iterator it = begin();
  74.                         while (it != end())
  75.                         {
  76.                                 it = erase(it);
  77.                         }
  78.                 }
  79.                 size_t size() const
  80.                 {
  81.                         return _size;
  82.                 }
  83.                 void push_back(const T& x)
  84.                 {
  85.                         /*Node* newnode = new Node(x);
  86.                         Node* tail = _head->_prev;
  87.                         newnode->_prev = tail;
  88.                         tail->_next = newnode;
  89.                         newnode->_next = _head;
  90.                         _head->_prev = newnode;*/
  91.                         insert(end(), x);
  92.                 }
  93.                 void push_front(const T& x)
  94.                 {
  95.                         insert(begin(), x);
  96.                 }
  97.                 void pop_front()
  98.                 {
  99.                         erase(begin());
  100.                 }
  101.                 void pop_back()
  102.                 {
  103.                         erase(--end());
  104.                 }
  105.                 void insert(iterator pos, const T& x)
  106.                 {
  107.                         Node* cur = pos._node;
  108.                         Node* prev = cur->_prev;
  109.                         Node* newnode = new Node(x);
  110.                         prev->_next = newnode;
  111.                         newnode->_prev = prev;
  112.                         newnode->_next = cur;
  113.                         cur->_prev = newnode;
  114.                         ++_size;
  115.                 }
  116.                 iterator erase(iterator pos)
  117.                 {
  118.                         assert(pos != end());
  119.                         Node* cur = pos._node;
  120.                         Node* nextNode = cur->_next;
  121.                         Node* prevNode = cur->_prev;
  122.                         prevNode->_next = nextNode;
  123.                         nextNode->_prev = prevNode;
  124.                         delete cur;
  125.                         --_size;
  126.                         return iterator(nextNode);
  127.                 }
  128.         private:
  129.                 Node* _head;
  130.                 size_t _size;
  131.         };
复制代码
经过几次的模仿实现stl,发现stl的容器大多都是雷同的,有点等待后面的map和set
list 的模仿实现就到这里啦
list与vector的对比

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

总结

总结

   C++ STL中的list是基于双向循环链表实现的序列容器,支持高效插入删除(O(1)时间复杂度),但随机访问服从较低。其核心特性包括:通过迭代器遍历元素(支持正向/反向迭代器)、插入操纵不引发迭代器失效(删除仅影响被删节点迭代器)。模仿实现需封装节点结构体,筹划泛型迭代器(重载++/--/*等操纵),并实现深拷贝控制。与vector对比,list得当频繁增删场景,而vector更得当随机访问和内存连续需求。理解其底层实现有助于优化数据操纵逻辑。
    不要走开,小编连续更新中~~~~~
  


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

东湖之滨

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表