• list模拟实现(15)


    目录

    1、简单框架

    1、list.h

    2、test.cpp

    2、list迭代器实现

    1、list.h

    2、test.cpp

    3、思考

    1、迭代器中的拷贝构造和赋值重载是否需要自己实现?析构呢?

    2、体会类型的力量

    3、const迭代器实现

    1、list.h

    2、test.cpp

    4、重载迭代器的operator->

    5、insert和erase接口实现

    1、insert模拟实现

    2、erase模拟实现

    6、vector与list对比

    1、vector

    2、list

    7、list拷贝构造、赋值重载问题

    1、clear模拟实现

    2、析构函数

    3、深拷贝

    1、传统写法

    2、现代写法

    8、迭代器区间和n个val初始化的构造函数冲突

    1、问题

    2、解决方法

    9、反向迭代器模拟实现


    1、简单框架

    list的底层是一个双向带头循环链表

    1、list.h

    1. #pragma once
    2. #include
    3. using namespace std;
    4. namespace tutu
    5. {
    6. template<class T>
    7. struct listNode
    8. {
    9. listNode* _next;
    10. listNode* _prev;
    11. T _data;
    12. listNode(const T& data=T())
    13. :_next(nullptr)
    14. ,_prev(nullptr)
    15. ,_data(data)
    16. {}
    17. };
    18. template<class T>
    19. class list
    20. {
    21. typedef listNode Node;
    22. public:
    23. list()
    24. :_head(new Node)
    25. {
    26. _head->_next = _head;
    27. _head->_prev = _head;
    28. }
    29. void push_back(const T& x)
    30. {
    31. Node* newnode = new Node(x);
    32. Node* tail = _head->_prev;
    33. tail->_next = newnode;
    34. newnode->_prev = tail;
    35. newnode->_next = _head;
    36. _head->_prev = newnode;
    37. }
    38. private:
    39. Node* _head;
    40. };
    41. void test1()
    42. {
    43. list<int> l1;
    44. l1.push_back(1);
    45. l1.push_back(2);
    46. l1.push_back(3);
    47. l1.push_back(4);
    48. }
    49. }

    2、test.cpp

    1. #include"list.h"
    2. int main()
    3. {
    4. tutu::test1();
    5. return 0;
    6. }

    2、list迭代器实现

    迭代器是像指针一样的类型,模仿指针的行为,解引用能取数据,++能到下一个位置。

    在string和vector中,迭代器就是原生指针,因为他们底层是连续的物理空间;但是list底层物理空间不是连续的,如果还用原生指针,解引用是一个节点,++到不了下一个位置,但是C++提供了类,自定义类型可以对运算符进行重载,所以list底层迭代器的实现就是对节点指针进行封装,重载operator++、operator!=和operator*。

    1、list.h

    1. #pragma once
    2. #include
    3. using namespace std;
    4. namespace tutu
    5. {
    6. template<class T>
    7. struct listNode
    8. {
    9. listNode* _next;
    10. listNode* _prev;
    11. T _data;
    12. listNode(const T& data=T())
    13. :_next(nullptr)
    14. ,_prev(nullptr)
    15. ,_data(data)
    16. {}
    17. };
    18. template<class T>
    19. struct __list_iterator
    20. {
    21. typedef listNode Node;
    22. Node* _node;
    23. __list_iterator(Node* node)
    24. :_node(node)
    25. {}
    26. //++it
    27. __list_iterator& operator++()
    28. {
    29. _node = _node->_next;
    30. return *this;
    31. }
    32. //it++
    33. __list_iterator operator++(int)
    34. {
    35. __list_iterator tmp(*this);
    36. _node = _node->_next;
    37. return tmp;
    38. }
    39. //--it
    40. __list_iterator& operator--()
    41. {
    42. _node = _node->_prev;
    43. return *this;
    44. }
    45. //it--
    46. __list_iterator operator--(int)
    47. {
    48. __list_iterator tmp(*this);
    49. _node = _node->_prev;
    50. return tmp;
    51. }
    52. T& operator*()
    53. {
    54. return _node->_data;
    55. }
    56. bool operator!=(const __list_iterator& it) const
    57. {
    58. return _node != it._node;
    59. }
    60. bool operator==(const __list_iterator& it) const
    61. {
    62. return _node == it._node;
    63. }
    64. };
    65. template<class T>
    66. class list
    67. {
    68. typedef listNode Node;
    69. public:
    70. typedef __list_iterator iterator;
    71. iterator begin()
    72. {
    73. return iterator(_head->_next);
    74. }
    75. iterator end()
    76. {
    77. return iterator(_head);
    78. }
    79. list()
    80. :_head(new Node)
    81. {
    82. _head->_next = _head;
    83. _head->_prev = _head;
    84. }
    85. void push_back(const T& x)
    86. {
    87. Node* newnode = new Node(x);
    88. Node* tail = _head->_prev;
    89. tail->_next = newnode;
    90. newnode->_prev = tail;
    91. newnode->_next = _head;
    92. _head->_prev = newnode;
    93. }
    94. private:
    95. Node* _head;
    96. };
    97. void test1()
    98. {
    99. list<int> l1;
    100. l1.push_back(1);
    101. l1.push_back(2);
    102. l1.push_back(3);
    103. l1.push_back(4);
    104. l1.push_back(5);
    105. list<int>::iterator it = l1.begin();
    106. while (it != l1.end())
    107. {
    108. cout << *it << " ";
    109. ++it;
    110. }
    111. cout << endl;
    112. }
    113. }

    2、test.cpp

    1. #include"list.h"
    2. int main()
    3. {
    4. tutu::test1();
    5. return 0;
    6. }

    3、思考

    1、迭代器中的拷贝构造和赋值重载是否需要自己实现?析构呢?

    答:都不需要。这里就是需要浅拷贝,默认生成的即可。迭代器中虽然有一个节点指针的成员,但是这个成员不归迭代器管,迭代器的意义就是访问和修改容器的。迭代器是借助节点的指针访问修改链表,节点是属于链表的,不属于迭代器,所以不归它管释放。

    2、体会类型的力量

    Node*原生指针和一个迭代器对象,它们占用的空间是一样大的,在32位机器上都是4个byte,并且值存的也一样,但是对他们使用运算符的意义和结果是不一样的。

    3、const迭代器实现

    普通对象调普通迭代器,const对象调const迭代器,目前我们只实现了普通迭代器,const迭代器还需要再写一份,传统的写法是再写一份const版本的迭代器,但是这样会造成代码冗余,这里推荐高手写法,即stl底层实现写法。

    比较一下普通迭代器和const迭代器的区别,普通迭代器可读可写,const迭代器只能读不能写,体现到代码层面上就是operator*,一个是返回引用,一个是返回const引用,所以这里用了一个模板参数解决这个问题。

    1、list.h

    1. #pragma once
    2. #include
    3. using namespace std;
    4. namespace tutu
    5. {
    6. template<class T>
    7. struct listNode
    8. {
    9. listNode* _next;
    10. listNode* _prev;
    11. T _data;
    12. listNode(const T& data=T())
    13. :_next(nullptr)
    14. ,_prev(nullptr)
    15. ,_data(data)
    16. {}
    17. };
    18. template<class T,class Ref>
    19. struct __list_iterator
    20. {
    21. //因为增加了一个模板参数,所以下面的类型都要改,所以增加一个typedef
    22. typedef __list_iterator self;
    23. typedef listNode Node;
    24. Node* _node;
    25. __list_iterator(Node* node)
    26. :_node(node)
    27. {}
    28. //++it
    29. self& operator++()
    30. {
    31. _node = _node->_next;
    32. return *this;
    33. }
    34. //it++
    35. self operator++(int)
    36. {
    37. __list_iterator tmp(*this);
    38. _node = _node->_next;
    39. return tmp;
    40. }
    41. //--it
    42. self& operator--()
    43. {
    44. _node = _node->_prev;
    45. return *this;
    46. }
    47. //it--
    48. self operator--(int)
    49. {
    50. self tmp(*this);
    51. _node = _node->_prev;
    52. return tmp;
    53. }
    54. Ref operator*()
    55. {
    56. return _node->_data;
    57. }
    58. bool operator!=(const self& it) const
    59. {
    60. return _node != it._node;
    61. }
    62. bool operator==(const self& it) const
    63. {
    64. return _node == it._node;
    65. }
    66. };
    67. template<class T>
    68. class list
    69. {
    70. typedef listNode Node;
    71. public:
    72. typedef __list_iterator iterator;
    73. typedef __list_iteratorconst T&> const_iterator;
    74. const_iterator begin() const
    75. {
    76. return const_iterator(_head->_next);
    77. }
    78. const_iterator end() const
    79. {
    80. return const_iterator(_head);
    81. }
    82. iterator begin()
    83. {
    84. return iterator(_head->_next);
    85. }
    86. iterator end()
    87. {
    88. return iterator(_head);
    89. }
    90. list()
    91. :_head(new Node)
    92. {
    93. _head->_next = _head;
    94. _head->_prev = _head;
    95. }
    96. void push_back(const T& x)
    97. {
    98. Node* newnode = new Node(x);
    99. Node* tail = _head->_prev;
    100. tail->_next = newnode;
    101. newnode->_prev = tail;
    102. newnode->_next = _head;
    103. _head->_prev = newnode;
    104. }
    105. private:
    106. Node* _head;
    107. };
    108. void Print_list(const list<int>& lt)
    109. {
    110. list<int>::const_iterator it = lt.begin();
    111. while (it != lt.end())
    112. {
    113. cout << *it << " ";
    114. ++it;
    115. }
    116. cout << endl;
    117. }
    118. void test1()
    119. {
    120. list<int> l1;
    121. l1.push_back(1);
    122. l1.push_back(2);
    123. l1.push_back(3);
    124. l1.push_back(4);
    125. l1.push_back(5);
    126. Print_list(l1);
    127. }
    128. }

    2、test.cpp

    1. #include"list.h"
    2. int main()
    3. {
    4. tutu::test1();
    5. return 0;
    6. }

    4、重载迭代器的operator->

    前面都是对list举例,链表中放整形,如果是放一个自定义类型,那么cout<<*it 时就会出问题,例如:list,解引用是一个Date类,需要对流提取进行重载,如果不想重载可以如图:

    迭代器是像指针一样的类型,所以可以对operator->进行重载,可以得到如下代码:

    在__list_iterator中,对operator->进行重载,如:

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

    注意:

    然后对于operator->的返回值和operator*有着相同的问题,普通迭代器返回Date*,const迭代器返回const Date*,所以上述不能给T*,这里建议新增模板参数Ptr,传参时,普通迭代器传T*,const迭代器传const T*,如:

    1. #pragma once
    2. #include
    3. using namespace std;
    4. namespace tutu
    5. {
    6. template<class T>
    7. struct listNode
    8. {
    9. listNode* _next;
    10. listNode* _prev;
    11. T _data;
    12. listNode(const T& data=T())
    13. :_next(nullptr)
    14. ,_prev(nullptr)
    15. ,_data(data)
    16. {}
    17. };
    18. template<class T,class Ref,class Ptr>
    19. struct __list_iterator
    20. {
    21. //因为增加了一个模板参数,所以下面的类型都要改,所以增加一个typedef
    22. typedef __list_iterator self;
    23. typedef listNode Node;
    24. Node* _node;
    25. __list_iterator(Node* node)
    26. :_node(node)
    27. {}
    28. //++it
    29. self& operator++()
    30. {
    31. _node = _node->_next;
    32. return *this;
    33. }
    34. //it++
    35. self operator++(int)
    36. {
    37. __list_iterator tmp(*this);
    38. _node = _node->_next;
    39. return tmp;
    40. }
    41. //--it
    42. self& operator--()
    43. {
    44. _node = _node->_prev;
    45. return *this;
    46. }
    47. //it--
    48. self operator--(int)
    49. {
    50. self tmp(*this);
    51. _node = _node->_prev;
    52. return tmp;
    53. }
    54. Ref operator*()
    55. {
    56. return _node->_data;
    57. }
    58. Ptr operator->()
    59. {
    60. return &_node->_data;
    61. }
    62. bool operator!=(const self& it) const
    63. {
    64. return _node != it._node;
    65. }
    66. bool operator==(const self& it) const
    67. {
    68. return _node == it._node;
    69. }
    70. };
    71. template<class T>
    72. class list
    73. {
    74. typedef listNode Node;
    75. public:
    76. typedef __list_iterator iterator;
    77. typedef __list_iteratorconst T&,const T*> const_iterator;
    78. const_iterator begin() const
    79. {
    80. return const_iterator(_head->_next);
    81. }
    82. const_iterator end() const
    83. {
    84. return const_iterator(_head);
    85. }
    86. iterator begin()
    87. {
    88. return iterator(_head->_next);
    89. }
    90. iterator end()
    91. {
    92. return iterator(_head);
    93. }
    94. list()
    95. :_head(new Node)
    96. {
    97. _head->_next = _head;
    98. _head->_prev = _head;
    99. }
    100. void push_back(const T& x)
    101. {
    102. Node* newnode = new Node(x);
    103. Node* tail = _head->_prev;
    104. tail->_next = newnode;
    105. newnode->_prev = tail;
    106. newnode->_next = _head;
    107. _head->_prev = newnode;
    108. }
    109. private:
    110. Node* _head;
    111. };
    112. struct Date
    113. {
    114. int _year;
    115. int _month;
    116. int _day;
    117. Date(int year = 1,int month = 1,int day=1)
    118. :_year(year)
    119. ,_month(month)
    120. ,_day(day)
    121. {}
    122. };
    123. void Print_list2(const list& lt)
    124. {
    125. list::const_iterator it = lt.begin();
    126. while (it != lt.end())
    127. {
    128. cout << it->_year << "/" << it->_month << "/" << it->_day << endl;
    129. ++it;
    130. }
    131. cout << endl;
    132. }
    133. void test2()
    134. {
    135. list l1;
    136. l1.push_back(Date(2022, 11, 23));
    137. l1.push_back(Date(2022, 11, 24));
    138. l1.push_back(Date(2022, 11, 25));
    139. l1.push_back(Date(2022, 11, 26));
    140. Print_list2(l1);
    141. }
    142. }

    5、insert和erase接口实现

    1、insert模拟实现

    有了insert,push_back和push_front可以复用:

    2、erase模拟实现

    有了erase,pop_back和pop_front就可以复用:

    6、vector与list对比

    1、vector

    vector是连续的物理空间,是优势,也是劣势。

    优势:支持高效的随机访问。

    劣势:1、空间不够要增容,增容代价比较大  2、可能存在一定的空间浪费,按需申请,会导致频繁增容,所以都会扩2倍左右  3、头部或中部插入删除需要挪动数据,效率低下

    2、list

    list很好解决了vector以上的问题

    1、按需申请释放空间  2、list任意位置支持O(1)插入删除

    小结:vector和list是互补的两个数据结构。

    7、list拷贝构造、赋值重载问题

    1、clear模拟实现

    2、析构函数

    3、深拷贝

    1、传统写法

    2、现代写法

    1、支持迭代器区间初始化的构造函数

    2、拷贝构造

    3、赋值重载

    8、迭代器区间和n个val初始化的构造函数冲突

    1、问题

    n个val初始化的构造函数:

    目前我们的代码中,已经写了支持一段迭代器区间初始化和n个val初始化的构造函数,但是当写以下代码时,编译器会报错:

    这就是经典的,迭代器区间初始化和n个val初始化的构造函数的冲突问题:

    小结:1、整形的字面常量默认为int类型的,浮点数的字面常量默认为double类型。  2、编译器匹配的原则:只会找更匹配的。

    2、解决方法

    法一:将size_t  n改为int  n

    法二:提供一个int  n的重载版本

    9、反向迭代器模拟实现

    反向迭代器跟正向迭代器的区别就是++、--的方向是反的,所以反向迭代器封装正向迭代器即可,控制重载++、--的方向。

    list库里的底层rbegin、rend跟begin、end是对称的。所以operator*取前一个位置,主要就是为了让反向迭代器开始和结束跟正向迭代器对称。

    因为反向迭代器是对正向迭代器的一个封装,所以可以做成模板,iterator是哪个容器的迭代器,reverse_iterator就可以适配出那个容器的反向迭代器,这是复用的体现。


    reverse_iterator.h

    1. #pragma once
    2. namespace tutu
    3. {
    4. template<class Iterator,class Ref,class Ptr>
    5. class reverse_iterator
    6. {
    7. typedef reverse_iterator self;
    8. public:
    9. reverse_iterator(Iterator it)
    10. :_it(it)
    11. {}
    12. //++rit
    13. self operator++()
    14. {
    15. --_it;
    16. return *this;
    17. }
    18. //--rit
    19. self operator--()
    20. {
    21. ++_it;
    22. return *this;
    23. }
    24. bool operator!=(const self& rit) const
    25. {
    26. return _it != rit._it;
    27. }
    28. Ref operator*()
    29. {
    30. Iterator prev = _it;
    31. return *--prev;
    32. }
    33. Ptr operator->()
    34. {
    35. return &operator*();
    36. }
    37. private:
    38. Iterator _it;
    39. };
    40. }

    在list.h中包一下上述头文件,再增加两行typedef,就可以实现反向迭代器

    我们这里有三个模板参数,而库里只有一个,这里是做了简化,因为库里的比较麻烦。

    注意:

    1、一个类里面要取它里面的东西,只能取它的成员变量和内嵌类型,模板参数可通过typedef取到。 

    2、要取模板参数中的内嵌类型,编译器这里肯定是通不过的,因为编译器编译到这里还没有实例化,凡是要取一个模板参数里的内嵌类型(内部类或typedef的),加个typename是告诉编译器,它是一个类型,先让它通过,等实例化时,再去找它里面的类型。

    3、如果上述按照库中实现,定义一个模板参数,在__list_iterator定义了内嵌类型,但是vector、string就不能直接套,它们底层是原生指针,不是自定义类型,所以还是三个模板参数比较好。vector的迭代器是原生指针,无法取内部类型,那么上面的实现就完了,stl源代码中是使用了一个叫迭代器萃取的技术解决的。

  • 相关阅读:
    深入探讨医保购药APP的技术架构与设计思路
    安装Vue Devtools调试工具插件
    记一次使用nacos2踩到的坑
    Python 配置pyqt5开发环境
    Scala词频统计
    vue2实现可拖拽甘特图(结合element-ui的gantt图)
    C++ 指向数组的指针
    用git上传github加强
    健身戴什么耳机,盘点几款健身佩戴的耳机推荐
    《实现领域驱动设计》- 领域服务
  • 原文地址:https://blog.csdn.net/qq_44463986/article/details/128005725