当前位置:   article > 正文

list的模拟实现

list的模拟实现

一、list的概念引入

1.vector与list的对比

为什么会有list?

补充vector的缺点存在的

vector缺点:

1.头部和中部的插入删除效率低,O(N)因为需要挪动数据。

2.插入数据空间不够需要增容。增容需要开新空间、拷贝数据、会付出很大的代价。

优点:

1、支持下标的随机访问。间接的就很好的支持排序、二分查找、堆算法等等。

list出厂就是为了解决vector的缺陷

优点:

1.list头部、中间插入不再需要挪动数据,效率高。O(1)

2.list插入数据是新增节点,不需要增容

缺点:

不支持随机访问。

2.关于struct和class的使用

C++中的struct和class的唯一区别在于默认访问权限(即你不写public、private这种访问限定符)不同,struct默认为共有(public),而class默认为私有(private),一般情况,成员部分私有,部分公有,就用class,所有成员都开放或共有,就用struct

所以下文写的节点和迭代器类都用struct是因为,struct中的成员函数都默认为公有理,也不用写public了,但是你用class就要写个public

3.list的迭代器失效问题

list本质:带头双向循环链表

支持操作接口的角度分迭代器的类型:单向(forward_list)、双向(list)、随机(vector)

从使用场景的角度分迭代器的类型:(正向迭代器、反向迭代器)+const迭代器

二、list的模拟实现

1.list三个基本函数类

list本质是一个带头双向循环链表:

模拟实现list,要实现下列三个类

1) 模拟实现结点类

2) 模拟实现迭代器的类

3) 模拟实现list主要功能的类

list的类的模拟实现其基本功能(增删等操作)要建立在迭代器类和节点类均已实现好的情况下才得以完成。

2.list的结点类实现

因为list的本质为带头双向循环链表,所以其每个结点都要确保有下列成员:

1.前驱指针

2.后继指针

3.data值存放数据

而结点类的内部只需要实现一个构造函数即可。

  1. template<class T>
  2. struct _list_node
  3. {
  4. listnode<T>* _next;
  5. listnode<T>* _prev;
  6. T _data;
  7. listnode(const T& val=T())
  8. :_next(nullptr)
  9. ,_prev(nullptr)
  10. ,_data(val)
  11. {}
  12. };

为什么是_list_node<T>?

首先,C++中用struct定义是可不加struct,重点是这里用了一个类模板,类模板的类名不是真正的类型且类模板不支持自动推类型,即_list_node不是真正的类型,定义变量时,_list_node<T>这种才是真正的类型,也就是用类模板定义变量时必须指定对应的类型。

注:结构体模板或类模板在定义时可不加<T>,但使用时必须加<T>。

3.list的迭代器类的实现

因为list其本质就是带头双向循环链表,而链表的物理空间是不连续的,是通过结点的指针顺次链接,我们不能向先前的string和vector一样直接解引用还是结点,结点指针++还是结点指针,而string和vector的物理空间是连续的,所以这俩不需要实现迭代器类,可以直接使用。

为了能让list像vector一样接应用后访问对应节点中的值,++访问到下一个数据,我们需要单独写一个迭代器类的接口实现,在其内部进行封装补齐相应的功能,而这就要借助运算符重载来完成。

注:迭代器封装后是想模拟指针的行为

3.1 基本框架
  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;
  6. Node* _node;
  7. };

1)迭代器类模板为什么要三个参数?

若只有普通迭代器的话,一个class T参数就够了,但因为有const迭代器原因,需要加两个参数,两个参数明Ref(reference:引用)和Ptr(pointer:指针),名字怎么起都行,但这种有意义的名字很推荐的,即这两个参数一个让你传引用,一个让你传指针,具体等到下文讲到const迭代器再说

2)迭代器类到底是什么?

迭代器类就是一个结点的指针变量_node,但是因为我们要运算符重载等一些列操作,不得不把迭代器写成类,完成哪些操作,list的迭代器才能正确的++到下一个位置,解引用访问节点的值。

3)结点指针和迭代器的区别?

当他们都指向同一个结点,那么在物理内存中他们都存的是这个结点地址,物理上是一样的,但是他们的类型不一样,他们的意义就不一样。比如*cur是一个指针的解引用,取到的值是结点;*it是去调用迭代器中“ * ”的运算符重载operator*,返回值是结点中存的值。

类型决定了对空间的解释权

3.2 构造函数
  1. _list_iterator(Node* node)
  2. :_node(node)
  3. {}
3.3 operator*
  1. Ref operator*()
  2. {
  3. return _node->_data;
  4. }

