ToB企服应用市场:ToB评测及商务社交产业平台

标题: 【C++】——list 容器的解析与极致实现 [打印本页]

作者: 饭宝    时间: 2024-10-18 04:52
标题: 【C++】——list 容器的解析与极致实现

人的一切痛苦,本质上都是对自己的无能的愤怒。

—— 王小波


目次
1、list 介绍
2、list的使用
2.1 list 的构造
2.2 iterator 的使用
2.3 list 的修改
2.4一些特殊接口
2.5 迭代器失效题目
3、实现list
3.1底层结构
结点类
list类
迭代器类
3.2功能接口
迭代器接口
插入删除
拷贝赋值析构函数
4、list与vector 区别对比


1、list 介绍

List是C++标准模板库(STL)中的一个成员,其本质为带头双向循环链表。差别于连续的、精密排列的数组容器Vector,List容器的内部是由双向链表构成的,使得它在插入和删除操作上,就如同行云流水一样平常顺畅,不需移动其它元素。
List的结构示意图如下:环状链表的尾是一个空节点,符合“左闭右开”区间。

List得当的场景是那些需要频繁进行插入和删除操作的场所。
例如,如果你正在管理一个动态变化的列表,如使命调度、职员列队等场景,List的特性将大放异彩。但是如果你的应用场景更多地需要随机访问元素,那么Vector或者数组可能是更佳的选择。因为List的顺序访问性能相比之下会显得有些力有未逮。



迭代器的介绍
迭代器可以按照功能、性质两大模块分类

迭代器的一些性质(重载的运算符)决定着算法库里对算法的调用

而这些迭代器是子类与父类的继承关系

2、list的使用

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


  1. void TestList1()
  2. {
  3.         list<int> l1;                         // 构造空的l1
  4.         list<int> l2(4, 100);                 // l2中放4个值为100的元素
  5.         list<int> l3(l2.begin(), l2.end());  // 用l2的[begin(), end())左闭右开的区间构造l3
  6.         list<int> l4(l3);                    // 用l3拷贝构造l4
  7.         // 以数组为迭代器区间构造l5
  8.         int array[] = { 16,2,77,29 };
  9.         list<int> l5(array, array + sizeof(array) / sizeof(int));
  10.         // 列表格式初始化C++11
  11.         list<int> l6{ 1,2,3,4,5 };
  12.         list<int> l7 = { 1,2,4,5,6,7,8 };
  13.         // 用迭代器方式打印l5中的元素
  14.         list<int>::iterator it = l5.begin();
  15.         while (it != l5.end())
  16.         {
  17.                 cout << *it << " ";
  18.                 ++it;
  19.         }
  20.         cout << endl;
  21.         // C++11范围for的方式遍历
  22.         for (auto& e : l5)
  23.                 cout << e << " ";
  24.         cout << endl;
  25. }
复制代码
2.2 iterator 的使用

关于 list 的迭代器可以姑且理解为指针,本质是通过运算符重载模拟指针的行为


【TIP】
  1. void PrintList(const list<int>& l)
  2. {
  3.         // 注意这里调用的是list的 begin() const,返回list的const_iterator对象
  4.         for (list<int>::const_iterator it = l.begin(); it != l.end(); ++it)
  5.         {
  6.                 cout << *it << " ";
  7.                 // *it = 10; 编译不通过
  8.         }
  9.         cout << endl;
  10. }
  11. void TestList2()
  12. {
  13.         int array[] = { 1, 2, 3, 4, 5, 6, };
  14.         list<int> l(array, array + sizeof(array) / sizeof(array[0]));
  15.         // 使用正向迭代器正向list中的元素
  16.         // list<int>::iterator it = l.begin();   // C++98中语法
  17.         auto it = l.begin();                     // C++11之后推荐写法
  18.         while (it != l.end())
  19.         {
  20.                 cout << *it << " ";
  21.                 ++it;
  22.         }
  23.         cout << endl;
  24.         // 使用反向迭代器逆向打印list中的元素
  25.         // list<int>::reverse_iterator rit = l.rbegin();
  26.         auto rit = l.rbegin();
  27.         while (rit != l.rend())
  28.         {
  29.                 cout << *rit << " ";
  30.                 ++rit;
  31.         }
  32.         cout << endl;
  33.         //迭代器不支持 因为 list 迭代器是双向 不支持 + -
  34.         /*l.insert(l.begin() + 3);*/
  35.         //想要实现第三个位置后插入一个数可以通过下面代码
  36.         it = l.begin();
  37.         int k = 3;
  38.         while (k--)
  39.         {
  40.                 it++;//支持++
  41.         }
  42.         l.insert(it, 40);
  43.         PrintList(l);
  44. }
