飞道的博客

C++--list

330人阅读  评论(0)

前言

        这篇文章对于理解封装是非常有帮助的,list的底层是双向链表结构,我们在学习数据结构是就已经学过了双向链表,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。因为list独特的结构,在模拟实现的时候就会发现为了list接口更好为用户使用更多是通过封装。

这篇文章会开始从list的使用开始,看完list的使用之后你会发现跟string和vector的接口使用几乎是一样的,虽然他们的使用是一样的,他的接口都是一样的,但是后面我们通过对接口的模拟实现,你就会发现是不一样,到底哪里不一样就需要你的深入观看--卖个关子。总的来说这篇文章就是来展示迭代器的魅力和模板的魅力。


目录

前言

一、list的介绍及使用

1.1list的介绍

1.2.list的使用

二、list的模拟实现--非const

2.1list的节点

2.2list的迭代器

2.2迭代器的价值

2.3list的接口

三、list的模拟实现--const

3.1理解const修饰iterator

3.2实现const修饰iterator

四、list的模拟实现--大佬的iterator

4.1第三个参数

4.2大佬的iterator

一、list的介绍及使用


1.1list的介绍

        list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。因为list的底层是双向链表结构。

list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好。

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

带头循环双向链表

1.2.list的使用

对于list的使用来说,list中的接口比较多,实则是与vector差不多的,接下来我们就来简单的演示,掌握list正确的使用,然后再去深入研究背后的原理,已达到可扩展的能力。

1.2.1 list的构造

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

1.2.2 list iterator的使用

在list使用中,我们通过迭代器的方式打印链表元素,在vector中我们用的是[],因为vector是顺序表结构,我们可以通过下标进行随机访问。而list却不能,只能用迭代器的方式进行,它的行为像指针一样。这个后面会通过源码剖析的角度来看待,这里就简单的认为迭代就类似于指针。

list是链表结构,在链表中因为内存地址不是连续开辟的,比如:通过下标2去找地址,顺序表中,地址是连续对应的,而链表的地址是随机开辟的,通过下标是不能找到相应的地址的。

 以上就是证明为什么list不能用[]访问,而选择用迭代器。

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

例: 


  
  1. // list迭代器的使用
  2. // 注意:遍历链表只能用迭代器和范围for
  3. void PrintList(const list<int>& l)
  4. {
  5. // 注意这里调用的是list的 begin() const,返回list的const_iterator对象
  6. for (list< int>::const_iterator it = l. begin(); it != l. end(); ++it)
  7. {
  8. cout << *it << " ";
  9. // *it = 10; 编译不通过
  10. }
  11. cout << endl;
  12. }
  13. int main()
  14. {
  15. list< int> l;
  16. l. push_back( 1);
  17. l. push_back( 2);
  18. l. push_back( 3);
  19. l. push_back( 4);
  20. PrintList(l);
  21. return 0;
  22. }

这里需要说明一下就是begin和end既能使用iterator又能使用const_iterator。

begin
iterator begin();const_iterator begin() const;
end
iterator end();const_iterator end() const;

使用const_iterator it =l.begin();这里是通过拷贝构造,即使我们没有写拷贝构造,但是系统会默认生成拷贝构造将指针进行拷贝。

const_iterator it = l.begin(); 

1.2.3list capacity

在链表中就不需要提前把空间开好,这里是按需申请,就不需要reserve和resize了。

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

例: 


  
  1. int main()
  2. {
  3. list<int> l;
  4. l .push_back( 1);
  5. l .push_back( 2);
  6. l .push_back( 3);
  7. l .push_back( 4);
  8. cout << l .size() << endl;
  9. if (!l.empty())
  10. {
  11. cout << "no empty!" << endl;
  12. }
  13. return 0;
  14. }

 1.2.4 list element access

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

 例: 


  
  1. void tets_element(list<int>& l)
  2. {
  3. cout << l .front() << endl;
  4. cout << l .back() << endl;
  5. }

1.2.5 list modifiers

