• C++——list(2)


    作者:几冬雪来

    时间:2023年9月28日

    内容:C++——list内容讲解

    目录

    前言: 

    list的const迭代器: 

    const的iterator: 

    const迭代器: 

    operator->: 

    拷贝构造:

    迭代器接口补充: 

    代码: 

    结尾: 


    前言: 

    在上一篇博客中我们讲解了list的新接口,list与vector,string的区别所在。更加进一步的说明了list的迭代器的书写,而在今天我们将借由list迭代器来对list的const迭代器进行详细的讲解与说明。 

    list的const迭代器: 

    在学习list的const迭代器之前,我们先来回顾一下list的迭代器。

    list,string和vector的迭代器访问的是指针,所以所有的容器都希望提供像指针一样去访问容器的方式。 

    在string和vector只需要提供原生指针即可。

    string和vector使用的原生指针,在这里typedef T*就是iterator,因为普通的指针完美的符合这个行为

    并且string和vector的物理空间是连续的

    因此它的解引用访问的就是该处的数据,进行++就是访问下一个数据。 

    const的iterator: 

    那么在这里,const的iterator是什么,它使用的指针和vector,string有什么不同

    相较于二者,const的iterator有所不同。 

    在const的使用中分为两种,一种是指针不能被修改,另外一种是指向的内容不能被修改

    像上图在*之前的const修饰的都是指向的内容不能被修改,都是它自己是可以被修改的。所以的迭代器都要符合

    string和vector的不同是因为二者的底层结构占了便宜,因为它们的空间是连续的,而list则是不连续的

    const迭代器: 

    在list中因为空间是不连续的,因此它的指针不能直接使用。这个时候就要借助一个类来封装,来控制它的行为。 

    那么如果控制它的行为呢?我们可以重载它的operator*和operator++,这样就能控制它这里的行为

    在这里我们就将封装的迭代器类型进行一个typedef来达到我们的目标

    接下来我们进行讲解const迭代器,这里我们要使用的const是指向的内容不能被修改,而它自己本身是可以被修改的。 

    这个地方很多人都会这样去写。 

    普通迭代器的对象前加上一个const,这里的const影响的是我们的对象,对象不能被修改。因此在后面运行代码的时候就不能调++等操作。 

    既然这条路走不通,那么我们该如何实现呢?这个地方就需要去控制operator*来达到这个目的

    为了解决问题,在定义处我们又定义了一个新的类,这个新的类和原先的类基本一致。唯一有不同的点就是在operator*处

    那么是哪个地方不同,我们就来对比一下。 

    对比两个类中的operator*,我们不难看出二者的返回值不一样,新的类对比原先的类,在返回值处多了一个const

    但是如果真的要这样写的话,那就太冗余了。 

    这里有没有什么好办法呢?是有的,在这里我们可以通过一个类型去控制这个返回值,也就是增加一个模板参数。 

    类似这里,我们就添加了一个模板参数,同时将operator*的返回值更改为Ref。那么这里的Ref是什么呢

    从上面的类模板中我们是不知道的,这个时候就要看下面的iterator的书写了。

    在这个地方普通的iterator的Ref代表的是T引用,而const iterator在这个地方则是代表的const T引用。 

    这也就完成了想要的通过一个类模板给不同的实例化,其实这里本质上我们还是写了两个类,因为我们给了不同的模板参数

    同时因为是不同的类,因此原先的类就不能使用了,在这个地方就需要我们对新的类进行typedef一下

    这里将新的类typedef为self,同时在下面operator++和operator*等涉及到这个返回值的都要对其进行修改

    operator->: 

    接下来来讲解一下list中的operator->接口

    这个地方有人就要问了,在我们还在学C语言的时候不是有讲解过->和&之间的关系,虽然书写的代码不同,但是结果是相同的。

    在list迭代器中刚刚书写了解引用的操作,为什么还要加入->? 

    为了解答问题,在这里我们写了一个链表,链表的类型是自定义类型。然后接下来我们要对类型进行访问。 

    然后来看一下访问的代码。 

    在这里我们迭代器模拟的是一个指向结构的指针。 

    因为上面迭代器的类型是自定义类型,因此这个地方数据模拟的就是自定义类型的指针。所以代码就要用到->了。 

    因此上面的这一串代码就能改编为下面这样。

    而因为在这里使用了->,所以在迭代器中我们要加入operator->来辅助我们完成

    那么再将它的代码写出来。

    先比较与我们operator解引用时候书写的代码,operator->明显有着不同之处。首先是返回值,解引用的返回值是Ref,而->的返回值是T*

    同时二者return的内容也是不同,->与解引用相比多了一个&符号

    这两种情况就导致了operator->的返回值略显奇怪。

    根据上图进行解释。 

    operator*和operator->处return的_node->_val的值都是我们这里自定义类型A。但是因为返回值的不同,解引用的代码可以装换为对A的解引用,然后就能访问它里面的值

    operator->不同,它的返回值是T*转换的结果是A*也就是A的指针,而我们没办法通过A*去访问A里面的值

    因此理论上来说正确的代码应该这样写。 

    这个地方需要两个->才能真正访问我们里面的数据,第一个->是去调用operator->返回的是A*, 然后第二个->就是去访问A里面的值

    但是如果是这样书写的话,可读性极差。而我们的运算符重载正好要求可读性,那么编译器就进行了一个特殊处理,在这里省略了一个->

    那么接下来一个问题就是它的const该怎么写,案例来说这里的返回值是const T*。 

     

    解决这个问题的话,我们可以新增加一个模板参数来区分它们。 

    拷贝构造

    接下来我们来讲解list的拷贝构造

    通过两篇博客我们可以发现list的迭代器和vector与string多多少少有些不同,但是它们有一点是相同的,那就是在拷贝构造的时候都会出现深浅拷贝的问题。 

    为了处理深浅拷贝的问题,在这个地方如果要将lt1给lt2,首先就要开空间,给出各个指向的是什么

    开好空间之后,接下来就要遍历数据了。在这个地方有一个要注意的点,那就是正常情况下因为不清楚T的对象不确定是vector,string还是其他的,因此在遍历条件处要加引用操作符。 

    最后再将lt1里面的数据插入到lt2,这样就能完成我们的深拷贝了。 

    同样的我们也可以对我们的代码进行优化。 

    因为在list迭代器中多次运用到开空间的操作,因此我们可以将它单独拿出来写一个接口,方便以后的调用。 

    迭代器接口补充: 

    再在最后,将我们平时迭代器用到的代码,比如erase,insert等等都写上,到这里我们的迭代器就正式完成了。 

    代码: 

    1. using namespace std;
    2. #include<>
    3. #pragma once
    4. #include
    5. namespace bit
    6. {
    7. template<class T>
    8. struct list_node
    9. {
    10. list_node* _next;
    11. list_node* _prev;
    12. T _val;
    13. list_node(const T& val = T())
    14. :_next(nullptr),
    15. _prev(nullptr),
    16. _val(val)
    17. {
    18. }
    19. };
    20. template<class T,class Ref,class Ptr>
    21. struct __list_iterator
    22. {
    23. typedef list_node Node;
    24. typedef __list_iterator self;
    25. Node* _node;
    26. __list_iterator(Node* _node)
    27. :_node(node)
    28. {
    29. }
    30. iterator begin()
    31. {
    32. return _head->_next;
    33. }
    34. iterator end()
    35. {
    36. return _head;
    37. }
    38. Ref operator*()
    39. {
    40. return _node->_val;
    41. }
    42. self& operator++()
    43. {
    44. _node = _node->_next;
    45. return *this;
    46. }
    47. Ptr operator->()
    48. {
    49. return &_node->_val;
    50. }
    51. self operator++(int)
    52. {
    53. self tmp(*this);
    54. _node = _node->_next;
    55. return tmp;
    56. }
    57. self& operator--()
    58. {
    59. _node = _node->_prev;
    60. return *this;
    61. }
    62. self operator==(int)
    63. {
    64. self tmp(*this);
    65. _node = _node->_prev;
    66. return tmp;
    67. }
    68. bool operator!=(const Ref& it)
    69. {
    70. return _node != it._node;
    71. }
    72. bool operator==(const Ref& it)
    73. {
    74. return _node == it._node;
    75. }
    76. };
    77. template<class T>
    78. class list
    79. {
    80. typedef list_node Node;
    81. public:
    82. typedef __list_iterator iterator;
    83. typedef __list_iteratorconst T&, const T*> const_iterator;
    84. /*typedef const __list_iterator const_iterator;*/
    85. list()
    86. {
    87. empty_init()
    88. }
    89. ~list()
    90. {
    91. clear();
    92. delete _head;
    93. _head = nullptr;
    94. }
    95. void empty_init()
    96. {
    97. _head = new Node;
    98. _head->_next = _head;
    99. _head->_prev = _head;
    100. }
    101. list(const list& lt)
    102. {
    103. empty_init();
    104. for (auto& e : lt)
    105. {
    106. push_back(e);
    107. }
    108. }
    109. void swap(list& lt)
    110. {
    111. std::swap(_head, lt._head);
    112. std::swap(_size, lt._size);
    113. }
    114. list& operator=(list lt)
    115. {
    116. swap(lt);
    117. return *this;
    118. }
    119. void clear()
    120. {
    121. iterator it = begin();
    122. while (it != end())
    123. {
    124. it = erase(it);
    125. }
    126. }
    127. void push_back(const T& x)
    128. {
    129. insert(end(), x);
    130. }
    131. void push_front(const T& x)
    132. {
    133. insert(begin(), x);
    134. }
    135. void pop_back()
    136. {
    137. erase(--end());
    138. }
    139. void pop_front()
    140. {
    141. erase(begin());
    142. }
    143. iterator insert(iterator pos, const T& x)
    144. {
    145. Node* cur = pos._node;
    146. Node* prev = cur->_prev;
    147. Node* newnode = new Node(x);
    148. prev->_next = newnode;
    149. newnode->_next = cur;
    150. cur->_prev = newnode;
    151. newnode->_prev = prev;
    152. return newnode;
    153. }
    154. iterator erase(iterator pos)
    155. {
    156. assert(pos != end());
    157. Node* cur = pos._node;
    158. Node* prev = cur->_prev;
    159. Node* next = cur->_next;
    160. prev->_next = next;
    161. next->_prev = prev;
    162. delete cur;
    163. return next;
    164. }
    165. private:
    166. Node* _head;
    167. };
    168. }

    结尾: 

    到这里,我们的list也就告一段落了。大部分知识再看一次是不够的,很多都可以要自己画图对其进行理解,并且list的一些知识到后面学习的时候还大有用处,它也是我们C++一个重要的板块,最后希望这篇博客能为学习的各位提供帮助。 

  • 相关阅读:
    okHttp的https请求忽略ssl证书认证
    [jetson]jetson更新系统时候提示nvidia-l4t-bootloader的错误
    【Java进阶】多线程(一)
    数据结构 链表
    MetaGPT: Merging Large Language Models Using Model Exclusive Task Arithmetic
    Chapter6 : Has Artificial Intelligence Impacted Drug Discovery?
    印尼全面禁止直播带货
    Java微服务+分布式+全栈项目(一)---->项目介绍+MyBatis-Plus入门
    内置升压的单声道D类音频功率放大器:HT81293
    【C++编程】类的静态 static 成员 & 常 const 函数
  • 原文地址:https://blog.csdn.net/dongxue727504/article/details/133253844