复制代码
2.3 list 的修改


  1. struct A {
  2. public:
  3.         A(int a1 = 1, int a2 = 1)
  4.                 :_a1(a1)
  5.                 , _a2(a2)
  6.         {
  7.                 cout << "A(int a1=1,int a2=1)" << endl;
  8.         }
  9.         A(const A& aa)
  10.                 :_a1(aa._a1)
  11.                 , _a2(aa._a2)
  12.         {
  13.                 cout << "A(const A& aa)" << endl;
  14.         }
  15. private:
  16.         int _a1;
  17.         int _a2;
  18. };
  19. void TestList3()
  20. {
  21.         int array[] = { 1, 2, 3 };
  22.         list<int> L(array, array + sizeof(array) / sizeof(array[0]));
  23.         // 在list的尾部插入4,头部插入0
  24.         L.push_back(4);
  25.         L.push_front(0);
  26.         PrintList(L);
  27.         // 删除list尾部节点和头部节点
  28.         L.pop_back();
  29.         L.pop_front();
  30.         PrintList(L);
  31.         list<A>lt;
  32.         A aa1(1, 1);
  33.         lt.push_back(aa1);//有名对象尾插
  34.         lt.push_back(A(2, 2));//匿名对象尾插
  35.         //lt.push_back(3, 3);//不支持 push back只支持一个参数
  36.         lt.emplace_back(aa1);
  37.         lt.emplace_back(A(2, 2));
  38.         cout << endl;
  39.         // 前面的插入都是 构造+拷贝构造
  40.         // (3,3)的插入 并没有拷贝构造 性能上来说优于 push back
  41.         lt.emplace_back(3, 3);//支持 本质支持直接传构造A对象的参数
  42. }
复制代码


可以看到 emplace_back 可以支持直接传构造A的参数 省去了临时变量的拷贝构造
插入与删除

  1. // insert /erase
  2. void TestList4()
  3. {
  4.         int array1[] = { 1, 2, 3 };
  5.         list<int> L(array1, array1 + sizeof(array1) / sizeof(array1[0]));
  6.         // 获取链表中第二个节点
  7.         auto pos = ++L.begin(); //pos 可以理解为下标
  8.         cout << *pos << endl;
  9.         // 在pos前插入值为4的元素
  10.         L.insert(pos, 4);
  11.         PrintList(L);
  12.         // 在pos前插入6个值为5的元素
  13.         L.insert(pos, 6, 5);
  14.         PrintList(L);
  15.         // 在pos前插入[v.begin(), v.end)区间中的元素
  16.         vector<int> v{ 7, 8, 9 };
  17.         L.insert(pos, v.begin(), v.end());
  18.         PrintList(L);
  19.         // 删除pos位置上的元素
  20.         L.erase(pos);
  21.         PrintList(L);
  22.         // 删除list中[begin, end)区间中的元素,即删除list中的所有元素
  23.         L.erase(L.begin(), L.end());
  24.         PrintList(L);
  25. }
复制代码

  1. // resize/swap/clear
  2. void TestList5()
  3. {
  4.         // 用数组来构造list
  5.         int array1[] = { 1, 2, 3 };
  6.         list<int> l1(array1, array1 + sizeof(array1) / sizeof(array1[0]));
  7.         PrintList(l1);
  8.         // 交换l1和l2中的元素
  9.         list<int> l2;
  10.         l1.swap(l2);
  11.         PrintList(l1);
  12.         PrintList(l2);
  13.         // 将l2中的元素清空
  14.         l2.clear();
  15.         cout << l2.size() << endl;
  16. }
复制代码




2.4一些特殊接口