函数声明 接口说明
push_front 在list首元素前插入值为val的元素
pop_front 删除list中第一个元素
push_back 在list尾部插入值为val的元素
insert 在list position 位置中插入值为val的元素
erase 删除list position位置的元素
swap 交换两个list中的元素
clear 清空list中的有效元素

  例:


  
  1. // list插入和删除
  2. void test_modifi ers()
  3. {
  4. list < int> l;
  5. //尾插
  6. l. push_back( 1);
  7. l. push_back( 2);
  8. l. push_back( 3);
  9. l. push_back( 4);
  10. //尾删
  11. l. pop_back();
  12. PrintList(l);
  13. cout << endl;
  14. //头插
  15. l. push_front( 6);
  16. l. push_front( 7);
  17. l. push_front( 8);
  18. //头删
  19. l. pop_front();
  20. PrintList(l);
  21. cout << endl;
  22. //第一个位置插入10
  23. auto pos = l. begin();
  24. l. insert(pos, 10);
  25. PrintList(l);
  26. cout << endl;
  27. // 删除pos位置上的元素
  28. l. erase(++pos);
  29. PrintList(l);
  30. }
  31. // resize/swap/clear
  32. void TestList5()
  33. {
  34. // 用数组来构造list
  35. int array1[] = { 1, 2, 3 };
  36. list<int> l1(array1, array1 + sizeof(array1) / sizeof(array1[0]));
  37. PrintList(l1);
  38. // 交换l1和l2中的元素
  39. list< int> l2;
  40. l1. swap(l2);
  41. PrintList(l1);
  42. PrintList(l2);
  43. // 将l2中的元素清空
  44. l2. clear();
  45. cout << l2. size() << endl;
  46. }

1.2.6 list的迭代器失效

之前在vector中insert是失效的,但是在list中insert是不失效的。因为insert插入并没有改变pos的地址。erase却会失效,迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。

例:


  
  1. void TestListIterator1()
  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. // erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给其赋值
  9. l. erase(it);
  10. ++it;
  11. }
  12. }


  
  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. }

1.2.7 sort

在list中单独有sort,在之前是以模板的形式写入算法库<algorithm>。这里就说明因为它是已链表的结构,排序需要重新实现,那么就意味着list中sort的时间复杂度是不一样的。

list::sort
void sort();

std::sort
template <class RandomAccessIterator>
  void sort (RandomAccessIterator first, RandomAccessIterator last);

 我们通过一段测试代码来比较,同样的长度但是花费时间是巨大的。


  
  1. void test_op()
  2. {
  3. srand( time( 0));
  4. const int N = 100000;
  5. vector< int> v;
  6. v. reserve(N);
  7. list< int> lt1;
  8. list< int> lt2;
  9. for ( int i = 0; i < N; ++i)
  10. {
  11. auto e = rand();
  12. //v.push_back(e);
  13. lt1. push_back(e);
  14. lt2. push_back(e);
  15. }
  16. // 拷贝到vector排序,排完以后再拷贝回来
  17. int begin1 = clock();
  18. for ( auto e : lt1)
  19. {
  20. v. push_back(e);
  21. }
  22. sort(v. begin(), v. end());
  23. size_t i = 0;
  24. for ( auto& e : lt1)
  25. {
  26. e = v[i++];
  27. }
  28. int end1 = clock();
  29. int begin2 = clock();
  30. // sort(lt.begin(), lt.end());
  31. lt2. sort();
  32. int end2 = clock();
  33. printf( "vector sort:%d\n", end1 - begin1);
  34. printf( "list sort:%d\n", end2 - begin2);
  35. }

在运行结果:

1.Debug

vector sort:437
list sort:6937

2.Release

vector sort:7
list sort:16

在N个数据需要排序,vector+ 算法sort  list+ sort通过测试发现list中sort是非常耗时的,vector中sort想对来说更加省时直接用list排序还不如将list的数据拷贝到vector中快。

二、list的模拟实现--非const

我们会通过几个阶段来进行模拟实现,如果一下将全部模拟实现是加大了学习的成本,是对学习很不友好的。


2.1list的节点

因为我们知道list是一个双向链表,所以节点里面有一个前指针,一个后指针,有一个数据data。同时我们也模拟一个构造函数list (const list& x),用于list节点的初始化。


  
  1. template < class T >
  2. struct list_node
  3. {
  4. list_node < T > * _ next;
  5. list_node < T > * _prev;
  6. T _data;
  7. / /构造函数
  8. list_node (const T & x )
  9. :_ next (nullptr )
  10. , _prev (nullptr )
  11. , _data (x )
  12. { }
  13. };

