• C++:stl:list的常用接口及其模拟实现


    本文主要介绍c++:stl中list常用接口的功能及使用方法,比较list与vector的区别,并对list的常用接口进行模拟实现。

    目录

    一、list的介绍和使用

    1.list介绍

    2.list使用

    1.list的构造

    2.list iterator的使用

    3.list 容量相关

    4.list元素访问

    5.list修改

    6.list的迭代器失效

    二、list的模拟实现

    1.list的节点类

    2.list的迭代器类

    1.正向迭代器

    2.反向迭代器

    3.list类

    三、list和vector的对比


    一、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来说这可能是一个重要的因素)

    2.list使用

    list中的接口比较多,此处类似,只需要掌握如何正确的使用,然后再去深入研究背后的原理,已达到可扩展的能力。以下为list中一些常见的重要接口

    1.list的构造

    构造函数接口说明
    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. void test1()
    2. {
    3. list<int> lt1; // 构造空的lt1
    4. list<int> lt2(4, 100); // lt2中放4个值为100的元素
    5. list<int> lt3(lt2.begin(), lt2.end()); //用lt2的[begin(), end())左闭右开的区间构造lt3
    6. list<int> lt4(lt3); // 用lt3拷贝构造lt4
    7. // 以数组为迭代器区间构造l5
    8. int array[] = { 10,20,30,40 };
    9. list<int> lt5(array, array + sizeof(array) / sizeof(int));
    10. // 列表格式初始化C++11
    11. list<int> lt6{ 1,2,3,4,5 };
    12. // 用迭代器方式打印l5中的元素
    13. list<int>::iterator it = lt5.begin();
    14. while (it != lt5.end())
    15. {
    16. cout << *it << " ";
    17. ++it;
    18. }
    19. cout << endl;
    20. //10 20 30 40
    21. // C++11范围for的方式遍历
    22. for (auto& e : lt5)
    23. cout << e << " ";
    24. cout << endl;
    25. //10 20 30 40
    26. }

    2.list iterator的使用

    此处可以将迭代器理解成一个指针,该指针指向list中的某个节点。

    函数声明接口说明
    begin + end返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器
    rbegin + rend返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的 reverse_iterator,即begin位置
    1. void test2()
    2. {
    3. int array[] = { 1, 2, 3, 4, 5};
    4. list<int> lt(array, array + sizeof(array) / sizeof(array[0]));
    5. // 使用正向迭代器正向list中的元素
    6. // list::iterator it = l.begin(); // C++98中语法
    7. auto it = lt.begin(); // C++11之后推荐写法
    8. while (it != lt.end())
    9. {
    10. cout << *it << " ";
    11. ++it;
    12. }
    13. cout << endl;//1 2 3 4 5
    14. // 使用反向迭代器逆向打印list中的元素
    15. // list::reverse_iterator rit = l.rbegin();
    16. auto rit = lt.rbegin();
    17. while (rit != lt.rend())
    18. {
    19. cout << *rit << " ";
    20. ++rit;
    21. }
    22. cout << endl;//1 2 3 4 5
    23. }

    注意:

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

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

    3.list 容量相关

    函数声明接口说明
    empty检测list是否为空,是返回true,否则返回false
    size返回list中有效节点的个数
    1. void test3()
    2. {
    3. int array[] = { 1, 2, 3, 4, 5 };
    4. list<int> lt(array, array + sizeof(array) / sizeof(array[0]));
    5. if (!lt.empty())
    6. cout << "false" << endl;//false
    7. cout<size() << endl;//5
    8. }

    4.list元素访问

    函数声明接口说明
    front返回list的第一个节点中值的引用
    back返回list的最后一个节点中值的引用
    1. void test4()
    2. {
    3. int array[] = { 1, 2, 3, 4, 5 };
    4. list<int> lt(array, array + sizeof(array) / sizeof(array[0]));
    5. cout << lt.front() << endl;//1
    6. cout << lt.back() << endl;//5
    7. }

    5.list修改

    函数声明接口说明
    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. void PrintList(const list<int>& lt)
    2. {
    3. // 注意这里调用的是list的 begin() const,返回list的const_iterator对象
    4. for (list<int>::const_iterator it = lt.begin(); it != lt.end(); ++it)
    5. {
    6. cout << *it << " ";
    7. // *it = 10; 编译不通过
    8. }
    9. cout << endl;
    10. }
    11. // push_back/pop_back/push_front/pop_front
    12. void test5()
    13. {
    14. int array[] = { 1, 2, 3 };
    15. list<int> lt(array, array + sizeof(array) / sizeof(array[0]));
    16. // 在list的尾部插入4,头部插入0
    17. lt.push_back(4);
    18. lt.push_front(0);
    19. PrintList(lt);//0 1 2 3 4
    20. // 删除list尾部节点和头部节点
    21. lt.pop_back();
    22. lt.pop_front();
    23. PrintList(lt);//1 2 3
    24. }

    1. // insert /erase
    2. void test6()
    3. {
    4. int array1[] = { 1, 2, 3 };
    5. list<int> lt(array1, array1 + sizeof(array1) / sizeof(array1[0]));
    6. // 获取链表中第二个节点
    7. auto pos = ++lt.begin();
    8. cout << *pos << endl;//2
    9. // 在pos前插入值为4的元素
    10. lt.insert(pos, 4);
    11. PrintList(lt);//1 4 2 3
    12. // 在pos前插入5个值为5的元素
    13. lt.insert(pos, 5, 5);
    14. PrintList(lt);//1 4 5 5 5 5 5 2 3
    15. // 在pos前插入[v.begin(), v.end)区间中的元素
    16. vector<int> v{ 7, 8, 9 };
    17. lt.insert(pos, v.begin(), v.end());
    18. PrintList(lt);//1 4 5 5 5 5 5 7 8 9 2 3
    19. // 删除pos位置上的元素
    20. lt.erase(pos);
    21. PrintList(lt);//1 4 5 5 5 5 5 7 8 9 3
    22. // 删除list中[begin, end)区间中的元素,即删除list中的所有元素
    23. lt.erase(lt.begin(), lt.end());
    24. PrintList(lt);//
    25. }

    1. // resize/swap/clear
    2. void test7()
    3. {
    4. // 用数组来构造list
    5. int array1[] = { 1, 2, 3 };
    6. list<int> lt1(array1, array1 + sizeof(array1) / sizeof(array1[0]));
    7. PrintList(lt1);//1 2 3
    8. // 交换l1和l2中的元素
    9. list<int> lt2;
    10. lt1.swap(lt2);
    11. PrintList(lt1);//
    12. PrintList(lt2);//1 2 3
    13. // 将l2中的元素清空
    14. lt2.clear();
    15. cout << lt2.size() << endl;//0
    16. }

    list中还有一些操作,需要用到时可参阅list的文档说明。

    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> lt(array, array + sizeof(array) / sizeof(array[0]));
    5. auto it = lt.begin();
    6. while (it != lt.end())
    7. {
    8. // erase()函数执行后,it所指向的节点已被删除,
    9. // 因此it无效,在下一次使用it时,必须先给其赋值
    10. lt.erase(it);
    11. ++it;
    12. }
    13. }
    14. // 改正
    15. void TestListIterator2()
    16. {
    17. int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
    18. list<int> lt(array, array + sizeof(array) / sizeof(array[0]));
    19. auto it = lt.begin();
    20. while (it != lt.end())
    21. {
    22. lt.erase(it++); // it = l.erase(it);
    23. }
    24. }

    二、list的模拟实现

    1.list的节点类

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

    2.list的迭代器类

    1.正向迭代器

    迭代器有两种实现方式,具体应根据容器底层数据结构实现:

    1. 原生态指针,比如:vector

    2. 将原生态指针进行封装,因迭代器使用形式与指针完全相同,因此在自定义的类中必须实现以下方法:

            1. 指针可以解引用,迭代器的类中必须重载operator*()

            2. 指针可以通过->访问其所指空间成员,迭代器类中必须重载oprator->()

            3. 指针可以++向后移动,迭代器类中必须重载operator++()与operator++(int)

            至于operator--()/operator--(int)释放需要重载,根据具体的结构来抉择,双向链表可以向前移动,所以需要重载,如果是forward_list就不需要重载--

            4. 迭代器需要进行是否相等的比较,因此还需要重载operator==()与operator!=()

    1. template<class T, class Ref, class Ptr>
    2. //Ref和Ptr分布表示 T& 和 T* 或者const T& 和const T*
    3. class ListIterator
    4. {
    5. typedef ListNode Node;
    6. typedef ListIterator Self;
    7. // Ref 和 Ptr 类型需要重定义下,实现反向迭代器时需要用到
    8. public:
    9. typedef Ref Ref;
    10. typedef Ptr Ptr;
    11. public:
    12. Node* _node;
    13. // 构造
    14. ListIterator(Node* node = nullptr)
    15. : _node(node)
    16. {}
    17. // 具有指针类似行为
    18. Ref operator*()
    19. {
    20. return _node->_val;
    21. }
    22. Ptr operator->()
    23. {
    24. return &(operator*());
    25. }
    26. // 迭代器移动
    27. Self& operator++()
    28. {
    29. _node = _node->_next;
    30. return *this;
    31. }
    32. Self operator++(int)
    33. {
    34. Self temp(*this);
    35. _node = _node->_next;
    36. return temp;
    37. }
    38. Self& operator--()
    39. {
    40. _node = _node->_prev;
    41. return *this;
    42. }
    43. Self operator--(int)
    44. {
    45. Self temp(*this);
    46. _node = _node->_prev;
    47. return temp;
    48. }
    49. // 迭代器比较
    50. bool operator!=(const Self& l)const
    51. {
    52. return _node != l._node;
    53. }
    54. bool operator==(const Self& l)const
    55. {
    56. return _node != l._node;
    57. }
    58. };

    2.反向迭代器

    通过前面例子知道,反向迭代器的++就是正向迭代器的--,反向迭代器的--就是正向迭代器的++,因此反向迭代器的实现可以借助正向迭代器,即:反向迭代器内部可以包含一个正向迭代器,对正向迭代器的接口进行包装即可。

    1. template<class Iterator>
    2. class ReverseListIterator
    3. {
    4. // 注意:此处typename的作用是明确告诉编译器,Ref是Iterator类中的一个类型,而不是静态成员变量
    5. // 否则编译器编译时就不知道Ref是Iterator中的类型还是静态成员变量
    6. // 因为静态成员变量也是按照类名::静态成员变量名的方式访问的
    7. public:
    8. typedef typename Iterator::Ref Ref;
    9. typedef typename Iterator::Ptr Ptr;
    10. typedef ReverseListIterator Self;
    11. public:
    12. Iterator _it;
    13. // 构造
    14. ReverseListIterator(Iterator it)
    15. : _it(it)
    16. {}
    17. // 具有指针类似行为
    18. Ref operator*()
    19. {
    20. Iterator temp(_it);
    21. --temp;
    22. return *temp;
    23. }
    24. Ptr operator->()
    25. {
    26. return &(operator*());
    27. }
    28. // 迭代器移动
    29. Self& operator++()
    30. {
    31. --_it;
    32. return *this;
    33. }
    34. Self operator++(int)
    35. {
    36. Self temp(*this);
    37. --_it;
    38. return temp;
    39. }
    40. Self& operator--()
    41. {
    42. ++_it;
    43. return *this;
    44. }
    45. Self operator--(int)
    46. {
    47. Self temp(*this);
    48. ++_it;
    49. return temp;
    50. }
    51. // 迭代器比较
    52. bool operator!=(const Self& l)const
    53. {
    54. return _it != l._it;
    55. }
    56. bool operator==(const Self& l)const
    57. {
    58. return _it != l._it;
    59. }
    60. };

    3.list类

    1. template<class T>
    2. class list
    3. {
    4. typedef ListNode Node;
    5. public:
    6. // 正向迭代器
    7. typedef ListIterator iterator;
    8. typedef ListIteratorconst T&, const T&> const_iterator;
    9. // 反向迭代器
    10. typedef ReverseListIterator reverse_iterator;
    11. typedef ReverseListIterator const_reverse_iterator;
    12. public:
    13. // List的构造
    14. list()
    15. {
    16. CreateHead();
    17. }
    18. list(int n, const T& value = T())
    19. {
    20. CreateHead();
    21. for (int i = 0; i < n; ++i)
    22. push_back(value);
    23. }
    24. template <class Iterator>
    25. list(Iterator first, Iterator last)
    26. {
    27. CreateHead();
    28. while (first != last)
    29. {
    30. push_back(*first);
    31. ++first;
    32. }
    33. }
    34. list(const list& l)
    35. {
    36. CreateHead();
    37. // 用l中的元素构造临时的temp,然后与当前对象交换
    38. list temp(l.begin(), l.end());
    39. this->swap(temp);
    40. }
    41. list& operator=(list l)
    42. {
    43. this->swap(l);
    44. return *this;
    45. }
    46. ~list()
    47. {
    48. clear();
    49. delete _head;
    50. _head = nullptr;
    51. }
    52. // List的迭代器
    53. iterator begin()
    54. {
    55. return iterator(_head->_next);
    56. }
    57. iterator end()
    58. {
    59. return iterator(_head);
    60. }
    61. const_iterator begin()const
    62. {
    63. return const_iterator(_head->_next);
    64. }
    65. const_iterator end()const
    66. {
    67. return const_iterator(_head);
    68. }
    69. reverse_iterator rbegin()
    70. {
    71. return reverse_iterator(end());
    72. }
    73. reverse_iterator rend()
    74. {
    75. return reverse_iterator(begin());
    76. }
    77. const_reverse_iterator rbegin()const
    78. {
    79. return const_reverse_iterator(end());
    80. }
    81. const_reverse_iterator rend()const
    82. {
    83. return const_reverse_iterator(begin());
    84. }
    85. // List的容量相关
    86. size_t size()const
    87. {
    88. Node* cur = _head->_next;
    89. size_t count = 0;
    90. while (cur != _head)
    91. {
    92. count++;
    93. cur = cur->_next;
    94. }
    95. return count;
    96. }
    97. bool empty()const
    98. {
    99. return _head->_next == _head;
    100. }
    101. void resize(size_t newsize, const T& data = T())
    102. {
    103. size_t oldsize = size();
    104. if (newsize <= oldsize)
    105. {
    106. // 有效元素个数减少到newsize
    107. while (newsize < oldsize)
    108. {
    109. pop_back();
    110. oldsize--;
    111. }
    112. }
    113. else
    114. {
    115. while (oldsize < newsize)
    116. {
    117. push_back(data);
    118. oldsize++;
    119. }
    120. }
    121. }
    122. // List的元素访问操作
    123. // 注意:List不支持operator[]
    124. T& front()
    125. {
    126. return _head->_next->_val;
    127. }
    128. const T& front()const
    129. {
    130. return _head->_next->_val;
    131. }
    132. T& back()
    133. {
    134. return _head->_prev->_val;
    135. }
    136. const T& back()const
    137. {
    138. return _head->_prev->_val;
    139. }
    140. // List的插入和删除
    141. void push_back(const T& val)
    142. {
    143. insert(end(), val);
    144. }
    145. void pop_back()
    146. {
    147. erase(--end());
    148. }
    149. void push_front(const T& val)
    150. {
    151. insert(begin(), val);
    152. }
    153. void pop_front()
    154. {
    155. erase(begin());
    156. }
    157. // 在pos位置前插入值为val的节点
    158. iterator insert(iterator pos, const T& val)
    159. {
    160. Node* pNewNode = new Node(val);
    161. Node* pCur = pos._node;
    162. // 先将新节点插入
    163. pNewNode->_prev = pCur->_prev;
    164. pNewNode->_next = pCur;
    165. pNewNode->_prev->_next = pNewNode;
    166. pCur->_prev = pNewNode;
    167. return iterator(pNewNode);
    168. }
    169. // 删除pos位置的节点,返回该节点的下一个位置
    170. iterator erase(iterator pos)
    171. {
    172. // 找到待删除的节点
    173. Node* pDel = pos._node;
    174. Node* pRet = pDel->_next;
    175. // 将该节点从链表中拆下来并删除
    176. pDel->_prev->_next = pDel->_next;
    177. pDel->_next->_prev = pDel->_prev;
    178. delete pDel;
    179. return iterator(pRet);
    180. }
    181. void clear()
    182. {
    183. Node* cur = _head->_next;
    184. // 采用头删除删除
    185. while (cur != _head)
    186. {
    187. _head->_next = cur->_next;
    188. delete cur;
    189. cur = _head->_next;
    190. }
    191. _head->_next = _head->_prev = _head;
    192. }
    193. void swap(bite::list& l)
    194. {
    195. std::swap(_head, l._head);
    196. }
    197. private:
    198. void CreateHead()
    199. {
    200. _head = new Node;
    201. _head->_prev = _head;
    202. _head->_next = _head;
    203. }
    204. private:
    205. Node* _head;
    206. };

    三、list和vector的对比

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

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

  • 相关阅读:
    离散数学笔记——集合
    css大屏转动效果
    Java代码审计WebGoat—— 登录注册模块初审计
    Web前端大作业——基于HTML+CSS+JavaScript仿英雄联盟LOL游戏网站
    数组更新检测
    腾讯云学生用户专享便宜云服务器购买指南
    pthread_create函数的应用
    WSL下安装ubuntu 18.04 +meep进行FDTD仿真计算
    P02 反射
    React native新架构组成
  • 原文地址:https://blog.csdn.net/Bottle2023/article/details/133429240