reverse 逆置链表 sort 排序 (算法库里没有list的排序(迭代器不匹配))

  1. void test_list4()//容器的 逆置与排序
  2. {
  3.         list<int>lt;
  4.         lt.push_back(1);
  5.         lt.push_back(2);
  6.         lt.push_back(3);
  7.         lt.push_back(4);
  8.         lt.push_back(5);
  9.         print_list(lt);
  10.         lt.reverse();
  11.         print_list(lt);
  12.         reverse(lt.begin(), lt.end());
  13.         print_list(lt);
  14.         reverse(++lt.begin(), --lt.end());
  15.         print_list(lt);
  16.         //算法库里的排序和容器的排序都是默认升序
  17.         lt.sort();
  18.         print_list(lt);
  19.         //想要降序 则需要 仿函数
  20.         //less<int>ls;//升序
  21.         //greater<int>gt;//降序
  22.         //还可以结合匿名对象使用
  23.         lt.sort(greater<int>());
  24.         print_list(lt);
  25. }
复制代码


merge 归并 与 unique 去重(条件均有序链表)
  1. void test_list5() // merge有序list合并  unique 有序list去除重复数据
  2. {
  3.         std::list<double> first, second;
  4.         first.push_back(3.1);
  5.         first.push_back(2.2);
  6.         first.push_back(2.9);
  7.         second.push_back(3.7);
  8.         second.push_back(7.1);
  9.         second.push_back(1.4);
  10.         first.sort();
  11.         second.sort();
  12.         //将 second和first合并 second置为空 前提两list有序
  13.         first.merge(second);
  14.         print_list(first);
  15.         print_list(second);
  16.         list<int>lt;
  17.         lt.push_back(1);
  18.         lt.push_back(2);
  19.         lt.push_back(2);
  20.         lt.push_back(3);
  21.         lt.push_back(3);
  22.         lt.push_back(4);
  23.         lt.push_back(1);
  24.         lt.push_back(1);
  25.         print_list(lt);
  26.         lt.sort();
  27.         //有序的数据去掉重复的数据
  28.         lt.unique();
  29.         print_list(lt);
  30. }
复制代码


剪切拼接 splice 的妙用
  1. void test_list6()
  2. {
  3.         //把一个链表结点转移给另一个链表
  4.         std::list<int> mylist1, mylist2;
  5.         std::list<int>::iterator it;
  6.         // set some initial values:
  7.         for (int i = 1; i <= 4; ++i)
  8.                 mylist1.push_back(i);      // mylist1: 1 2 3 4
  9.         for (int i = 1; i <= 3; ++i)
  10.                 mylist2.push_back(i * 10);   // mylist2: 10 20 30
  11.         it = mylist1.begin();
  12.         ++it;                         // points to 2
  13.        
  14.         mylist1.splice(it, mylist2); // mylist1: 1 10 20 30 2 3 4
  15.                                 // mylist2 (empty)
  16.                                // "it" still points to 2 (the 5th element)
  17.         print_list(mylist1);
  18.         print_list(mylist2);// splice 剪切功能
  19.         //splice 还可以调整当前list的顺序
  20.         list<int>lt;
  21.         lt.push_back(1);
  22.         lt.push_back(2);
  23.         lt.push_back(3);
  24.         lt.push_back(4);
  25.         lt.push_back(5);
  26.         print_list(lt);
  27.         int x = 0;
  28.         cin >> x;
  29.         it = find(lt.begin(), lt.end(), x);
  30.         if (it != lt.end())
  31.         {
  32.                  // 调整当前链表的元素顺序  将 it指向元素 调整到 begin
  33.                 lt.splice(lt.begin(), lt, it);
  34.                
  35.                 // //迭代器区间
  36.                 //lt.splice(lt.begin(), lt, it,lt.end());
  37.         }
  38.         print_list(lt);
  39. }
复制代码



2.5 迭代器失效题目

   前面说过,此处可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。
  因为list的底层结构为带头结点的双向循环链表,因此在 list 中进行插入时是不会导致 list 的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。
  这在 vector 是不创建的,因为 vector 的插入操作有元素移动可能导致重新设置空间,导致原有的迭代器全部失效。
  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. }
复制代码
VS2022 对于迭代器失效有着严格的监控机制直接报错

  1. // 改正
  2. void TestListIterator()
  3. {
  4.         int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
  5.         list<int> l(array, array + sizeof(array) / sizeof(array[0]));
  6.         auto it = l.begin();
  7.         while (it != l.end())
  8.         {
  9.                 l.erase(it++);   //it = l.erase(it); erase 返回下一个位置的迭代器
  10.         }
  11. }
复制代码
这里的 erase 会返回下一个结点的位置



对于 erase(it++); 可以理解为 先it++了再实行erase 代码块里的内容
3、实现list

list 容器的底层是一个带头双向循环链表