0


模拟实现STL中的list


1.设计list的结点

STL中的list采用的底层数据结构是带头双向循环链表,我们也采用这种方式,因此,结点可以这样来设计。

代码如下:为了满足容器中可以存放各种类型的数据这一需求,我们使用模板元编程的思想,将结点设计为模板类型。

  1. /* 定义节点 */
  2. template<class T> // T表示存储的数据的类型
  3. struct ListNode
  4. {
  5. ListNode<T>* _prev; // 指向前一个结点
  6. ListNode<T>* _next; // 指向后一个结点
  7. T _data; // 存储数据
  8. ListNode(const T& x = T()) // 构造函数
  9. :_next(nullptr)
  10. , _prev(nullptr)
  11. , _data(x)
  12. {}
  13. };

2.设计list的迭代器

list的底层空间不像string和vector那样是连续的,因此,list的迭代器需要对结点的指针进行封装,来模拟指针的行为。比如:连续空间上的指针进行++操作,直接就能到达后一个数据的位置,但是不连续空间上的指针进行++操作不能到达后一个数据的位置。

同样,我们需要将迭代器设计为模板类型,我们设计三个模板参数,分别是:

  • T:存储的数据的类型。
  • Ref:存储的数据的引用类型,相当于T&。
  • Ptr:存储的数据的指针类型,相当于T*。
  1. template<class T, class Ref, class Ptr>
  2. struct __list_iterator
  3. {
  4. typedef ListNode<T> Node;
  5. typedef __list_iterator<T, Ref, Ptr> self;
  6. // 成员变量
  7. Node* _node; // 对结点的指针进行封装
  8. };

之所以遮掩设计是为了同时满足const对象和非const对象的需求,const对象需要调用const版本的迭代器,非const兑现需要调用非const版本的迭代器。

代码如下:迭代器模仿的是原生指针的行为,因此,我们实现迭代器的时候,至少需要实现以下操作。

  • 前置++和后置++
  • 前置--和后置--
  • 指针的解引用(*)操作
  • 指针的箭头(->)操作
  • 判断 相等 和 不相等 操作
  1. /*
  2. * 迭代器
  3. * T:存储的元素类型
  4. * Ref:该类型的引用T&
  5. * Ptr:该类型的指针T*
  6. */
  7. template<class T, class Ref, class Ptr>
  8. struct __list_iterator
  9. {
  10. typedef ListNode<T> Node;
  11. typedef __list_iterator<T, Ref, Ptr> self;
  12. /* 成员变量 */
  13. Node* _node; // 对结点的指针进行封装即可
  14. /* 成员函数 */
  15. __list_iterator(Node* x)
  16. :_node(x)
  17. {}
  18. // ++it
  19. self& operator++() // 前置++,让当前迭代器指向下一个节点
  20. {
  21. _node = _node->_next;
  22. return *this; // 返回++以后的值
  23. }
  24. // it++
  25. self operator++(int) // 后置++,让当前迭代器指向下一个节点
  26. {
  27. //__list_iterator<T> tmp(*this);
  28. self tmp(*this);
  29. _node = _node->_next;
  30. return tmp; // 返回++之前的值
  31. }
  32. // --it
  33. self& operator--() // 前置--,让当前迭代器指向上一个节点
  34. {
  35. _node = _node->_prev;
  36. return *this; // 返回--之后的值
  37. }
  38. // it--
  39. self operator--(int) // 后置--,让当前迭代器指向上一个节点
  40. {
  41. self tmp(*this);
  42. _node = _node->_prev; // 返回--之前的值
  43. return tmp;
  44. }
  45. Ref operator*() // 返回当前结点的数据域的引用
  46. {
  47. return _node->_data;
  48. }
  49. Ptr operator->() // 返回当前结点中数据域的指针
  50. {
  51. return &(_node->_data);
  52. }
  53. bool operator!=(const self& s) // 判断两个迭代器是否不相等
  54. {
  55. return _node != s._node;
  56. }
  57. bool operator==(const self& s) // 判断两个迭代器是否相等
  58. {
  59. return _node == s._node;
  60. }
  61. };

3.list类的设计总览