1)返回值为什么是Ref?

Ref是模板参数,因为迭代器类的模板参数Ref传入的要么是T&,要么是const T&,就是为了const迭代器和普通迭代器的同时实现,底层就是这么实现的,意义就是一个只读,一个可读可写

注:比如之前讲的vector的迭代器,*it(假设it是迭代器变量)就是拿到对应的值,那么list的迭代器也要同理,解引用迭代器就是为了访问对应位置的值,那么list只要通过迭代器返回对应结点的值就好了。

3.4 operator->
  1. Ptr operator->()
  2. {
  3. return &_node->_data;
  4. }

1)为什么需要operator->?

本质因为自定义类型需要,那需从list存的类型是个自定义类型说起,以Date类说明。若list存了个自定义类型的Date类,就无法打印,因为我们并没有重载Date类的operator<<,若是内置类型的话才可以正常输出,那写一个operator<<重载吗?不,因为你无法确定要用那些类,也不能每个类都写operator<<,怎么半?我们访问Date类本质是想访问它内置类型(int)的_year,_month,_day吧,那我们不妨写个专属于自定义类型的operator-> (因为内置类型只需要*it就可以直接输出了,但自定义类型不可以直接输出),利用operator->直接访问类的成员变量,而内置类型可以直接输出

故从根源上解决问题:在迭代器中实现个operator->:

(Ptr是迭代器的模板参数,我们用来作为T*或const T*的)

3.5 operator前置++和后置++和--
  1. Self& operator++()
  2. {
  3. _node = _node->_next;
  4. return *this;
  5. }
  6. Self operator++(int)
  7. {
  8. Self tmp(*this);
  9. _node = _node->_next;
  10. return tmp;
  11. }
  12. Self& operator--()
  13. {
  14. _node = _node->_prev;
  15. return *this;
  16. }
  17. Self operator--(int)
  18. {
  19. Self tmp(*this);
  20. _node = _node->_prev;
  21. return tmp;
  22. }

1)迭代器++对于list是什么意思?

迭代器++的意思就是想让其指向下一个节点,--正好相反,为了区分前置和后置++(--),我们会用函数重载,也就是多一个“没用的”参数:int,这个参数没什么用,只是为了区分++和--

3.6 operator==与operator!=
  1. bool operator!=(const Self& it)
  2. {
  3. return _node != it._node;
  4. }
  5. bool operator==(const Self& it)
  6. {
  7. return _node == it._node;
  8. }

两个迭代器怎么比较的?

迭代器中就一个成员变量_node,结点指针,只要比较当前结点指针是否相同即可,这个操作在判断迭代器是否走到头有用。

问题:迭代器的拷贝构造、赋值和析构函数需要我们实现吗?

:不需要

因为迭代器存的就是一个节点指针,而节点是属于链表list的,那么它的释放应有list来实现,那么迭代器的析构也无需我们自己写了。总而言之,迭代器并不涉及从堆上开空间,使用系统提供的浅拷贝就可以了。

4、list类的实现

在节点类和迭代器类都实现的前提下,就可实现list主要功能:增删等操作的实现

4.1 基本框架
  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. private:
  9. Node* _head;
  10. };

const_iterator(const迭代器的介绍)

我们知道const_iterator在begin()和end()中的返回值是需要用到的,其主要作用就是当迭代器只读时使用,因为普通迭代器和const迭代器的实现区别仅仅在于成员函数的返回值不同,难道重写一遍吗?不用,我们模板参数多两个就好了,一个是引用class Ref(T&或const T&),一个是指针class Ptr(T*或const T*),当Ref时const T&就是const迭代器的调用,这就利用模板实现了两个迭代器的同时实现。

4.2 构造函数
  1. list()
  2. {
  3. _head = new Node;
  4. _Head->_next = _head;
  5. _head->_prev = _head;
  6. }

我们开辟一个头结点,然后使其处于一个对应的初始状态即可。

4.3 begin()和end()
  1. iterator begin()
  2. { //第一个位置应该是头节点的下一个节点
  3. return iterator(head->_next);//用匿名对象构造iterator类型的
  4. }
  5. iterator end()
  6. {
  7. return iterator(_head);
  8. }
  9. const_iterator begin()const
  10. {
  11. return const_iterator(head->next);
  12. }
  13. const_iterator end()const
  14. {
  15. return const_iterator(_head);
  16. }

1)关于匿名构造的理解

比如iterator(_head->next);iterator是一个类模板类型(它被typedef过),那不应该实例化一个对象再构造吗?这里没有用是因为这里是匿名对象的构造,这里这么用比较方便。