2.2list的迭代器

stl3.0当中的迭代器实现:


  
  1. template< class T, class Ref, class Ptr>
  2. struct __list_iterator {
  3. typedef __list_iterator<T, T&, T*> iterator;
  4. typedef __list_iterator<T, const T&, const T*> const_iterator;
  5. typedef __list_iterator<T, Ref, Ptr> self;
  6. typedef bidirectional_iterator_tag iterator_category;
  7. typedef T value_type;
  8. typedef Ptr pointer;
  9. typedef Ref reference;
  10. typedef __list_node<T>* link_type;
  11. typedef size_t size_type;
  12. typedef ptrdiff_t difference_type;
  13. link_type node;
  14. __list_iterator(link_type x) : node(x) {}
  15. __list_iterator() {}
  16. __list_iterator( const iterator& x) : node(x.node) {}
  17. bool operator==( const self& x) const { return node == x.node; }
  18. bool operator!=( const self& x) const { return node != x.node; }
  19. reference operator*() const { return (*node).data; }
  20. #ifndef __SGI_STL_NO_ARROW_OPERATOR
  21. pointer operator->() const { return &( operator*()); }
  22. #endif /* __SGI_STL_NO_ARROW_OPERATOR */
  23. self& operator++() {
  24. node = (link_type)((*node).next);
  25. return * this;
  26. }
  27. self operator++( int) {
  28. self tmp = * this;
  29. ++* this;
  30. return tmp;
  31. }
  32. self& operator--() {
  33. node = (link_type)((*node).prev);
  34. return * this;
  35. }
  36. self operator--( int) {
  37. self tmp = * this;
  38. --* this;
  39. return tmp;
  40. }

对于stl3.0当中的迭代器实现,我们首先不去关系为什么是3个模板参数,也不用去深研其他的typedef,我们可以理解在iterator中不单单是一个指针了,当然在iterator也有指针,如下

typedef __list_node<T>* link_type;

link_type node;

在之前我们理解中,我们直接对指针进行操作,进行++,--,*(解引用)等等操作,但是对于迭代器iterator,我们通过观察发现iterator中,因为list是链式结构,我们对其++,--,接引用是不能直接实现,所以就不能简单的通过指针就能完成这些操作,那么它是通过对iterator封装来实现++,--等操作的。使用++,--等操作是通过函数调用

大家感兴趣可以先看看上面的,我们先用一个简述版的来带大家简要实现一下


  
  1. template < class T>
  2. struct _list_iterator
  3. {
  4. typedef list_node<T> node;
  5. node* _pnode;
  6. //构造函数
  7. _list_iterator(node* p)
  8. :_pnode(p)
  9. {}
  10. //接引用就是返回节点的值
  11. T& operator*()
  12. {
  13. return _pnode->_data;
  14. }
  15. //++操作就是就将当前指向下一个节点
  16. _list_iterator<T>& operator++()
  17. {
  18. _pnode = _pnode->_next;
  19. return * this;
  20. }
  21. //--操作就是将当前指向前一个节点
  22. _list_iterator<T>& operator--()
  23. {
  24. _pnode = _pnode->_prev;
  25. return * this;
  26. }
  27. //不等于传参节点的指针不等于this节点的指针
  28. bool operator!=( const _list_iterator<T>& it)
  29. {
  30. return _pnode != it._pnode;
  31. }
  32. };

2.2迭代器的价值

这里虽然我们使用vector和list的使用方法是基本类似的,但是我们发现他们的底层已经出现很大的差别。这里*(接引用),在gcc下的vector中我们是直接对原生指针进行操作,但是在list下我们是通过函数调用来实现的。

最后关于迭代器需要强调一点,关于vector中的 iterator其实他可能也不是用原生指针,这个需要看代码的实现,因为我们在gcc和vs中我们发现迭代器失效,他们失效并不一样,意味着他们底层代码实现时不一样的,在gcc它可以是通过原生指针的方式实现,在vs也可以通过封装来实现。

结论:

1.封装底层实现,不暴露底层的实现细节

2.提供统一访问方式,降低使用成本

物理层面比较

对于list类封装中的iteratior与vector中的iteratior的字节大小是一样的,他们都是4字节。

为什么list也是4字节,因为只看成员变量,不看函数。所以说指针在类中封装后并没有变大,在内存中还是实实在在的4byte。

他们不同的是list中存的时候节点位置的指针,而vector中的是数据位置的指针。

所以在物理层面上他们是没有区别的,他们字节的大小是一样的,但是他们的类型不一样,底层实现就是天差地别了--类型的力量。

2.3list的接口


  
  1. template < class T>
  2. class list
  3. {
  4. typedef list_node<T> node;
  5. public:
  6. typedef _list_iterator<T> iterator;
  7. iterator begin( )
  8. {
  9. //使用了匿名对象--调用拷贝构造
  10. return iterator(_head->_next);
  11. }
  12. iterator end( )
  13. {
  14. return iterator(_head);
  15. }
  16. //当为null时,next与prev都指向head
  17. void empty_initalize( )
  18. {
  19. _head = new node( T());
  20. _head->_next = _head;
  21. _head->_prev = _head;
  22. }
  23. //为null调用empty_initalize
  24. list( )
  25. {
  26. empty_initalize();
  27. }
  28. //拷贝构造
  29. list( const list<T> & lt)
  30. {
  31. empty_initalize();
  32. //这里需要特别注意&(引用)
  33. //如果是内置类型就不影响,e接受数据后,push_back传入后销毁,下一个传又是从lt引用
  34. //如果lt是其他类型,比如是vector类型或者是泛型,数据是一串,
  35. //当push_back完后,没有用引用那么push_back会销毁,传一个数据他销毁了再传一个数据它又没了
  36. for ( const auto &e : lt)
  37. {
  38. push_back(e); //this.push_back
  39. }
  40. return * this; //返回链接值 head->1->2->3
  41. }
  42. //析构函数
  43. ~ list( )
  44. {
  45. clear(); //第一步清楚数据
  46. //第二步将头结点置空
  47. delete _head;
  48. _head = nullptr;
  49. }
  50. //将数据一一清理
  51. void clear( )
  52. {
  53. iterator it = begin();
  54. while (it != end())
  55. {
  56. it = erase(it);
  57. }
  58. }
  59. list<T>& operator=( const list<T>& lt)
  60. {
  61. if ( this != &lt) //判断链表数据不一样后进行操作
  62. {
  63. clear(); //先清空
  64. for ( const auto&e : lt) //传值
  65. {
  66. push_back(e);
  67. }
  68. }
  69. }
  70. //头插
  71. void push_front( const T& x)
  72. {
  73. insert( begin(), x); //调用insert
  74. }
  75. //头删
  76. void pop_front( )
  77. {
  78. erase( begin()); //调用erase
  79. }
  80. //尾删
  81. void pop_back( )
  82. {
  83. erase(-- end());
  84. }
  85. //尾插
  86. void push_back( const T& x)
  87. {
  88. node* newnode = new node(x);
  89. node* tail = _head->_prev;
  90. tail->_next = newnode;
  91. newnode->_prev = tail;
  92. newnode->_next = _head;
  93. _head->_prev = newnode;
  94. //insert(end(),x)
  95. }
  96. iterator insert( iterator pos, const T& x)
  97. {
  98. node* newnode = new node(); //新建节点
  99. node* cur = pos. _pnode; //插入节点是在数据后插,保存改数据后的节点
  100. node* prev = cur. _prve; //保存数据前的节点
  101. //4步链接头尾
  102. prev->_next = newnode;
  103. newnode->_prev = prev;
  104. newnode->_next = cur;
  105. cur->_prev = newnode;
  106. return iterator(newnode); //返回节点
  107. }
  108. iterator erase( iterator pos)
  109. {
  110. assert(pos != end()); //不能删除头节点
  111. node* prev = pos. _pnode->_prev; //保存pos前的节点
  112. node* next = pos. _pnode->_next; //保存pos后的节点
  113. prev->_next = next; //将后节点链接前节点
  114. next->_prev = prev; //将前节点链接后节点
  115. delete pos. _pnode; //删除pos节点--会失效
  116. return iterator(next); //返回pos节点后的节点
  117. }
  118. private:
  119. node* _head;
  120. };

list接口的模拟实现主要insert和erase有些难度,但是对于写过数据结构的代码,我相信大家稍花功夫就能够轻松解决。

insert图解如下

erase图解如下

三、list的模拟实现--const

下面内容主要是迭代器被const修饰,普通人的写法和大佬的写法,他们尽显锋芒。


3.1理解const修饰iterator

错误写法


  
  1. void list_test(const list<int>& lt)
  2. {
  3. const list< int>::iterator lt1 = lt. begin();
  4. }

首先需要理解const T* p1与T* const p2,const迭代器类似p1的行为,保护指向对象不被修改,但是迭代器本身是可以被修改的。

这里的const是修饰的lt1,不符合const迭代器的行为,因为他保护迭代器本身不能修改,那么我们也就不能++迭代器。

因为iterator是被封装使用的,我们发现在struct _list_iterator中,我们改变其返回值用const修饰,那么我们还是可以对迭代器进行++,--等操作,只是返回值不能被改变。


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

既然是需要const修饰返回值,那么我们能不能直接通过函数重载来支持呢?


  
  1. T& operator*()
  2.         {
  3.              return _pnode->_data;
  4.         }
  5. const T& operator*() const
  6.         {
  7.              return _pnode->_data;
  8.         }

答案是不能的。如果clt(被const修饰的参数)++,就调用operator++(),返回值是不被修改,这里的this也被const修改,那么this指向的节点都被修改,该节点就不能实现++操作,这样实现的话只能接引用,但不能++。


  
  1. const T& operator*() const
  2. {
  3. return _pnode->_data;
  4. }
  5. _list_iterator<T>& operator++()
  6. {
  7. _pnode = _pnode->_next;
  8. return * this;
  9. }

如果我们是实现

const _list_iterator<T>& operator++()  const

 答案也是不行的,因为这个过程就是对this值进行操作,这里其实就this已经都被const修饰了。有点绕但是我们主要需要知道整个this值,不然很容易就昏了。

3.2实现const修饰iterator

既然上述解释了在一个类中通过函数重载,const修饰的this会影响,因为我们需要函数重载的话,const不仅仅需要修返回值,也要修改this,上面写法就是错误的

const T& operator*() const

那么我们重新建立类,其他不变,只将返回参数改成被const修饰即可。

const T& operator*() 


  
  1. template< class T>
  2. struct _list_const_iterator
  3. {
  4. typedef list_node<T> node;
  5. node* _pnode;
  6. _list_const_iterator(node* p)
  7. :_pnode(p)
  8. {}
  9. const T& operator*()
  10. {
  11. return _pnode->_data;
  12. }
  13. _list_const_iterator<T>& operator++()
  14. {
  15. _pnode = _pnode->_next;
  16. return * this;
  17. }
  18. _list_const_iterator<T>& operator--()
  19. {
  20. _pnode = _pnode->_prev;
  21. return * this;
  22. }
  23. bool operator!=( const _list_const_iterator<T>& it)
  24. {
  25. return _pnode != it._pnode;
  26. }
  27. };

我们建立了两个类,所以我们在list直接进行用就可以了。添加上begin+end被const修饰的接口就可以了。


  
  1. template < class T>
  2. class list
  3. {
  4. typedef list_node<T> node;
  5. public:
  6. typedef _list_iterator<T> iterator;
  7. typedef _list_const_iterator<T> const_iterator;
  8. const_iterator begin() const
  9. {
  10. return const_iterator(_head->_next);
  11. }
  12. const_iterator end() const
  13. {
  14. return const_iterator(_head);
  15. }
  16. }

四、list的模拟实现--大佬的iterator

上面写的iterator,我们发现大多数代码都是一样的,仅仅是返回值不一样,就建立了两个类,这样的话代码就比较冗余。那么大佬是不可能写出这样的代码,大佬就通过增加模板参数就很好的解决这一问题了。


4.1第三个参数

原来模板参数只有一个类型,现在将模板参数增加为两个,以前我们是通过自己构建两个类,来实现他们不同功能,但是我们直接在模板里多增加一个类型,编译器就默认实例化两个类型。

template<class T, class Ref>

同一个类模板实例化出的2个类型

typedef __list_iterator<T, T&,> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;

这里T就是普通参数,Ref就是被const修饰的参数。那么在看源码的时候有三个参数,这里就不得不提到返回数据的指针了。


  
  1. struct pos
  2. {
  3. int _row;
  4. int _col;
  5. pos( int row= 0, int col= 0)
  6. :_row(row)
  7. , _col(col)
  8. {
  9. }
  10. };
  11. int main()
  12. {
  13. list< pos> lt;
  14. pos p1( 1, 1);
  15. lt.push_back(p1);
  16. lt.push_back(p1);
  17. lt.push_back(p1);
  18. lt.push_back( pos( 2, 2));
  19. lt.push_back( pos( 3, 3));
  20. list< pos>::iterator it = lt.begin();
  21. while (it != lt.end())
  22. {
  23. cout << (*it)._row << ":" << (*it)._col << endl;
  24. ++it;
  25. }
  26. return 0;
  27. }

我们在结构体中,是通过接引用(迭代器的位置)的值,该值再通过.去访问结构体中的行的值。这样用起来是非常别扭,以前访问结构体就是直接地址->指向实参,it->_row。

回忆C语言中->的用法


  
  1. struct Data
  2. {
  3. int a, b, c;
  4. };
  5. int main()
  6. {
  7. struct Data * p;
  8. struct Data A = { 1, 2, 3 };
  9. int x;
  10. p = &A;
  11. x = p->a;
  12. cout << x << endl;
  13. return 0;
  14. }

这里it是迭代器,所以我们需要封装->,然后在进行应用。

Ptr operator->()
        {
            return &_pnode->_data;
        }

Ptr返回的是节点数据的地址,拥有->后,我们再看看使用。

while (it != lt.end())
    {
        cout << it->_row << ":" << it->_col << endl;
        ++it;

    }

it->_row是什么意思?it->是调用it.operator->(),返回节点数据的地址,紧接着是返回节点数据的地址->_row,正确的写法应该是it->->_row。但是这是既不好看又不好用,编译器为了可读性,省略了一个->。

while (it != lt.end())
    {
        cout << it.operator->()->_row << ":" << it->_col << endl;
        ++it;

    }

当然这样用也是不错的。

综上所述知道我们所添加的第三个参数就是T*,用于接受返回的地址。

template<class T, class Ref, class Ptr>

typedef __list_iterator<T, T&, T*> iterator;

4.2大佬的iterator


  
  1. #pragma once
  2. #include<assert.h>
  3. namespace bit
  4. {
  5. template< class T>
  6. struct list_node
  7. {
  8. list_node<T>* _next;
  9. list_node<T>* _prev;
  10. T _data;
  11. list_node( const T& x)
  12. :_next( nullptr)
  13. , _prev( nullptr)
  14. , _data(x)
  15. {}
  16. };
  17. // 同一个类模板实例化出的2个类型
  18. // typedef __list_iterator<T, T&, T*> iterator;
  19. // typedef __list_iterator<T, const T&, const T*> const_iterator;
  20. template< class T, class Ref, class Ptr>
  21. struct __list_iterator
  22. {
  23. typedef list_node<T> node;
  24. typedef __list_iterator<T, Ref, Ptr> Self;
  25. node* _pnode;
  26. __list_iterator(node* p)
  27. :_pnode(p)
  28. {}
  29. //因为很多时候我们需要返回到数据的指针
  30. //返回数据的指针
  31. Ptr operator->()
  32. {
  33. return &_pnode->_data;
  34. }
  35. Ref operator*()
  36. {
  37. return _pnode->_data;
  38. }
  39. Self& operator++()
  40. {
  41. _pnode = _pnode->_next;
  42. return * this;
  43. }
  44. // it++
  45. Self operator++( int)
  46. {
  47. Self tmp(*this);
  48. _pnode = _pnode->_next;
  49. return tmp;
  50. }
  51. Self& operator--()
  52. {
  53. _pnode = _pnode->_prev;
  54. return * this;
  55. }
  56. Self operator--( int)
  57. {
  58. Self tmp(*this);
  59. _pnode = _pnode->_prev;
  60. return tmp;
  61. }
  62. bool operator!=( const Self& it) const
  63. {
  64. return _pnode != it._pnode;
  65. }
  66. bool operator==( const Self& it) const
  67. {
  68. return _pnode == it._pnode;
  69. }
  70. };
  71. //vector<int>
  72. //vector<string>
  73. //vector<vector<int>>
  74. // 类名 类型
  75. // 普通类 类名 等价于 类型
  76. // 类模板 类名 不等价于 类型
  77. // 如:list模板 类名list 类型list<T>
  78. // 类模板里面可以用类名代表类型,但是建议不要那么用
  79. template< class T>
  80. class list
  81. {
  82. typedef list_node<T> node;
  83. public:
  84. typedef __list_iterator<T, T&, T*> iterator;
  85. //typedef __list_const_iterator<T> const_iterator;
  86. typedef __list_iterator<T, const T&, const T*> const_iterator;
  87. const_iterator begin() const
  88. {
  89. return const_iterator(_head->_next);
  90. }
  91. const_iterator end() const
  92. {
  93. return const_iterator(_head);
  94. }
  95. iterator begin()
  96. {
  97. return iterator(_head->_next);
  98. }
  99. iterator end()
  100. {
  101. //iterator it(_head);
  102. //return it;
  103. return iterator(_head);
  104. }
  105. void empty_initialize()
  106. {
  107. _head = new node( T());
  108. _head->_next = _head;
  109. _head->_prev = _head;
  110. _size = 0;
  111. }
  112. list()
  113. {
  114. empty_initialize();
  115. }
  116. //迭代器区间构造
  117. template < class InputIterator>
  118. list(InputIterator first, InputIterator last)
  119. {
  120. empty_initialize();
  121. while (first != last)
  122. {
  123. push_back(*first);
  124. ++first;
  125. }
  126. }
  127. void swap(list<T>& lt)
  128. {
  129. std:: swap(_head, lt._head);
  130. std:: swap(_size, lt._size);
  131. }
  132. // 现代写法--复用
  133. // lt2(lt1)
  134. list( const list<T>& lt) //在类内部:类名 等等价于 类型
  135. //list(const list& lt) // 这里list<T>&与list&是一样的,但是不建议用
  136. {
  137. empty_initialize();
  138. list<T> tmp(lt.begin(), lt.end()); //复用迭代器区间构造
  139. swap(tmp); //交换head和size
  140. }
  141. // lt3 = lt1
  142. list<T>& operator=(list<T> lt) //不能加&,因为就是需要交换的是lt的拷贝
  143. //list& operator=(list lt) // 不建议
  144. {
  145. swap(lt);
  146. return * this;
  147. }
  148. size_t size() const
  149. {
  150. return _size;
  151. }
  152. bool empty() const
  153. {
  154. return _size == 0;
  155. }
  156. ~ list()
  157. {
  158. clear();
  159. delete _head;
  160. _head = nullptr;
  161. }
  162. void clear()
  163. {
  164. iterator it = begin();
  165. while (it != end())
  166. {
  167. it = erase(it);
  168. }
  169. }
  170. void push_back(const T& x)
  171. {
  172. insert( end(), x);
  173. }
  174. void push_front(const T& x)
  175. {
  176. insert( begin(), x);
  177. }
  178. void pop_front()
  179. {
  180. erase( begin());
  181. }
  182. void pop_back()
  183. {
  184. erase(-- end());
  185. }
  186. iterator insert(iterator pos, const T& x)
  187. {
  188. node* newnode = new node(x);
  189. node* cur = pos._pnode;
  190. node* prev = cur->_prev;
  191. // prev newnode cur
  192. prev->_next = newnode;
  193. newnode->_prev = prev;
  194. newnode->_next = cur;
  195. cur->_prev = newnode;
  196. ++_size;
  197. return iterator(newnode);
  198. }
  199. iterator erase(iterator pos)
  200. {
  201. assert(pos != end());
  202. node* prev = pos._pnode->_prev;
  203. node* next = pos._pnode->_next;
  204. prev->_next = next;
  205. next->_prev = prev;
  206. delete pos._pnode;
  207. --_size;
  208. return iterator(next);
  209. }
  210. private:
  211. node* _head;
  212. size_t _size; //为了减少后续频繁调用循环查找size
  213. };
  214. }

                                                                                完结!


转载:https://blog.csdn.net/includeevey/article/details/128554418
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场