• 【C++】STL之list深度剖析及模拟实现


    目录

    前言

    一、list 的使用

     1、构造函数

    2、迭代器

    3、增删查改

    4、其他函数使用

    二、list 的模拟实现

     1、节点的创建

     2、push_back 和 push_front

     3、普通迭代器

     4、const 迭代器

     5、增删查改(insert、erase、pop_back、pop_front)

     6、构造函数和析构函数

      6.1、默认构造

      6.2、构造 n 个 val 的对象

      6.3、拷贝构造

      6.4、迭代器区间构造

      6.5、 赋值运算符重载

      6.6、析构函数

    三、list 模拟实现源代码

    四、list 的迭代器失效

    五、list 和 vector的对比


    前言

    1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
    2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向 其前一个元素和后一个元素。
    3. list 与 forward_list 非常相似:最主要的不同在于 forward_list 是单链表,只能朝前迭代,已让其更简单高效。
    4. 与其他的序列式容器相比(array,vector,deque),list 通常在任意位置进行插入、移除元素的执行效率更好。
    5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问.

    一、list 的使用

     1、构造函数

    构造函数接口说明
    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. int main()
    2. {
    3. // 默认构造
    4. list<int> lt;
    5. lt.push_back(1);
    6. lt.push_back(2);
    7. lt.push_back(3);
    8. lt.push_back(4);
    9. // 拷贝构造
    10. list<int> lt2(lt);
    11. // 构造 n 个节点
    12. list<int> lt3(5, 1);
    13. // 迭代器区间构造
    14. list<int> lt4(lt.begin(), lt.end());
    15. return 0;
    16. }

    2、迭代器

    函数声明接口说明
    begin + end返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器
    rbegin + rend返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的 reverse_iterator,即begin位置
    1. int main()
    2. {
    3. int a[] = { 1,2,3,4,5,6,7,8,9 };
    4. list<int> lt(a, a + 9);
    5. auto it = lt.begin();
    6. while (it != lt.end())
    7. {
    8. cout << *it << " ";
    9. it++;
    10. }
    11. cout << endl;
    12. return 0;
    13. }

    迭代器一般是用来遍历和查找的; 

    而反向迭代器的使用是类似的,只不过调用的函数换成了 rbegin 和 rend 。

    注意:反向迭代器的迭代使用的也是++。但迭代器区间一样是[rbegin, rend);

    3、增删查改

    函数声明接口说明
    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 main()
    2. {
    3. vector<int> v = { 1,2,3,4,5,6,7,8,9 };
    4. list<int> lt(v.begin(), v.end());
    5. for (auto e : lt) cout << e << " ";
    6. cout << endl;
    7. lt.push_front(10);
    8. lt.push_back(20);
    9. for (auto e : lt) cout << e << " ";
    10. cout << endl;
    11. lt.pop_front();
    12. lt.pop_back();
    13. for (auto e : lt) cout << e << " ";
    14. cout << endl;
    15. auto pos = find(lt.begin(), lt.end(), 5);
    16. lt.insert(pos, 50);
    17. for (auto e : lt) cout << e << " ";
    18. cout << endl;
    19. pos = find(lt.begin(), lt.end(), 8);
    20. lt.erase(pos);
    21. for (auto e : lt) cout << e << " ";
    22. cout << endl;
    23. return 0;
    24. }

    4、其他函数使用

    函数声明接口说明
    empty检测 list 是否为空,是返回 true ,否则返回 false
    size返回 list 中有效节点的个数
    front返回 list 的第一个节点中值的引用
    back返回 list 的最后一个节点中值的引用

    二、list 的模拟实现

     1、节点的创建

    1. template<class T>
    2. struct list_node//节点
    3. {
    4. list_node* _next;
    5. list_node* _prev;
    6. T _data;
    7. // 构造函数
    8. list_node(const T& x = T())
    9. :_next(nullptr)
    10. , _prev(nullptr)
    11. , _data(x)
    12. {}
    13. };

       由于节点存储的数据可能是任意类型,所以我们需要将将节点定义为模板类。这里我们需要写一个给缺省值的默认构造函数,便于之后在主类中new一个新节点时直接初始化,同时将两个指针置为空,将数据写入数据域中。

     2、push_back 和 push_front

    1. class list
    2. {
    3. public:
    4. typedef list_node node;
    5. private:
    6. node* _head;
    7. }
    8. //尾插
    9. void push_back(const T& x) const
    10. {
    11. node* new_node = new node(x);
    12. node* tail = _head->_prev;
    13. //链接节点之间的关系
    14. tail->_next = new_node;
    15. new_node->_prev = tail;
    16. new_node->_next = _head;
    17. _head->_prev = new_node;
    18. }
    19. //头插
    20. void push_front(const T& x)
    21. {
    22. node* head = _head->_next;
    23. node* new_node = new node(x);
    24. _head->_next = new_node;
    25. new_node->_prev = _head;
    26. new_node->_next = head;
    27. head->_prev = new_node;
    28. }

     这里模拟的头插和尾插也很简单,因为和我们之前在数据结构时候的双向循环链表是一样的,只需要找到头或者尾,然后链接四个节点间的关系即可。

     3、普通迭代器

    注意:list 的迭代器是自定义类型,不是原生指针node*。

    迭代器为自定义类型,其中*,++等都是通过运算符重载来完成的。

    所以我们需要重载的符号:*,->,前置++,后置++,前置--,后置--,!=,==

    1. template<class T>
    2. struct __list_iterator
    3. {
    4. typedef list_node node;
    5. typedef __list_iterator self;
    6. node* _node;
    7. //构造函数
    8. __list_iterator(node* n)
    9. :_node(n)
    10. {}
    11. //重载*运算符
    12. T& operator*()
    13. {
    14. return _node->_val;
    15. }
    16. T* operator->()
    17. {
    18. return &_node->_data;
    19. }
    20. //重载前置++运算符
    21. self& operator++()
    22. {
    23. _node = _node->_next;
    24. return *this;
    25. }
    26. //重载后置++运算符
    27. self operator++(int)
    28. {
    29. self tmp(*this);
    30. _node = _node->_next;
    31. return tmp;
    32. }
    33. //重载前置--运算符
    34. self& operator--()
    35. {
    36. _node = _node->_prev;
    37. return *this;
    38. }
    39. //重载后置--运算符
    40. self operator--(int)
    41. {
    42. self tmp(*this);
    43. _node = _node->_prev;
    44. return tmp;
    45. }
    46. //重载!=运算符
    47. bool operator!=(const self& s)
    48. {
    49. return _node != s._node;
    50. }
    51. //重载==运算符
    52. bool operator==(const self& s)
    53. {
    54. return _node == s._node;
    55. }
    56. };

     此处我实现了一个简单的正向迭代器,使用一个模板参数T表示类型。

     当普通迭代器封装好了之后,我们需要在list类中来实现它的 begin() 和 end() 方法。由于迭代器的名字一般都是 iterator,而且对于范围for来说,也只能通过 iterator 来转换为迭代器进行遍历。所以这里我们将其typedef为iterator。

    1. template<class T>
    2. class list//链表
    3. {
    4. typedef list_node node;
    5. public:
    6. typedef __list_iterator iterator;
    7. iterator begin()
    8. {
    9. return iterator(_head->_next);
    10. }
    11. iterator end()
    12. {
    13. return iterator(_head);
    14. }
    15. private:
    16. node* _head;
    17. };

     4、const 迭代器

      const迭代器与普通迭代器的区别在于const迭代器指向的内容是不能修改的,但是它的指向是可以修改的。

    1. template<class T>
    2. class list//链表
    3. {
    4. typedef list_node node;
    5. public:
    6. typedef __list_const_iterator const_iterator;
    7. const_iterator begin()
    8. {
    9. return const_iterator(_head->_next);
    10. }
    11. const_iterator end()
    12. {
    13. return const_iterator(_head);
    14. }
    15. private:
    16. node* _head;
    17. };

      我们最好的做法就是在__list_iterator 的类模板中的添加两个模板参数,然后再 list 类中 typedef 两份分别将第二个参数分别改成 T& 和 const T& 的类型,本质上就是让编译器根据传入的 Ref 的不同来自动示例化出 const 迭代器类,而我们还需要重载一个->运算符,因为list中可能存储的是自定义类型,这个自定义类型如果要是有多个成员变量的话,我们就需要使用->来解引用访问成员变量,同样还是要区分普通迭代器和const 迭代器,所以就增加了另一个模版参数 Ptr。具体的解决做法如下:

    1. template<class T, class Ref, class Ptr>
    2. struct __list_iterator
    3. {
    4. typedef list_node node;
    5. typedef __list_iterator self;
    6. node* _node;
    7. __list_iterator(node* n)
    8. :_node(n)
    9. {}
    10. Ref operator*()//解引用
    11. {
    12. return _node->_data;
    13. }
    14. Ptr operator->()
    15. {
    16. return &_node->_data;
    17. }
    18. ...
    19. };

    然后,最终在链表类中使用如下:

    1. template<class T>
    2. class list//链表
    3. {
    4. typedef list_node node;
    5. public:
    6. typedef __list_iterator iterator;//普通迭代器
    7. typedef __list_iteratorconst T&, const T*> const_iterator;//const迭代器
    8. iterator begin()
    9. {
    10. return iterator(_head->_next);//匿名对象的返回
    11. }
    12. const_iterator begin() const
    13. {
    14. return const_iterator(_head->_next);
    15. }
    16. iterator end()
    17. {
    18. return iterator(_head);
    19. }
    20. const_iterator end() const
    21. {
    22. return const_iterator(_head);
    23. }
    24. private:
    25. node* _head;
    26. };

     5、增删查改(insert、erase、pop_back、pop_front)

    1. // 指定位置插入
    2. void insert(iterator pos, const T& x)
    3. {
    4. node* cur = pos._node;
    5. node* prev = cur->_prev;
    6. node* new_node = new node(x);
    7. prev->_next = new_node;
    8. new_node->_prev = prev;
    9. new_node->_next = cur;
    10. cur->_prev = new_node;
    11. }
    12. // 指定位置删除
    13. iterator erase(iterator pos)
    14. {
    15. assert(pos != end());
    16. node* prev = pos._node->_prev;
    17. node* next = pos._node->_next;
    18. prev->_next = next;
    19. next->_prev = prev;
    20. delete pos._node;
    21. return iterator(next);
    22. }
    23. // 尾删
    24. void pop_back()
    25. {
    26. erase(--end());
    27. }
    28. // 头删
    29. void pop_front()
    30. {
    31. erase(begin());
    32. }

     6、构造函数和析构函数

      6.1、默认构造

      由于后面会频繁对空进行初始化,所以在这里对它进行了封装,方便后面的调用。

    1. void empty_init()//空初始化
    2. {
    3. _head = new node;
    4. _head->_next = _head;
    5. _head->_prev = _head;
    6. }
    7. list()
    8. {
    9. empty_init();
    10. }

      6.2、构造 n 个 val 的对象

    1. //用n个val构造对象
    2. list(int n, const T& val = T())
    3. {
    4. empty_init();
    5. for (int i = 0; i < n; i++)
    6. {
    7. push_back(val);
    8. }
    9. }

      6.3、拷贝构造

    1. //拷贝构造传统写法
    2. list(const list& lt)
    3. {
    4. empty_init();
    5. for (auto e : lt)
    6. {
    7. push_back(e);
    8. }
    9. }
    10. //拷贝构造现代写法
    11. list(const list& lt)
    12. {
    13. empty_init();
    14. list tmp(lt.begin(), lt.end());
    15. swap(tmp);
    16. }

      6.4、迭代器区间构造

    1. template <class Iterator>
    2. list(Iterator first, Iterator last)
    3. {
    4. empty_init();
    5. while (first != last)
    6. {
    7. push_back(*first);
    8. ++first;
    9. }
    10. }

      6.5、 赋值运算符重载

    1. //赋值运算符重载
    2. list& operator=(list lt)//注意这里不能用引用
    3. {
    4. swap(lt);
    5. return *this;
    6. }

      6.6、析构函数

    1. //要全部清理掉
    2. ~list()
    3. {
    4. clear();
    5. delete _head;
    6. _head = nullptr;
    7. }
    8. //不释放头结点
    9. void clear()
    10. {
    11. iterator it = begin();
    12. while (it != end())
    13. {
    14. it = erase(it);
    15. //这样也可以
    16. //erase(it++);
    17. }
    18. }

    三、list 模拟实现源代码

    1. template<class T>
    2. struct list_node//节点
    3. {
    4. list_node* _next;
    5. list_node* _prev;
    6. T _data;
    7. list_node(const T& x = T())
    8. :_next(nullptr)
    9. , _prev(nullptr)
    10. , _data(x)
    11. {}
    12. };
    13. template<class T, class Ref, class Ptr>
    14. struct __list_iterator
    15. {
    16. typedef list_node node;
    17. typedef __list_iterator self;
    18. node* _node;
    19. __list_iterator(node* n)
    20. :_node(n)
    21. {}
    22. Ref operator*()//解引用
    23. {
    24. return _node->_data;
    25. }
    26. Ptr operator->()
    27. {
    28. return &_node->_data;
    29. }
    30. //前置++
    31. self& operator++()
    32. {
    33. _node = _node->_next;
    34. return *this;
    35. }
    36. //后置++
    37. self operator++(int)
    38. {
    39. self tmp(*this);
    40. _node = _node->_next;
    41. return tmp;
    42. }
    43. //前置--
    44. self& operator--()
    45. {
    46. _node = _node->_prev;
    47. return *this;
    48. }
    49. //后置--
    50. self operator--(int)
    51. {
    52. self tmp(*this);
    53. _node = _node->_prev;
    54. return tmp;
    55. }
    56. bool operator!=(const self& s)
    57. {
    58. return _node != s._node;
    59. }
    60. bool operator==(const self& s)
    61. {
    62. return _node == s._node;
    63. }
    64. };
    65. template<class T>
    66. class list//链表
    67. {
    68. typedef list_node node;
    69. public:
    70. typedef __list_iterator iterator;//普通迭代器
    71. typedef __list_iteratorconst T&, const T*> const_iterator;//const迭代器
    72. iterator begin()
    73. {
    74. return iterator(_head->_next);//匿名对象的返回
    75. }
    76. const_iterator begin() const
    77. {
    78. return const_iterator(_head->_next);
    79. }
    80. iterator end()
    81. {
    82. return iterator(_head);
    83. }
    84. const_iterator end() const
    85. {
    86. return const_iterator(_head);
    87. }
    88. void empty_init()//空初始化
    89. {
    90. _head = new node;
    91. _head->_next = _head;
    92. _head->_prev = _head;
    93. }
    94. list()
    95. {
    96. empty_init();
    97. }
    98. //迭代器区间构造
    99. template <class Iterator>
    100. list(Iterator first, Iterator last)
    101. {
    102. empty_init();
    103. while (first != last)
    104. {
    105. push_back(*first);//push_back使用的前提是要有哨兵位的头结点
    106. ++first;
    107. }
    108. }
    109. // 交换函数
    110. void swap(list& tmp)
    111. {
    112. std::swap(_head, tmp._head);
    113. }
    114. //现代拷贝构造
    115. list(const list& lt)
    116. {
    117. list tmp(lt.begin(), lt.end());
    118. swap(tmp);
    119. }
    120. //现代赋值写法
    121. list& operator=(list lt)
    122. {
    123. swap(lt);
    124. return *this;
    125. }
    126. ~list()//要全部清理掉
    127. {
    128. clear();
    129. delete _head;
    130. _head = nullptr;
    131. }
    132. void clear()//不释放头结点
    133. {
    134. iterator it = begin();
    135. while (it != end())
    136. {
    137. it = erase(it);
    138. //这样也可以
    139. //erase(it++);
    140. }
    141. }
    142. void insert(iterator pos, const T& x)
    143. {
    144. node* cur = pos._node;
    145. node* prev = cur->_prev;
    146. node* new_node = new node(x);
    147. prev->_next = new_node;
    148. new_node->_prev = prev;
    149. new_node->_next = cur;
    150. cur->_prev = new_node;
    151. }
    152. iterator erase(iterator pos)
    153. {
    154. assert(pos != end());
    155. node* prev = pos._node->_prev;
    156. node* next = pos._node->_next;
    157. prev->_next = next;
    158. next->_prev = prev;
    159. delete pos._node;
    160. return iterator(next);
    161. }
    162. //尾插
    163. void push_back(const T& x) const
    164. {
    165. //node* new_node = new node(x);
    166. //node* tail = _head->_prev;
    167. 链接节点之间的关系
    168. //tail->_next = new_node;
    169. //new_node->_prev = tail;
    170. //new_node->_next = _head;
    171. //_head->_prev = new_node;
    172. insert(end(), x);
    173. }
    174. //头插
    175. void push_front(const T& x)
    176. {
    177. //node* head = _head->_next;
    178. //node* new_node = new node(x);
    179. //_head->_next = new_node;
    180. //new_node->_prev = _head;
    181. //new_node->_next = head;
    182. //head->_prev = new_node;
    183. insert(begin(), x);
    184. }
    185. //尾删
    186. void pop_back()
    187. {
    188. erase(--end());
    189. }
    190. //头删
    191. void pop_front()
    192. {
    193. erase(begin());
    194. }
    195. private:
    196. node* _head;
    197. };

    四、list 的迭代器失效

      当我们使用 erase 进行删除后,此时指向删除位置的迭代器就失效了,再次使用就会令程序崩溃。

      因此若要多次删除,则需要在使用后利用 erase 的返回值更新迭代器,这样使用才不会出现错误。

    1. int main()
    2. {
    3. vector<int> v = { 1, 2,3,5,4,6 };
    4. list<int> lt(v.begin(), v.end());
    5. list<int>::iterator pos = find(lt.begin(), lt.end(), 3);
    6. for (int i = 0; i < 3; i++)
    7. {
    8. pos = lt.erase(pos); //利用erase的返回值更新迭代器
    9. }
    10. for (auto e : lt) cout << e << " ";
    11. cout << endl;
    12. return 0;
    13. }

    五、list 和 vector的对比

    vectorlist
    底 层 结 构动态顺序表,一段连续空间带头结点的双向循环链表
    随 机 访 问支持随机访问,访问某个元素效率O(1)不支持随机访问,访问某个元素 效率O(N)
    插 入 和 删 除任意位置插入和删除效率低,需要搬移元素,时间复杂 度为O(N),插入时有可能需要增容,增容:开辟新空 间,拷贝元素,释放旧空间,导致效率更低任意位置插入和删除效率高,不 需要搬移元素,时间复杂度为 O(1)
    空 间 利 用 率底层为连续空间,不容易造成内存碎片,空间利用率 高,缓存利用率高底层节点动态开辟,小节点容易 造成内存碎片,空间利用率低, 缓存利用率低
    迭 代 器原生态指针对原生态指针(节点指针)进行封装
    迭 代 器 失 效在插入元素时,要给所有的迭代器重新赋值,因为插入 元素有可能会导致重新扩容,致使原来迭代器失效,删 除时,当前迭代器需要重新赋值否则会失效插入元素不会导致迭代器失效, 删除元素时,只会导致当前迭代 器失效,其他迭代器不受影响
    使 用 场 景需要高效存储,支持随机访问,不关心插入删除效率大量插入和删除操作,不关心随机访问


    本文要是有不足的地方,欢迎大家在下面评论,我会在第一时间更正。

     

  • 相关阅读:
    HTTP代理是什么,有什么用?
    基于BP神经网络的手写数字识别问题研究附Matlab代码
    利用Freemaker模板引擎制作包含表格和图片的word导出模板
    Tomcat部署本地和服务器Springboot和Vue项目
    网络编程day6——基于C/S架构封装的线程池
    蓝桥杯入门即劝退(五)跑断腿的小蓝
    MFC Windows 程序设计[127]之菜单初体验
    nuc flycontroler dimension
    【FPGA教程案例89】编译码2——使用vivado核实现RS信道编译码
    SpringBoot自动装配原理(简单易懂)
  • 原文地址:https://blog.csdn.net/m0_63198468/article/details/132572068