我们的list类设计如下:

  • 成员变量只有一个头结点的指针。
  • 成员函数涉及迭代器的相关操作四个特殊的成员函数,插入和删除操作。
  1. template<class T> // T表示存储的数据的类型
  2. class list
  3. {
  4. private:
  5. typedef ListNode<T> Node; // 对结点类型重命名
  6. Node* _head; // 定义一个头结点
  7. public:
  8. /* 迭代器的声明 */
  9. typedef __list_iterator<T, T&, T*> iterator; // 普通版本的迭代器
  10. typedef __list_iterator<T, const T&, const T*> const_iterator; // const版本的迭代器
  11. /* 迭代器的相关操作 */
  12. iterator begin(); // 返回第一个元素的迭代器
  13. iterator end(); // 返回最后一个元素的后一个位置的迭代器
  14. const_iterator begin() const; // 提供给const对象的begin
  15. const_iterator end() const; // 提供给const对象的end
  16. /* 四个特殊的成员函数 */
  17. list(); // 无参的默认构造函数
  18. ~list(); // 析构函数
  19. list(const list<T>& lt); // 拷贝构造
  20. list<T>& operator=(const list<T>& lt); // 赋值运算符重载函数
  21. /* 插入操作 */
  22. iterator insert(iterator pos, const T& x); // 在指定位置之前插入指定元素
  23. void push_back(const T& x); // 尾插
  24. void push_front(const T& x); // 头插
  25. /* 删除操作 */
  26. iterator erase(iterator pos); // 删除指定位置的元素
  27. void pop_back(); // 尾删
  28. void pop_front(); // 头删
  29. };

4.list类的迭代器操作

begin()系列接口用于获取第一个元素的迭代器,也就是头结点后面那个元素的迭代器。

end()系列接口用于获取最后一个元素后面那个元素的迭代器,也就是头结点的迭代器。

为了满足const对象和非const对象的不同需求,我们提供const版本和非const版本。

  1. /* 迭代器的声明 */
  2. typedef __list_iterator<T, T&, T*> iterator; // 普通版本的迭代器
  3. typedef __list_iterator<T, const T&, const T*> const_iterator; // const版本的迭代器
  4. iterator begin() // 返回第一个元素的迭代器
  5. {
  6. return _head->_next; // 利用单参数的构造函数进行隐式的类型转换
  7. }
  8. iterator end() // 返回最后一个元素的后一个位置的迭代器
  9. {
  10. return _head; // 利用单参数的构造函数进行隐式的类型转换
  11. }
  12. const_iterator begin() const // 提供给const对象的begin
  13. {
  14. return _head->_next;
  15. }
  16. const_iterator end() const // 提供给const对象的end
  17. {
  18. return _head;
  19. }

5.list类的四个特殊的默认成员函数

无参的默认构造函数

  • 只需要构造出一个起哨兵作用的头结点即可

  1. // 无参的默认构造函数
  2. list()
  3. : _head(new Node)
  4. , _head->_next(_head)
  5. , _head->_prev(_head);
  6. {}

拷贝构造函数

  • list的拷贝构造函数可以复用尾插实现。(尾插在后面会讲解,先用起来)
  1. // 拷贝构造
  2. list(const list<T>& lt)
  3. : _head(new Node)
  4. , _head->_next(_head)
  5. , _head->_prev(_head);
  6. {
  7. for (const auto& e : lt)
  8. {
  9. push_back(e); // 复用尾插实现
  10. }
  11. }

赋值运算符重载函数

传统写法:释放赋值list的所有结点,然后复用尾插接口将被赋值list的所有结点尾插进来。

  1. // 传统写法的赋值运算符重载函数
  2. list<T>& operator=(const list<T>& lt)
  3. {
  4. if (this != &lt)
  5. {
  6. // 释放之前的所有结点
  7. iterator it = begin();
  8. while (it != end())
  9. {
  10. it = erase(it);
  11. }
  12. // 复用push_back尾插所有结点
  13. for (const auto& e : lt)
  14. {
  15. push_back(e);
  16. }
  17. }
  18. return *this;
  19. }

现代写法:

  • 现代写法依赖于交换函数,所以我们先实现交换两个list的函数,交换两个list只需要交换list中的 _head 指针即可。
  • 在现代写法中,我们以传值传参的方式传递参数,此时的参数就相当于被赋值对象的一份临时拷贝,也就是复用拷贝构造函数,然后将当前对象与这个临时拷贝的对象进行交换。并且这个临时对象析构的时候,自动调用析构函数,析构的是赋值对象之前的值,避免了手动释放结点。
  1. void swap(list<T>& tmp)
  2. {
  3. std::swap(_head, tmp._head);
  4. }
  5. // 现代写法的赋值运算符重载函数
  6. list<T>& operator=(const list<T> lt)
  7. {
  8. swap(lt);
  9. return *this;
  10. }

析构函数

list类涉及到资源的管理,析构函数需要显示给出,在析构的时候,需要逐个释放结点。

  1. // 析构函数
  2. ~list()
  3. {
  4. // 逐个释放节点
  5. iterator it = begin();
  6. while (it != end())
  7. {
  8. it = erase(it);
  9. }
  10. // 释放哨兵位的头结点
  11. delete _head;
  12. _head = nullptr;
  13. }

6.list类的插入操作