4.4拷贝构造
  1. list(const list<T>& It)
  2. {
  3. _head = new Node;
  4. _head->_next = _head;
  5. _head->_prev = _head;
  6. for (auto e : lt)
  7. {
  8. push_back(e);
  9. }
  10. }

解释:list的拷贝构造跟vector不同,它的拷贝是拷贝一个个节点(因为不连续的物理空间),那么我们可以用迭代器拿到list的一个个节点【上面是传统写法】

现代写法:

  1. template<class Inputerator>
  2. list(Inputerator first, Inputerator last)
  3. {
  4. _pHead = new Node();
  5. _pHead->next = _pHead;
  6. _pHead->prev = _pHead;
  7. while (first != last)
  8. {
  9. push_back(*first);
  10. first++;
  11. }
  12. }
  13. //拷贝构造(现代写法)
  14. list(const list<T>& L)
  15. {
  16. _pHead = new Node();
  17. _pHead->next = _pHead;
  18. _pHead->prev = _pHead;
  19. list<T> tmp(L.begin(), L.end());
  20. swap(_pHead, tmp._pHead);
  21. }

1)为什么有的拷贝构造需要初始化,operator=不需要?

以string 的模板实现为例

4.5 clear
  1. void clear()
  2. {
  3. iterator it = begin();
  4. while (it != end())
  5. {
  6. it = erase(it);
  7. }
  8. }

1) it=erase(it)什么意思

防止迭代器失效,因为erase返回的是被删除位置的下一个位置,比如删除pos位置的,pos位置被删除后,这个位置的迭代器就失效了,那就无法通过++找到后面的位置了,所以我们通过erase返回值来更新一下迭代器位置,即更新到被删除位置的下一个位置

4.6 operator=
  1. list<T>& operator=(list<T> lt)
  2. {
  3. swap(_head, lt._head);
  4. return *this;
  5. }
4.7 析构函数
  1. ~list()
  2. {
  3. clear();
  4. delete _head;
  5. _head = nullptr;
  6. }
4.8 insert
  1. iterator insert(iterator pos, const T& x)
  2. {
  3. Node* newnode = new Node(x);
  4. //找到pos位置的指针和pos之前的指针
  5. Node* cur = pos._node;
  6. Node* prev = cur->_prev;
  7. //prev newnode cur
  8. prev->_next = newnode;
  9. newnode->_prev = prev;
  10. newnode->_next = cur;
  11. cur->_prev = newnode;
  12. //返回新插入元素的迭代器位置
  13. return iterator(newnode);
  14. }

1)返回参数为什么是iterator?

本质是为了防止迭代器失效问题

注:insert指的是插入到指定位置的前面

4.9 push_back和push_front
  1. void push_back(const T& x)
  2. {
  3. Node* tail = head->prev;
  4. Node* newnode = new Node(x);
  5. tail->_next = newnode;
  6. newnode->_prev = tail;
  7. newnode->_next = _head;
  8. _head->_prev = newnode;
  9. }
4.10 erase
  1. iterator erase(iterator pos)
  2. {
  3. assert(pos != end())
  4. {
  5. Node* cur = pos._node;
  6. Node* prev = cur->_prev;
  7. NOde* next = cur->_next;
  8. prev->_next = next;
  9. next->_prev = prev;
  10. delete cur;
  11. return iterator(next);
  12. }
  13. }

返回值问题

erase的返回值,返回的是被删除位置的下一个位置,库里面这么规定,本质是因为删除元素一定会导致此位置的迭代器失效,故返回被删除位置的下一个位置可以更新迭代器

4.11pop_back和pop_front
  1. void pop_back()
  2. {
  3. erase(--end());
  4. }
  5. void pop_front()
  6. {
  7. erase(begin());
  8. }

