• C++ list详解以及模拟实现


    目录

    1.list的使用

    1.1list的定义

    1.2list的使用

    1.3list iterator使用

    1.4list capacity

    1.5list element access

    1.6list增删查改

    2.list迭代器失效问题

     3.list的模拟实现


    1.list的使用

    1.1list的定义

    1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。

    2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。

    3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。

    4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好。

    5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)

    1.2list的使用

    构造函数结构说明
    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.3list iterator使用

    函数声明接口声明
    begin+end返回第一个元素的迭代器+返回最后一个元素的下一个位置的迭代器
    rbegin+rend返回第一个元素reverse_iterator,即end的位置+返回最后一个元素下一个位置的reverse_iterator,即begin的位置

    需要注意的是:

    1.begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动

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

    1.4list capacity

    函数声明接口说明
    empty检查list是否为空,为空返回true,不为空返回false
    size返回list中有效节点的个数

    1.5list element access

    函数声明接口说明
    front返回list的第一个节点中值的引用
    back返回list的最后一个节点中值的引用

    1.6list增删查改

    函数声明接口声明
    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中的有效元素

    2.list迭代器失效问题

    与string和vector相似,list的迭代器也会出现失效的问题

    1. #include
    2. #include
    3. using namespace std;
    4. int main()
    5. {
    6. int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
    7. list<int> l(array, array + sizeof(array) / sizeof(array[0]));
    8. auto it = l.begin();
    9. while (it != l.end())
    10. {
    11. l.erase(it);
    12. ++it;
    13. }
    14. }

    下面是正确的使用方式,每次都更正一下迭代器it指向的结点位置。

    1. void TestListIterator()
    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. l.erase(it++); // it = l.erase(it);
    9. }
    10. }

     3.list的模拟实现

    1. #pragma once
    2. #include
    3. namespace mylist
    4. {
    5. template<class T>
    6. struct ListNode
    7. {
    8. ListNode* _next;
    9. ListNode* _prev;
    10. T _data;
    11. ListNode(const T& x = T())
    12. :_next(nullptr)
    13. ,_prev(nullptr)
    14. ,_data(x)
    15. {}
    16. };
    17. template<class T>
    18. struct __list_iterator
    19. {
    20. typedef ListNode Node;
    21. typedef __list_iterator self;
    22. Node* _node;
    23. __list_iterator(Node* x)
    24. :_node(x)
    25. {}
    26. // ++it
    27. self& operator++()
    28. {
    29. _node = _node->_next;
    30. return *this;
    31. }
    32. // it++
    33. self operator++(int)
    34. {
    35. //__list_iterator tmp(*this);
    36. self tmp(*this);
    37. _node = _node->_next;
    38. return tmp;
    39. }
    40. self& operator--()
    41. {
    42. _node = _node->_prev;
    43. return *this;
    44. }
    45. self operator--(int);
    46. T& operator*()
    47. {
    48. return _node->_data;
    49. }
    50. bool operator!=(const self& s)
    51. {
    52. return _node != s._node;
    53. }
    54. bool operator==(const self& s);
    55. };
    56. template<class T>
    57. class list
    58. {
    59. typedef ListNode Node;
    60. public:
    61. typedef __list_iterator iterator;
    62. iterator begin()
    63. {
    64. //return iterator(_head->_next);
    65. return _head->_next;
    66. }
    67. iterator end()
    68. {
    69. return _head;
    70. }
    71. void empty_init()
    72. {
    73. _head = new Node;
    74. _head->_next = _head;
    75. _head->_prev = _head;
    76. }
    77. list()
    78. {
    79. empty_init();
    80. }
    81. void clear()
    82. {
    83. iterator it = begin();
    84. while (it != end())
    85. {
    86. it = erase(it);
    87. }
    88. }
    89. ~list()
    90. {
    91. clear();
    92. delete _head;
    93. _head = nullptr;
    94. }
    95. //list(const list& lt)
    96. list(list& lt)
    97. {
    98. empty_init();
    99. for (const auto& e : lt)
    100. {
    101. push_back(e);
    102. }
    103. }
    104. // lt1 = lt2;
    105. // list& operator=(const list& lt)
    106. /*list& operator=(list& lt)
    107. {
    108. if (this != <)
    109. {
    110. clear();
    111. for (const auto& e : lt)
    112. {
    113. push_back(e);
    114. }
    115. }
    116. return *this;
    117. }*/
    118. void swap(list& tmp)
    119. {
    120. std::swap(_head, tmp._head);
    121. }
    122. //list& operator=(list lt)
    123. list& operator=(list lt)
    124. {
    125. swap(lt);
    126. return *this;
    127. }
    128. void push_back(const T& x)
    129. {
    130. /*Node* newnode = new Node(x);
    131. Node* tail = _head->_prev;
    132. tail->_next = newnode;
    133. newnode->_prev = tail;
    134. newnode->_next = _head;
    135. _head->_prev = newnode;*/
    136. insert(end(), x);
    137. }
    138. void push_front(const T& x)
    139. {
    140. insert(begin(), x);
    141. }
    142. void pop_back()
    143. {
    144. erase(--end());
    145. }
    146. void pop_front()
    147. {
    148. erase(begin());
    149. }
    150. // vector insert会导致迭代器失效
    151. // list会不会?不会
    152. iterator insert(iterator pos, const T& x)
    153. {
    154. Node* cur = pos._node;
    155. Node* prev = cur->_prev;
    156. Node* newnode = new Node(x);
    157. // prev newnode cur
    158. prev->_next = newnode;
    159. newnode->_prev = prev;
    160. newnode->_next = cur;
    161. cur->_prev = newnode;
    162. //return iterator(newnode);
    163. return newnode;
    164. }
    165. iterator erase(iterator pos)
    166. {
    167. assert(pos != end());
    168. Node* cur = pos._node;
    169. Node* prev = cur->_prev;
    170. Node* next = cur->_next;
    171. prev->_next = next;
    172. next->_prev = prev;
    173. delete cur;
    174. return next;
    175. }
    176. private:
    177. Node* _head;
    178. };
    179. void test_list1()
    180. {
    181. list<int> lt;
    182. lt.push_back(1);
    183. lt.push_back(2);
    184. lt.push_back(3);
    185. lt.push_back(4);
    186. list<int>::iterator it = lt.begin();
    187. while (it != lt.end())
    188. {
    189. //*it += 10;
    190. cout << *it << " ";
    191. ++it;
    192. }
    193. cout << endl;
    194. for (auto e : lt)
    195. {
    196. cout << e << " ";
    197. }
    198. cout << endl;
    199. }
    200. void test_list2()
    201. {
    202. list<int> lt;
    203. lt.push_back(1);
    204. lt.push_back(2);
    205. lt.push_back(3);
    206. lt.push_back(4);
    207. for (auto e : lt)
    208. {
    209. cout << e << " ";
    210. }
    211. cout << endl;
    212. lt.push_back(5);
    213. lt.push_front(0);
    214. for (auto e : lt)
    215. {
    216. cout << e << " ";
    217. }
    218. cout << endl;
    219. lt.pop_back();
    220. lt.pop_front();
    221. for (auto e : lt)
    222. {
    223. cout << e << " ";
    224. }
    225. cout << endl;
    226. lt.clear();
    227. for (auto e : lt)
    228. {
    229. cout << e << " ";
    230. }
    231. cout << endl;
    232. lt.push_back(10);
    233. lt.push_back(20);
    234. for (auto e : lt)
    235. {
    236. cout << e << " ";
    237. }
    238. cout << endl;
    239. }
    240. void test_list3()
    241. {
    242. list<int> lt;
    243. lt.push_back(1);
    244. lt.push_back(2);
    245. lt.push_back(3);
    246. lt.push_back(4);
    247. for (auto e : lt)
    248. {
    249. cout << e << " ";
    250. }
    251. cout << endl;
    252. list<int> copy(lt);
    253. for (auto e : copy)
    254. {
    255. cout << e << " ";
    256. }
    257. cout << endl;
    258. list<int> lt1;
    259. lt1.push_back(10);
    260. lt1.push_back(20);
    261. lt1.push_back(30);
    262. lt1.push_back(40);
    263. lt = lt1;
    264. for (auto e : copy)
    265. {
    266. cout << e << " ";
    267. }
    268. cout << endl;
    269. }
    270. }

     4.list和vector的对比

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

  • 相关阅读:
    Bluespec SytemVerilog 握手协议接口转换
    2024年11月1日Github流行趋势
    ssh无法登录远程服务器
    程序员如何选择职业赛道?
    RabbitMQ 消息队列学习 (四)
    云原生之深入解析Jenkins多分支管道
    插入一百万数据的最优解分析和耗时
    CAD Exchanger SDK 3.13 Crack
    nginx安装带stream 并处理svn跳转
    mysql忘记密码无法登录
  • 原文地址:https://blog.csdn.net/qq_74732628/article/details/136307834