在指定位置之前插入

  1. // 在指定位置之前插入指定元素
  2. iterator insert(iterator pos, const T& x)
  3. {
  4. // prev newnode cur
  5. Node* cur = pos._node;
  6. Node* prev = cur->_prev;
  7. Node* newnode = new Node(x);
  8. // 连接prev和newnode
  9. prev->_next = newnode;
  10. newnode->_prev = prev;
  11. // 连接newnode和cur
  12. newnode->_next = cur;
  13. cur->_prev = newnode;
  14. return newnode;
  15. }

尾插:在最后一个结点之后插入

  1. // 尾插
  2. void push_back(const T& x)
  3. {
  4. // head -> …… -> tail -> newnode ->head
  5. Node* newnode = new Node(x);
  6. Node* tail = _head->_prev;
  7. // 连接tail和newnode
  8. tail->_next = newnode;
  9. newnode->_prev = tail;
  10. // 连接newnode和head
  11. newnode->_next = _head;
  12. _head->_prev = newnode;
  13. }
  14. // 复用insert实现尾插
  15. void push_back(const T& x)
  16. {
  17. insert(end(), x);
  18. }

头插:复用insert在begin()之前插入。

  1. // 头插
  2. void push_front(const T& x)
  3. {
  4. insert(begin(), x);
  5. }

7.list类的删除操作

删除指定位置的元素:

  1. // 删除指定位置的元素
  2. iterator erase(iterator pos)
  3. {
  4. assert(pos != end());
  5. // prev cur next
  6. Node* cur = pos._node;
  7. Node* prev = cur->_prev;
  8. Node* next = cur->_next;
  9. // 断开要删除结点和前后结点的连接
  10. prev->_next = next;
  11. next->_prev = prev;
  12. // 释放要删除的结点
  13. delete cur;
  14. // 返回指向被删除结点的后一个结点的迭代器
  15. return next;
  16. }

复用erase实现尾删和头删:

  • 尾删:end()的上一个位置就是尾结点。
  • 头删:begin()就是第一个结点的位置。
  1. // 尾删
  2. void pop_back()
  3. {
  4. erase(--end());
  5. }
  6. // 头删
  7. void pop_front()
  8. {
  9. erase(begin());
  10. }

