• C++标准模板库STL——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来说这可能是一个重要的因素)

    list的基本结构图

    2.list的使用

    我们这里可以简单看一下文档里面关于list各种构造函数的介绍

    2.1list的构造

    目前我们只掌握这四种构造函数的使用方法

    代码案例

    1. #include
    2. #include
    3. using namespace std;
    4. void test_list1()
    5. {
    6. list<int> lt1; //1.这里我们构造了一个空的list对象
    7. list<int> lt2(10, 100); // 2.这里我们通过用构造了10个100的方式构造了lt2
    8. //由于list不支持随机访问,所以下面我们需要借助迭代器遍历一下lt2
    9. cout << "lt2中的元素遍历:" << endl;
    10. list<int>::iterator it = lt2.begin();
    11. while (it != lt2.end())
    12. {
    13. cout << *it << " ";
    14. it++;
    15. }
    16. cout << endl;
    17. cout << "lt3拷贝lt2构造后的元素遍历:" << endl;
    18. list<int> lt3(lt2);
    19. list<int>::iterator its = lt3.begin();
    20. while (its != lt3.end())
    21. {
    22. cout << *its << " ";
    23. its++;
    24. }
    25. cout << endl;
    26. list<int> lt4(lt3.begin(), lt3.end());
    27. cout << "lt4用lt3的迭代器区间构造后的元素遍历:" << endl;
    28. list<int>::iterator it1 = lt4.begin();
    29. while (it1 != lt4.end())
    30. {
    31. cout << *it1 << " ";
    32. it1++;
    33. }
    34. cout << endl;
    35. }
    36. int main()
    37. {
    38. test_list1();
    39. return 0;
    40. }

    代码运行结果:

    2.2 list 迭代器 iterator 的使用

    迭代器分为正向迭代器和反向迭代器,像begin() 和end()返回的是正向迭代器,rbegin()和rend()返回的是反向迭代器,然后这两种迭代器又可以和const进行结合形成上面c++11新加的cbegin()和cend(),crbegin()和crend(),其对应的迭代器名称也要跟着变化,我们可以看一下文档中的这些函数的说明,

    注意

    iterator T* 可读可写

    const_iterator T* 只读

    const iterator 这样实现是迭代器本身不能修改

    const_iterator  重新定义的一个类型,做到的是本身可以修改,但是指向的内容不能修改

    下面我们通过代码举个例子

    测试代码:

    1. void test_list2()
    2. {
    3. //迭代器的使用,我们就简单通过常用的正反向迭代器进行说明,
    4. //前面加了const的迭代器只需要记得不能修改迭代器所指向的内容
    5. //1.正向迭代器,我们用简单的遍历list元素来说明
    6. list<int> lt;
    7. lt.push_back(1); //在list里面插入6个结点
    8. lt.push_back(2);
    9. lt.push_back(3);
    10. lt.push_back(4);
    11. lt.push_back(5);
    12. lt.push_back(6);
    13. cout << "正向迭代器的遍历:" << endl;
    14. list<int>::iterator it = lt.begin();
    15. while (it != lt.end())
    16. {
    17. cout << *it << " ";
    18. it++;
    19. }
    20. cout << endl;
    21. // 2.反向迭代器
    22. list<int>::reverse_iterator rit = lt.rbegin();
    23. cout << "反向迭代器的遍历:" << endl;
    24. while (rit != lt.rend())
    25. {
    26. cout << *rit << " ";
    27. rit++;
    28. }
    29. cout << endl;
    30. }

    注意

    1. begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动
    2. rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动

    2.3 list的容量大小接口的使用

    代码测试:

    1. void test_list3()
    2. {
    3. //empty()接口和size()接口的使用,你会发现跟vector相比没有capacity()
    4. list<int> lt;
    5. cout << "empty():" << lt.empty() << endl;
    6. cout << "size():" << lt.size() << endl;
    7. lt.push_back(1);
    8. lt.push_back(2);
    9. lt.push_back(3);
    10. cout << "插入三个元素之后" << endl;
    11. cout << "empty():" << lt.empty() << endl;
    12. cout << "size():" << lt.size() << endl;
    13. }

    运行结果:

    2.4 list访问头尾元素的接口

    测试代码:

    1. void test_list4()
    2. {
    3. //front()接口和 back()接口的测试
    4. list<int> lt;
    5. lt.push_back(1);
    6. lt.push_back(2);
    7. lt.push_back(3);
    8. cout << "遍历" << endl;
    9. list<int>::iterator it = lt.begin();
    10. while (it != lt.end())
    11. {
    12. cout << *it << " ";
    13. it++;
    14. }
    15. cout << endl;
    16. cout << "第一个元素" << endl;
    17. cout << lt.front() << endl;
    18. cout << "最后一个元素" << endl;
    19. cout << lt.back() << endl;
    20. }

    测试结果:

    这个list底层是一个双向循环链表,所以通过头结点访问到第一个元素和最后一个元素比较简单,但是中间的元素就不支持随机访问了。

    2.5 list Modifiers

    我们先了解下以下这几个接口的使用方法

    1. //头插
    2. void push_front (const value_type& val);
    3. //头删
    4. void pop_front();
    5. //尾插
    6. void push_back (const value_type& val);
    7. //尾删
    8. void pop_back();
    9. //在pos位置插入元素val
    10. single element (1)
    11. iterator insert (iterator position, const value_type& val);
    12. //在pos位置插入n个元素val
    13. fill (2)
    14. void insert (iterator position, size_type n, const value_type& val);
    15. //在pos位置插入一段迭代器区间的结点
    16. range (3)
    17. template <class InputIterator>
    18. void insert (iterator position, InputIterator first, InputIterator last);
    19. //删除pos位置的结点
    20. iterator erase (iterator position);
    21. //删除一段迭代器区间的结点
    22. iterator erase (iterator first, iterator last);
    23. //交换两个链表,实际上只需要将头结点的指针跟大小size进行交换即可
    24. void swap (list& x);
    25. //将链表数据清空
    26. void clear();

    下面带大家来看看我们的使用案例

    测试代码:

    1. void test_list5()
    2. {
    3. list<int> lt;
    4. //尾插
    5. for (int i = 0; i < 10; i++)//尾插后的结点值是0 1 2 3 4 5 6 7 8 9
    6. {
    7. lt.push_back(i);
    8. }
    9. //我们这里使用简单的范围for来进行遍历
    10. cout << "尾插后链表中的结点值为:" << endl;
    11. for (auto e : lt)//范围for借助迭代器自动推导e的类型,自动给e赋值,自动往后++
    12. {
    13. cout << e << " ";
    14. }
    15. cout << endl;
    16. //头插
    17. lt.push_front(10);
    18. lt.push_front(20); //头插这两个结点后变成:20 10 0 1 2 3 4 5 6 7 8 9
    19. cout << "头插后的链表结点值为:" << endl;
    20. for (auto e : lt)
    21. {
    22. cout << e << " ";
    23. }
    24. cout << endl;
    25. //头删
    26. lt.pop_front();
    27. lt.pop_front();
    28. lt.pop_front(); //头删之后变成:1 2 3 4 5 6 7 8 9
    29. cout << "头删之后:" << endl;
    30. for (auto e : lt)
    31. {
    32. cout << e << " ";
    33. }
    34. cout << endl;
    35. //尾删
    36. lt.pop_back();
    37. lt.pop_back();
    38. lt.pop_back(); //尾删之后变成: 1 2 3 4 5 6
    39. cout << "尾删之后:" << endl;
    40. for (auto e : lt)
    41. {
    42. cout << e << " ";
    43. }
    44. cout << endl;
    45. //在pos位置插入一个值为val的结点
    46. list<int>::iterator it = lt.begin();
    47. it++;
    48. lt.insert(it, 20); //此时变成 1 20 2 3 4 5 6
    49. cout << "在第2个结点位置插入一个20:" << endl;
    50. for (auto e : lt)
    51. {
    52. cout << e << " ";
    53. }
    54. cout << endl;
    55. //在pos位置插入n个值为val的结点
    56. it++;
    57. ++it;
    58. lt.insert(it, 5, 30);//在第4个结点的位置插入5个30
    59. cout << "在第4个结点的位置插入5个30后:" << endl;
    60. for (auto e : lt)
    61. {
    62. cout << e << " ";
    63. }
    64. cout << endl;
    65. //在pos位置插入一段迭代器区间
    66. list<int> lt2(10, 100);
    67. lt.insert(it, lt2.begin(), lt2.end());//将lt2的10个值为100的结点从lt的第4个结点插入
    68. cout << "插入一段迭代器区间之后:" << endl;
    69. for (auto e : lt)
    70. {
    71. cout << e << " ";
    72. }
    73. cout << endl;
    74. //删除pos位置的结点 这里我们会涉及一个迭代器失效的问题,我们后面再说
    75. it = lt.erase(it);
    76. cout << "删除it位置的结点后:" << endl;
    77. for (auto e : lt)
    78. {
    79. cout << e << " ";
    80. }
    81. cout << endl;
    82. //删除一段迭代器区间的结点
    83. it = lt.erase(it, lt.end());//我们将it以及之后的结点都删除
    84. cout << "将it后面位置的结点都删除了" << endl;
    85. for (auto e : lt)
    86. {
    87. cout << e << " ";
    88. }
    89. cout << endl;
    90. //交换两个链表
    91. //我们先看看lt和lt2的两个链表结点的值,然后交换
    92. cout << "lt:" << endl;
    93. for (auto e : lt)
    94. {
    95. cout << e << " ";
    96. }
    97. cout << endl;
    98. cout << "lt2:" << endl;
    99. for (auto e : lt2)
    100. {
    101. cout << e << " ";
    102. }
    103. cout << endl;
    104. //交换后
    105. lt.swap(lt2);
    106. cout << "交换后:" << endl;
    107. cout << "lt:" << endl;
    108. for (auto e : lt)
    109. {
    110. cout << e << " ";
    111. }
    112. cout << endl;
    113. cout << "lt2:" << endl;
    114. for (auto e : lt2)
    115. {
    116. cout << e << " ";
    117. }
    118. cout << endl;
    119. //将链表数据清空
    120. cout << "将两个链表的数据都清空之后:" << endl;
    121. lt.clear();
    122. lt2.clear();
    123. cout << "lt:" << endl;
    124. for (auto e : lt)
    125. {
    126. cout << e << " ";
    127. }
    128. cout << endl;
    129. cout << "lt2:" << endl;
    130. for (auto e : lt2)
    131. {
    132. cout << e << " ";
    133. }
    134. cout << endl;
    135. }

    测试结果:

    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. //其赋值
    10. l.erase(it);
    11. ++it;
    12. }
    13. }

    运行这段代码之后,因为迭代器失效导致运行失败

    所以当我们删除了某个结点之后,迭代器需要重新赋值,而为了解决这个问题,给erase这个函数添加了一个返回值,返回一个迭代器,返回被删除的结点的后一个结点的迭代器这样用it可以接受就可以使得迭代器it再次生效

    将代码改正之后:

    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);
    10. }
    11. }

    这样就没有什么大问题了

    3. list 的模拟实现

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. using namespace std;
    6. namespace mylist
    7. {
    8. template<class T>
    9. //链表的每个结点的结构
    10. struct list_node
    11. {
    12. T _data; //存放数据
    13. list_node* _prev;//指向前一个结点的指针
    14. list_node* _next;//指向后一个结点的指针
    15. list_node(const T& val = T()) //构造一个结点对象
    16. :_data(val)
    17. , _prev(nullptr)
    18. , _next(nullptr)
    19. { }
    20. };
    21. // T T& T*
    22. // T cosnt T& const T*
    23. template<class T,class Ref,class Ptr>
    24. struct __list_iterator //list的迭代器结构
    25. {
    26. typedef list_node Node; //结点
    27. typedef __list_iterator _self; //_self就是一个实例化的迭代器
    28. Node* _node; //结点的指针
    29. __list_iterator(Node* node)
    30. :_node(node)
    31. {
    32. }
    33. Ref operator*() //重载运算符* 通过*能够访问结点里面的数据
    34. {
    35. return _node->_data;
    36. }
    37. Ptr operator->() //重载-> , 结构体指针访问成员可以用结构体对象->结构体成员
    38. {
    39. return &_node->_data;
    40. }
    41. _self& operator++() //重载运算符前置++,返回下一个结点的迭代器
    42. {
    43. _node = _node->_next;
    44. return (*this);
    45. }
    46. _self& operator--() //重载运算符前置--,返回前一个结点的迭代器
    47. {
    48. _node = _node->_prev;
    49. return (*this);
    50. }
    51. _self operator++(int) //重载运算符后置++,返回当前结点的迭代器的拷贝再++
    52. {
    53. Node* tmp(_node);
    54. _node = _node->_next;
    55. return tmp;
    56. }
    57. _self operator--(int) //重载运算符前置--,返回当前一个结点迭代器的拷贝再--
    58. {
    59. Node* tmp(_node);
    60. _node = _node->_prev;
    61. return tmp;
    62. }
    63. bool operator!=(const _self& n) //重载迭代器的比较运算符!=
    64. {
    65. return this->_node != n._node;
    66. }
    67. bool operator==(const _self& n) //重载迭代器的比较运算符==
    68. {
    69. return this->_node == n._node;
    70. }
    71. };
    72. template<class T>
    73. class list
    74. {
    75. typedef list_node Node;
    76. public:
    77. typedef __list_iterator iterator;
    78. typedef __list_iteratorconst T&, const T*> const_iterator;
    79. const_iterator begin() const
    80. {
    81. return const_iterator(_head->_next);
    82. }
    83. const_iterator end() const
    84. {
    85. return const_iterator(_head);
    86. }
    87. iterator begin()
    88. {
    89. return (_head->_next);
    90. }
    91. iterator end()
    92. {
    93. return _head;
    94. }
    95. void empty_init()
    96. {
    97. _head = new Node;
    98. _head->_next = _head;
    99. _head->_prev = _head;
    100. _size = 0;
    101. }
    102. void clear()
    103. {
    104. iterator it = begin();
    105. while (it != end())
    106. {
    107. it = erase(it);
    108. }
    109. }
    110. void swap(list& lt)
    111. {
    112. std::swap(_head, lt._head);
    113. std::swap(_size, lt._size);
    114. }
    115. list()
    116. {
    117. empty_init();
    118. }
    119. //lt1(lt2)
    120. list(const list& lt)
    121. {
    122. empty_init();
    123. for (auto e : lt)
    124. {
    125. push_back(e);
    126. }
    127. }
    128. ~list()
    129. {
    130. clear();
    131. delete _head;
    132. _head = nullptr;
    133. }
    134. list<int>& operator=(list<int>lt)
    135. {
    136. swap(lt);
    137. return *this;
    138. }
    139. void push_back(const T& x)
    140. {
    141. insert(end(), x);
    142. }
    143. void push_front(const T& x)
    144. {
    145. insert(begin(), x);
    146. }
    147. void pop_back(const T& x)
    148. {
    149. erase(--end());
    150. }
    151. void pop_front(const T& x)
    152. {
    153. erase(begin());
    154. }
    155. iterator insert(iterator pos, const T& x)
    156. {
    157. Node* newnode = new Node(x);
    158. Node* cur = pos._node;
    159. Node* prev = cur->_prev;
    160. prev->_next = newnode;
    161. newnode->_prev = prev;
    162. newnode->_next = cur;
    163. cur->_prev = newnode;
    164. ++_size;
    165. return iterator(newnode);
    166. }
    167. iterator erase(iterator pos)
    168. {
    169. Node* cur = pos._node;
    170. Node* prev = cur->_prev;
    171. Node* next = cur->_next;
    172. delete cur;
    173. prev->_next = next;
    174. next->_prev = prev;
    175. --_size;
    176. return iterator(next);
    177. }
    178. size_t size()
    179. {
    180. return _size;
    181. }
    182. private:
    183. Node* _head; //list的头结点
    184. size_t _size;//list的大小
    185. };
    186. }

    4. list和vector的比较

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

  • 相关阅读:
    11月25日:tp框架中的架构,配置,路由,控制器
    Docker的基本操作
    【校招VIP】java开源框架之Zookeeper
    大爷,快来玩呀!带禁手规则的五子棋实践强化学习理论
    Java枚举类 (详细解析java中的枚举类深入浅出)
    Qt自定义Widget实现互斥效果问题
    一文搞懂 MySQL 索引数据结构
    Vue+ElementUI技巧分享:自定义表单项label的文字提示
    EN 12467纤维水泥平板产品—CE认证
    MySql——InnoDB引擎总体架构
  • 原文地址:https://blog.csdn.net/weixin_63181097/article/details/133255298