三、源代码

  1. #pragma once
  2. #include <iostream>
  3. using namespace std;
  4. namespace lf
  5. {
  6. template <class T>
  7. struct listNode
  8. {
  9. T Data;
  10. listNode<T>* _next;
  11. listNode<T>* _prev;
  12. listNode(const T& val = T())
  13. :Data(val)
  14. ,_next(nullptr)
  15. ,_prev(nullptr)
  16. {}
  17. };
  18. template <class T,class Ref,class Ptr>
  19. struct list_Iterator
  20. {
  21. typedef listNode<T> node;
  22. typedef list_Iterator<T, Ref, Ptr> Self;
  23. node* _node;
  24. list_Iterator(node* node)
  25. :_node(node)
  26. {}
  27. Ref operator*()
  28. {
  29. return _node->Data;
  30. }
  31. Ptr operator->()
  32. {
  33. return &_node->Data;
  34. }
  35. Self operator++()
  36. {
  37. _node = _node->_next;
  38. return *this;
  39. }
  40. Self operator++(int)
  41. {
  42. Self tmp(*this);
  43. _node = _node->_next;
  44. return tmp;
  45. }
  46. Self operator--()
  47. {
  48. _node = _node->_prev;
  49. return *this;
  50. }
  51. Self operator--(int)
  52. {
  53. Self tmp(*this);
  54. _node = _node->_prev;
  55. return tmp;
  56. }
  57. bool operator!=(const Self& it)const
  58. {
  59. return _node != it._node;
  60. }
  61. bool operator==(const Self& it)const
  62. {
  63. return _node == it._node;
  64. }
  65. };
  66. template <class T>
  67. class list
  68. {
  69. typedef listNode<T> node;
  70. public:
  71. typedef list_Iterator<T, T&, T*> iterator;
  72. typedef list_Iterator<T, const T&, const T*> const_iterator;
  73. iterator begin()
  74. {
  75. return iterator(_head->_next);
  76. }
  77. iterator end()
  78. {
  79. return iterator(_head);
  80. }
  81. const_iterator begin() const
  82. {
  83. return const_iterator(_head->_next);
  84. }
  85. const_iterator end() const
  86. {
  87. return const_iterator(_head);
  88. }
  89. void empty_init()
  90. {
  91. _head = new node;
  92. _head->_next = _head;
  93. _head->_prev = _head;
  94. }
  95. list()
  96. {
  97. empty_init();
  98. }
  99. list(const list<T>& lt)
  100. {
  101. empty_init();
  102. for (auto e : lt)
  103. {
  104. push_back(e);
  105. }
  106. }
  107. ~list()
  108. {
  109. clear();
  110. delete _head;
  111. _head = nullptr;
  112. }
  113. list<T>& operator=(list<T> lt)
  114. {
  115. swap(_head, lt._head);
  116. return *this;
  117. }
  118. void clear()
  119. {
  120. iterator it = begin();
  121. while (it != end())
  122. {
  123. it = erase(it);
  124. }
  125. }
  126. // prev head next 2 head 1
  127. iterator erase(iterator pos)
  128. {
  129. node* cur = pos._node;
  130. node* prev = cur->_prev;
  131. node* next = cur->_next;
  132. prev->_next = next;
  133. next->_prev = prev;
  134. delete cur;
  135. cur = nullptr;
  136. return iterator(next);
  137. }
  138. // 1 2 newnode head
  139. void insert(iterator pos,const T& val)
  140. {
  141. node* newnode = new node(val);
  142. node* cur = pos._node;
  143. node* prev = cur->_prev;
  144. newnode->_next = cur;
  145. cur->_prev = newnode;
  146. prev->_next = newnode;
  147. newnode->_prev = prev;
  148. }
  149. void push_back(T val)
  150. {
  151. insert(end(), val);
  152. }
  153. void pop_back()
  154. {
  155. erase(--end());
  156. }
  157. private:
  158. node* _head;
  159. };
  160. void test_list1()
  161. {
  162. list<int> lt1;
  163. list<int>::iterator it = lt1.begin();
  164. lt1.insert(it, 1);
  165. lt1.push_back(2);
  166. lt1.push_back(3);
  167. //lt1.pop_back();
  168. list<int>::iterator it1 = lt1.end();
  169. //lt1.erase(--it1);
  170. /*for (auto _begin = lt1.begin(), _end = lt1.end(); _begin != _end; ++_begin)
  171. {
  172. cout << *_begin << " ";
  173. }*/
  174. lt1.clear();
  175. for (auto e : lt1)
  176. {
  177. cout << e << " ";
  178. }
  179. //cout << *it << endl;
  180. }
  181. void test_list2()
  182. {
  183. list<int> lt;
  184. lt.push_back(1);
  185. lt.push_back(2);
  186. lt.push_back(3);
  187. list<int> lt1(lt);
  188. lt.push_back(4);
  189. lt.push_back(5);
  190. lt.push_back(6);
  191. lt.push_back(7);
  192. lt1 = lt;
  193. lt.operator=(lt1);
  194. for (auto e : lt)
  195. {
  196. cout << e << " ";
  197. }
  198. cout << endl;
  199. for (auto e : lt1)
  200. {
  201. cout << e << " ";
  202. }
  203. cout << endl;
  204. }
  205. };

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/820948
推荐阅读
相关标签
  

闽ICP备14008679号