STL源码剖析(六)迭代器

╰+攻爆jí腚メ 2023-10-10 10:04 168阅读 0赞

STL源码剖析(六)迭代器

文章目录

  • STL源码剖析(六)迭代器
    • 一、什么是迭代器?
    • 二、实现一个简单的容器
    • 三、实现容器的迭代器
    • 四、STL迭代器的规范
      • 4.1 STL迭代器的规范
      • 4.2 STL迭代器的分类

本文将带你学习什么是迭代器,以及STL的迭代器规范

一、什么是迭代器?

迭代器是一种有指针行为(->,*,++)的东西,它可以是原生指针,也可以是重载了``operator->operator*operator++等运算符的对象`

对于原生指针就很清楚了,因为本身就具备指针的功能

对于迭代器是一个对象,你可能就有点不清楚了,别急,下面我使用一个非常简单的例子,来告诉你

二、实现一个简单的容器

首先我们实现了非常非常简单的容器(容器是一个存储数据的对象)

  1. template <class T>
  2. class MVector
  3. {
  4. public:
  5. MVector()
  6. {
  7. mStart = (T*)malloc(1024*1024);
  8. mEnd = mStart;
  9. }
  10. ~MVector()
  11. {
  12. free(mStart);
  13. }
  14. void push(const T& value)
  15. {
  16. *mEnd = value;
  17. ++mEnd;
  18. }
  19. private:
  20. T* mStart;
  21. T* mEnd;
  22. };

这实现了一个非常简单的容器,容器内部可以看是一个数组,mStart指向数组开头,mEnd指向数组最后一个元素的下一个位置,此容器只提供一个方法,那就是通过push添加元素

可以通过以下代码使用这个容器

  1. #include <iostream>
  2. #include <stdlib.h>
  3. int main(int argc, char* argv[])
  4. {
  5. MVector<int> mVt;
  6. for(int i = 0; i < 10; ++i)
  7. mVt.push(i);
  8. return 0;
  9. }

上述程序是简单的往容器中添加数据

想一下,我们现在如果要遍历容器内的所有元素,我们需要怎么做?

对于这个容器,我们当然可以通过某种方法,获取mStartmEnd指针,然后通过它们来遍历所有的数组元素。

但在某些更加复杂的容器中(底层是链表或者红黑树),在这些容器中,我们无法简单的通过指针来遍历所有元素,但是我们却想通过和指针一样的方式来访问容器中的元素,这时候就需要设计对象迭代器,然后重载运算符,将复杂的动作进行封装,提供和指针一样的功能

所以在STL中,大多数容器都提供了其对应的迭代器,下面我们就来为这个容器设计一个迭代器

三、实现容器的迭代器

首先说明一下,对于上述这个容器,其实它的迭代器只要是T*原生指针就行,但是为了演示复杂的迭代器是怎么设计的,这里将实现一个对象迭代器,如下

  1. template <class T>
  2. class Iterator
  3. {
  4. public:
  5. Iterator(T* data) : mData(data)
  6. {}
  7. Iterator& operator++()
  8. {
  9. ++mData;
  10. return *this;
  11. }
  12. T operator*()
  13. {
  14. return *mData;
  15. }
  16. bool operator!=(const Iterator& it)
  17. {
  18. return mData != it.mData;
  19. }
  20. private:
  21. T* mData;
  22. };

此迭代器内含一个对象指针,然后重载了operator++operator*operator!=运算符,这样子它就具有了指针的部分性质

有了迭代器之后,我们重新设计我们的容器,如下

  1. template <class T>
  2. class MVector
  3. {
  4. public:
  5. /* add */
  6. typedef Iterator<T> iterator;
  7. MVector()
  8. {
  9. mStart = (T*)malloc(1024*1024);
  10. mEnd = mStart;
  11. }
  12. ~MVector()
  13. {
  14. free(mStart);
  15. }
  16. void push(const T& value)
  17. {
  18. *mEnd = value;
  19. ++mEnd;
  20. }
  21. /* add */
  22. iterator begin()
  23. {
  24. return iterator(mStart);
  25. }
  26. /* add */
  27. iterator end()
  28. {
  29. return iterator(mEnd);
  30. }s
  31. private:
  32. T* mStart;
  33. T* mEnd;
  34. };

我们使用typedef Iterator<T> iterator对外提供了该容器的迭代器类型,然后提供了begin()end()来获取首尾迭代器

有了这些,我们就可以遍历容器的元素了,如下

  1. int main(int argc, char* argv[])
  2. {
  3. MVector<int> mVt;
  4. /* 添加元素 */
  5. for(int i = 0; i < 10; ++i)
  6. mVt.push(i);
  7. /* 遍历容器 */
  8. for(MVector<int>::iterator it = mVt.begin(); it != mVt.end(); ++it)
  9. std::cout<<*it<<" "; //访问元素
  10. std::cout<<std::endl;
  11. return 0;
  12. }

完整代码

  1. #include <iostream>
  2. #include <stdlib.h>
  3. template <class T>
  4. class Iterator
  5. {
  6. public:
  7. Iterator(T* data) : mData(data)
  8. {}
  9. Iterator& operator++()
  10. {
  11. ++mData;
  12. return *this;
  13. }
  14. T operator*()
  15. {
  16. return *mData;
  17. }
  18. bool operator!=(const Iterator& it)
  19. {
  20. return mData != it.mData;
  21. }
  22. private:
  23. T* mData;
  24. };
  25. template <class T>
  26. class MVector
  27. {
  28. public:
  29. typedef Iterator<T> iterator;
  30. MVector()
  31. {
  32. mStart = (T*)malloc(1024*1024);
  33. mEnd = mStart;
  34. }
  35. ~MVector()
  36. {
  37. free(mStart);
  38. }
  39. iterator begin()
  40. {
  41. return iterator(mStart);
  42. }
  43. iterator end()
  44. {
  45. return iterator(mEnd);
  46. }
  47. void push(const T& value)
  48. {
  49. *mEnd = value;
  50. ++mEnd;
  51. }
  52. private:
  53. T* mStart;
  54. T* mEnd;
  55. };
  56. int main(int argc, char* argv[])
  57. {
  58. MVector<int> mVt;
  59. for(int i = 0; i < 10; ++i)
  60. mVt.push(i);
  61. /* 遍历容器 */
  62. for(MVector<int>::iterator it = mVt.begin(); it != mVt.end(); ++it)
  63. std::cout<<*it<<" ";
  64. std::cout<<std::endl;
  65. return 0;
  66. }

四、STL迭代器的规范

经过上述的讲解,想必你已经知道什么是迭代器了,但是我们上述实现的迭代器不符合STL的规范,所以对于STL的许多算法,我们并无法使用,下面来看一看STL中迭代器的设计规范

stl_iterator.h文件中,定义了许多与迭代器相关的东西

4.1 STL迭代器的规范

首先我们来看一下,STL对迭代器的要求,STL要求迭代器至少需要能提供上述的5种类型,如下

  1. template <class Category, class T, class Distance = ptrdiff_t,
  2. class Pointer = T*, class Reference = T&>
  3. struct iterator {
  4. typedef Category iterator_category; //迭代器类型
  5. typedef T value_type; //迭代器所指的类型
  6. typedef Distance difference_type; //两个迭代器之间的距离描述
  7. typedef Pointer pointer; //迭代器所指的类型的指针类型
  8. typedef Reference reference; //迭代器所指类型的引用类型
  9. };

为什么需要提供这些信息?

因为在STL的算法中,通常需要这些信息来定义相应的类型,或者根据这些信息来采取不同的措施,如果你设计的迭代器不满足这些要求,那么你在使用STL的算法的时候,非常有可能编译失败

关于其它字段,这里不做讨论了,下面好好讨论一个迭代器分类iterator_category

4.2 STL迭代器的分类

STL对迭代器分为5类,其定义如下

  1. /* 输入式迭代器 */
  2. struct input_iterator_tag {};
  3. /* 输出式迭代器 */
  4. struct output_iterator_tag {};
  5. /* 单向迭代器 */
  6. struct forward_iterator_tag : public input_iterator_tag {};
  7. /* 双向迭代器 */
  8. struct bidirectional_iterator_tag : public forward_iterator_tag {};
  9. /* 随机访问迭代器 */
  10. struct random_access_iterator_tag : public bidirectional_iterator_tag {};

它们的关系如下

在这里插入图片描述

单向迭代器:表示往一个方向一格一格移动(单向链表)

双向迭代器:表示往前后方向一格一格移动(双向链表)

随机访问迭代器:可以随机访问(数组)

为什么需要迭代器分类?

主要目的是为了让算法可以采用不同的策略,下面举一个例子

在stl的算法中,有一个叫advance的算法,其作用是移动迭代器的指向,定义如下

  1. inline void advance(InputIterator& i, Distance n)

设想一下,如果现在迭代器对应的容器底部是一个数组,那么移动5格,只需要+5就行,如果是链表的话,就需要一格一格地移动,很明显,这两者地效率差别是相当大

STL的实现如下

  1. template <class InputIterator, class Distance>
  2. inline void advance(InputIterator& i, Distance n) {
  3. __advance(i, n, iterator_category(i));
  4. }

根据迭代器的iterator_category调用相应的__advance函数

对于随机访问的迭代器(random_access_iterator_tag)

  1. template <class RandomAccessIterator, class Distance>
  2. inline void __advance(RandomAccessIterator& i, Distance n,
  3. random_access_iterator_tag) {
  4. i += n;
  5. }

对于双向迭代器

  1. template <class BidirectionalIterator, class Distance>
  2. inline void __advance(BidirectionalIterator& i, Distance n,
  3. bidirectional_iterator_tag) {
  4. if (n >= 0)
  5. while (n--) ++i;
  6. else
  7. while (n++) --i;
  8. }

对于普通的迭代器

  1. template <class InputIterator, class Distance>
  2. inline void __advance(InputIterator& i, Distance n, input_iterator_tag) {
  3. while (n--) ++i;
  4. }

本文到此也就结束了

发表评论

表情:
评论列表 (有 0 条评论,168人围观)

还没有评论,来说两句吧...

相关阅读

    相关 STL剖析——

    前言 在STL的思想中,容器和算法是彼此独立设计的,再通过某种方式使它们连接;而迭代器是使算法独立于使用的容器类型,即迭代器是连接算法和容器的方法。由于迭代器是一种行为

    相关 STL

    什么是迭代器 迭代器是STL中行为类似指针的设计模式,它可以提供了一种对容器中的对象的访问方法;并且它没有暴露容器中内部的表述方式。 例如STL中的map和set,它

    相关 STL&&失效

    1.说说设计模式?(迭代器模式)         迭代器模式作为STL的六大组件之一,通俗来讲,它就是用来遍历容器的,对容器进行一定的操作。我们通常使用的容器vector