8.list.hpp源代码

  1. #include <assert.h>
  2. namespace xy
  3. {
  4. /* 定义节点 */
  5. template<class T> // T表示存储的数据的类型
  6. struct ListNode
  7. {
  8. ListNode<T>* _prev; // 指向前一个结点
  9. ListNode<T>* _next; // 指向后一个结点
  10. T _data; // 存储数据
  11. ListNode(const T& x = T()) // 构造函数
  12. :_next(nullptr)
  13. , _prev(nullptr)
  14. , _data(x)
  15. {}
  16. };
  17. /*
  18. * 迭代器
  19. * T:存储的元素类型
  20. * Ref:该类型的引用T&
  21. * Ptr:该类型的指针T*
  22. */
  23. template<class T, class Ref, class Ptr>
  24. struct __list_iterator
  25. {
  26. typedef ListNode<T> Node;
  27. typedef __list_iterator<T, Ref, Ptr> self;
  28. /* 成员变量 */
  29. Node* _node; // 对结点的指针进行封装即可
  30. /* 成员函数 */
  31. __list_iterator(Node* x)
  32. :_node(x)
  33. {}
  34. // ++it
  35. self& operator++() // 前置++,让当前迭代器指向下一个节点
  36. {
  37. _node = _node->_next;
  38. return *this; // 返回++以后的值
  39. }
  40. // it++
  41. self operator++(int) // 后置++,让当前迭代器指向下一个节点
  42. {
  43. //__list_iterator<T> tmp(*this);
  44. self tmp(*this);
  45. _node = _node->_next;
  46. return tmp; // 返回++之前的值
  47. }
  48. // --it
  49. self& operator--() // 前置--,让当前迭代器指向上一个节点
  50. {
  51. _node = _node->_prev;
  52. return *this; // 返回--之后的值
  53. }
  54. // it--
  55. self operator--(int) // 后置--,让当前迭代器指向上一个节点
  56. {
  57. self tmp(*this);
  58. _node = _node->_prev; // 返回--之前的值
  59. return tmp;
  60. }
  61. Ref operator*() // 返回当前结点的数据域的引用
  62. {
  63. return _node->_data;
  64. }
  65. Ptr operator->() // 返回当前结点中数据域的指针
  66. {
  67. return &(_node->_data);
  68. }
  69. bool operator!=(const self& s) // 判断两个迭代器是否不相等
  70. {
  71. return _node != s._node;
  72. }
  73. bool operator==(const self& s) // 判断两个迭代器是否相等
  74. {
  75. return _node == s._node;
  76. }
  77. };
  78. template<class T> // T表示存储的数据的类型
  79. class list
  80. {
  81. private:
  82. typedef ListNode<T> Node; // 对结点类型重命名
  83. Node* _head; // 定义一个头结点
  84. public:
  85. /* 迭代器的声明 */
  86. typedef __list_iterator<T, T&, T*> iterator; // 普通版本的迭代器
  87. typedef __list_iterator<T, const T&, const T*> const_iterator; // const版本的迭代器
  88. iterator begin() // 返回第一个元素的迭代器
  89. {
  90. return _head->_next; // 利用单参数的构造函数进行隐式的类型转换
  91. }
  92. iterator end() // 返回最后一个元素的后一个位置的迭代器
  93. {
  94. return _head; // 利用单参数的构造函数进行隐式的类型转换
  95. }
  96. const_iterator begin() const // 提供给const对象的begin
  97. {
  98. return _head->_next;
  99. }
  100. const_iterator end() const // 提供给const对象的end
  101. {
  102. return _head;
  103. }
  104. /* 四个特殊的成员函数 */
  105. // 无参的默认构造函数
  106. list()
  107. : _head(new Node)
  108. , _head->_next(_head)
  109. , _head->_prev(_head);
  110. {}
  111. // 拷贝构造
  112. list(const list<T>& lt)
  113. : _head(new Node)
  114. , _head->_next(_head)
  115. , _head->_prev(_head);
  116. {
  117. for (const auto& e : lt)
  118. {
  119. push_back(e);
  120. }
  121. }
  122. // 传统写法的赋值运算符重载函数
  123. list<T>& operator=(const list<T>& lt)
  124. {
  125. if (this != &lt)
  126. {
  127. // 释放之前的所有结点
  128. iterator it = begin();
  129. while (it != end())
  130. {
  131. it = erase(it);
  132. }
  133. // 复用push_back尾插所有结点
  134. for (const auto& e : lt)
  135. {
  136. push_back(e);
  137. }
  138. }
  139. return *this;
  140. }
  141. void swap(list<T>& tmp)
  142. {
  143. std::swap(_head, tmp._head);
  144. }
  145. // 现代写法的赋值运算符重载函数
  146. list<T>& operator=(const list<T> lt)
  147. {
  148. swap(lt);
  149. return *this;
  150. }
  151. // 析构函数
  152. ~list()
  153. {
  154. iterator it = begin();
  155. while (it != end())
  156. {
  157. it = erase(it);
  158. }
  159. delete _head;
  160. _head = nullptr;
  161. }
  162. /* 插入操作 */
  163. // 在指定位置之前插入指定元素
  164. iterator insert(iterator pos, const T& x)
  165. {
  166. // prev newnode cur
  167. Node* cur = pos._node;
  168. Node* prev = cur->_prev;
  169. Node* newnode = new Node(x);
  170. // 连接prev和newnode
  171. prev->_next = newnode;
  172. newnode->_prev = prev;
  173. // 连接newnode和cur
  174. newnode->_next = cur;
  175. cur->_prev = newnode;
  176. return newnode;
  177. }
  178. // 尾插
  179. void push_back(const T& x)
  180. {
  181. // head -> …… -> tail -> newnode ->head
  182. Node* newnode = new Node(x);
  183. Node* tail = _head->_prev;
  184. // 连接tail和newnode
  185. tail->_next = newnode;
  186. newnode->_prev = tail;
  187. // 连接newnode和head
  188. newnode->_next = _head;
  189. _head->_prev = newnode;
  190. }
  191. //void push_back(const T& x)
  192. //{
  193. // insert(end(), x);
  194. //}
  195. // 头插
  196. void push_front(const T& x)
  197. {
  198. insert(begin(), x);
  199. }
  200. /* 删除操作 */
  201. // 删除指定位置的元素
  202. iterator erase(iterator pos)
  203. {
  204. assert(pos != end());
  205. // prev cur next
  206. Node* cur = pos._node;
  207. Node* prev = cur->_prev;
  208. Node* next = cur->_next;
  209. // 断开要删除结点和前后结点的连接
  210. prev->_next = next;
  211. next->_prev = prev;
  212. // 释放要删除的结点
  213. delete cur;
  214. // 返回指向被删除结点的后一个结点的迭代器
  215. return next;
  216. }
  217. // 尾删
  218. void pop_back()
  219. {
  220. erase(--end());
  221. }
  222. // 头删
  223. void pop_front()
  224. {
  225. erase(begin());
  226. }
  227. };
  228. }
标签: c++ 开发语言

本文转载自: https://blog.csdn.net/D5486789_/article/details/143833799
版权归原作者 手捧向日葵的花语 所有, 如有侵权,请联系我们删除。

“模拟实现STL中的list”的评论:

还没有评论