MyTinySTL学习笔记:迭代器iterator(一)
创始人
2024-03-16 01:12:00
0

前言

本系列文章所学习的Github上基于C++11的开源项目MyTinySTL,项目地址为:(https://github.com/Alinshans/MyTinySTL),感谢Alinshans大佬开源这个优质的学习项目。

一、什么是迭代器

无论是序列容器还是关联容器,最常做的操作无疑是遍历容器中存储的元素,而实现此操作,多数情况会选用“迭代器(iterator)”来实现。那么,迭代器到底是什么呢?

我们知道,尽管不同容器的内部结构各异,但它们本质上都是用来存储大量数据的,换句话说,都是一串能存储多个数据的存储单元。因此,诸如数据的排序、查找、求和等需要对数据进行遍历的操作方法应该是类似的。

既然类似,完全可以利用泛型技术,将它们设计成适用所有容器的通用算法,从而将容器和算法分离开。但实现此目的需要有一个类似中介的装置,它除了要具有对容器进行遍历读写数据的能力之外,还要能对外隐藏容器的内部差异,从而以统一的界面向算法传送数据,于是迭代器就产生了。简而言之,在STL中,迭代器就是算法得以访问容器中数据的桥梁

我们再从代码的角度上去看,迭代器和 C++ 的指针非常类似,其实迭代器在代码层面就是一个行为类似于指针的对象,即①iterator是个对象 ②iterator这个对象具有指针的行为。

在C++中,STL为每一种标准容器定义了一种迭代器类型,这意味着,不同容器的迭代器也不同,其功能强弱也有所不同。

容器的迭代器的功能强弱,决定了该容器是否支持 STL 中的某种算法。

而常用的迭代器按功能强弱分为输入迭代器输出迭代器前向迭代器双向迭代器随机访问迭代器 5 种。

    //五种迭代器struct input_iterator_tag {}; //输入迭代器struct output_iterator_tag {}; //输出迭代器struct forward_iterator_tag : public input_iterator_tag {};//单向迭代器struct bidirectional_iterator_tag : public forward_iterator_tag {};//双向迭代器struct random_iterator_tag : public bidirectional_iterator_tag {};//随机迭代器

二、什么是相关类型

既然迭代器是算法访问容器中数据的桥梁,那么如果此时有一个算法需要知道所指向的对象的类型,我们怎么知道这个容器的“肚子”里都装了些什么?假如有如下这个代码:

	vector vec = {1,2,3};auto it = vec.begin();cout << *it << ' ';		//输出1++it;cout << *it << endl;	//输出2

我们可以对vector的迭代器通过解引用,来访问容器vec中的数据1,2,3。但现在的问题是,我们应该如何获取这个类型xxx?

在算法中,我们可能需要知道这个类型,以便对算法进行优化,所以就需要有一种方法,能让我们查询到迭代器的associated_type(相关类型),以满足我们的需要。

  • 迭代器的常用的(算法会询问迭代器的)5种associated_type
	//iterator 模板 定义一个统一的接口templatestruct iterator{typedef Category        iterator_category;//迭代器的种类typedef T               value_type;//值的类型typedef Pointer         pointer;//指针typedef Reference       reference;//引用typedef Distance        difference_type;//两个迭代器之间的距离};

从代码中我们可以知道,每个iterator必须提供5个统一的类型的别名,分别是iterator_categoryvalue_typepointerreferencedifference_type。它们用来回答以下的问题:

  • iterator_category:迭代器的类型(决定了迭代器可以怎么走,例如vector的迭代器可以随机访问,而链表的迭代器一次只能走一步);
  • value_type:迭代器所指容器中元素的类型;
  • difference_type:迭代器距离范围(即迭代器相减结果的范围)对应的类型,一般是内置类型ptrdiff_t(如果容器的容量范围是[0,2^32-1],则可以将difference_type定义为unsigned int);
  • reference:迭代器所指容器中元素的引用类型;
  • pointer:迭代器所指容器中元素的指针类型;

每个容器在实现自己的iterator时继承这个基类,为自己的5种associated_type赋上实参(通过typedef),这样就避免了容器实现自己的迭代器时接口不统一。

三、如何知道相关类型?iterator_traits的作用

我们已经知道了算法需要根据迭代器的5种associated_type来对迭代器所指容器做出最佳操作这个需求。那么现在,我们来了解一下算法与迭代器之间的问答机制,即迭代器是如何解决算法的需求。

我们知道iterator根据有没有能力定义自己的5种associated_type可以分为class iterator和non-class iterator(即native pointer,原生指针),class iterator可以通过直接问答机制回答算法的问题,而原生指针不可以。

那么有没有一个办法,这两种iterator输入进去,都可以得到相应的associated_type呢?

那么iterator_traits就闪亮登场了!
在这里插入图片描述
由上图可知,通过类模板偏特化的作用,不论是原生指针或者class iterator都可以让外界方便的得到其associated_type。

它的实现如下:

	// iterator traitstemplate struct has_iterator_cat{private:struct two { char a; char b; };template  static two test(...);//C++允许定义形参个数和类型不确定的函数,不确定的形参可以使用省略号template  static char test(typename U::iterator_category* = 0);//Typename关键字 告诉编译把一个特殊的名字解释成一个类型public:static const bool value = sizeof(test(0)) == sizeof(char); };template struct iterator_traits_impl {};template struct iterator_traits_impl{typedef typename Iterator::iterator_category iterator_category;typedef typename Iterator::value_type        value_type;typedef typename Iterator::pointer           pointer;typedef typename Iterator::reference         reference;typedef typename Iterator::difference_type   difference_type;};//iterator_traits_helper类的实现是两个模板偏特化//如果是value是true就使用继承iterator_traits_impl类的版本//如果是false就使用空类体的版本,不执行任何操作template struct iterator_traits_helper {};template struct iterator_traits_helper: public iterator_traits_impl::value ||//std::is_convertible//判断:参数一是否能转化成参数二的类型std::is_convertible::value>//是否能将传入的类型转换成迭代器//如果能,就返回true,反之false{};// 萃取迭代器的特性template struct iterator_traits : public iterator_traits_helper::value> {};// 针对原生指针的偏特化版本template struct iterator_traits{typedef random_access_iterator_tag           iterator_category;typedef T                                    value_type;typedef T*                                   pointer;typedef T&                                   reference;typedef ptrdiff_t                            difference_type;};template struct iterator_traits{typedef random_access_iterator_tag           iterator_category;typedef T                                    value_type;typedef const T*                             pointer;typedef const T&                             reference;typedef ptrdiff_t                            difference_type;};

由源码可知,iterator_traits类的实现为一个泛化版本+两个特化版本,泛化版本用于处理class iterator,特化版本用于处理native pointer和const native pointer。

我们还可以从源码中发现,偏特化版本只定义了5个类型别名,而泛化版本除此之外还有一系列的预处理函数,接下来我们就来了解一下iterator_traits是如何知道任意一个输入迭代器的associated_type的。

iterator_traits类在做的事情就是:接受一个迭代器类型(Iterator/T * /const T * )后,通过typedef的方法在自己的类中定义类型别名,将输入的迭代器的associated_type提取出来(即typedef后,iterator_traits类中的associated_type和输入的迭代器的associated_type就是相同的了)。

template struct iterator_traits_impl{typedef typename Iterator::iterator_category iterator_category;typedef typename Iterator::value_type        value_type;typedef typename Iterator::pointer           pointer;typedef typename Iterator::reference         reference;typedef typename Iterator::difference_type   difference_type;};

其中class iterator因为在类 iterator 内定义了5种associated_type,所以直接typdedef即可,而non-class iterator(native pointer)则需要做一些处理:

①如果传入的是一个普通的原始指针T*,那它所指容器种的对象就是T类型的,所以将value type重命名为T,其他同理。

②如果传入的是一个const的原始指针const T*,此时要注意!其所指容器中的对象是T类型而不是const T!!,这里地方需要解释一下,为什么指针定义为指向常量的指针,但容器中元素的类型却没有定义为const T,这是因为如果在定义中就将元素类型定义为const,那这些元素就无法被赋初值了,也就失去了意义,所以在这里的实现是将元素定义为T类型,指针仍是const T*,这样元素即可以在初始化时被赋值,又避免了用户通过迭代器对其进行修改。

	//情况一template struct iterator_traits{typedef random_access_iterator_tag           iterator_category;typedef T                                    value_type;typedef T*                                   pointer;typedef T&                                   reference;typedef ptrdiff_t                            difference_type;};//情况二template struct iterator_traits{typedef random_access_iterator_tag           iterator_category;typedef T                                    value_type;typedef const T*                             pointer;typedef const T&                             reference;typedef ptrdiff_t                            difference_type;};

这样我们就可以通过iterator_traits获得任意迭代器的associated_type了,如使用:typename iterator_traits::value_type

既然这样就可以了,那为什么该源码的实现中,还为泛化版本写了那么多辅助函数呢?我们来看看源码的泛化版本中,是谁调用了这个核心模块。

我们可以看到,源码中泛化版本的iterator_traits类的定义,其实就是继承了iterator_traits_helper这个类,为什么要这么做?

	template struct iterator_traits : public iterator_traits_helper::value> {};

可以理解成为实际上实现traits功能的类是iterator_traits_helper,但是它有两个模板参数而且类名也不符合STL标准,即最终的对外接口必须是struct iterator_traits {}

	template struct iterator_traits_helper {};template struct iterator_traits_helper: public iterator_traits_impl::value ||std::is_convertible::value>{};

iterator_traits_helper类的实现是两个模板偏特化,根据has_iterator_cat类的value值,来决定使用哪个helper函数。如果是value如果是true就使用继承iterator_traits_impl类的版本,如果是false就使用空类体的版本,不执行任何操作。

而由has_iterator_cat这个类名,我们不难猜出,这一层的意思是,如果传入的这个Iterator有iterator_category,就执行iterator_traits_impl类,进行最后的萃取。如果是false,它就不是个迭代器也就没必要萃取了。

那么has_iterator_cat类又是如何实现的呢?它为什么可以知道一个迭代器有没有iterator_category?让我们来看一下它的源码:

	template struct has_iterator_cat{private:struct two { char a; char b; };template  static two test(...);template  static char test(typename U::iterator_category* = 0);public:static const bool value = sizeof(test(0)) == sizeof(char); };

这里的处理十分巧妙!has_iterator_cat类利用SFINAE机制( 当一个模板展开失败的时候, 会尝试用其他的重载进行展开, 而不是直接报错,),使编译器根据迭代器有还是没有iterator_catefory类型成员,分别送入两个test函数,这两个test函数并不会执行,而是在编译期间,通过test函数的返回值判断迭代器进了哪个函数,从而判断迭代器有没有iterator_category类。

  • 第一个test函数因为形参是(…),它的意思是这个函数能够接受任意个任意类型的参数,该函数返回值为two。
  • 第二个函数拥有一个U::iterator_category类型的形参(这么写就是假设U必须有iterator_category),该函数返回值为char。

接下来我们就看test< T >(0)会如何在SFINAE规则下作用于这两个函数。在这个表达式中,尝试用T去代替U,此时如果T::iterator_category是有效的,当我们传入T时,则进入第二个test函数,此时这个函数虽然不会执行,但是在编译期间,我们就可以知道它的返回值为char类型,从而使value为ture。否则就会进入第一个函数,这时函数返回类型为two,sizeof(two)=2*sizeof(char),从而使value为false。

于是最后,我们再回到源码上发现:当传入一个类型T时,会首先在has_iterator_cat中萃取,如果类型T拥有iterator_category,那么返回一个true,这时iterator_traits_helper会判断其基类iterator_traits_impl,在iterator_traits_impl中如果类型T是5种迭代器之一的话,返回一个true值,typedef出5个associated_type。否则不执行任何操作。

最后

这是一篇记录学习收获和疑惑的帖子,文章的内容,参考自他人的资料以及个人当下的理解,表述并不一定正确。如有错误,还望提出您宝贵的意见。

相关内容

热门资讯

聚杰微纤(300819)披露制... 截至2025年12月25日收盘,聚杰微纤(300819)报收于28.81元,较前一交易日上涨3.0%...
康曼德资本董事长丁楹:A股将进... 2025年A股在政策、估值、盈利、资金四重支撑下走出了牛市行情,但市场细分赛道的分化却愈发明显。20...
缅甸妙瓦底KK园区等已被强力拆... 视频来源:公安部微信公众号 记者12月25日从公安部获悉,近日,公安部派出工作组会同缅甸、泰国执法部...
盐田港(000088)披露公司... 截至2025年12月25日收盘,盐田港(000088)报收于4.55元,较前一交易日上涨0.66%,...
952名缅甸妙瓦底地区涉电诈犯... 来源:人民日报客户端 中缅泰联合开展清剿缅甸妙瓦底地区 赌诈园区行动 952名缅甸妙瓦底地区涉电诈犯...
原创 新... 最近几个赛季,孙铭徽一直都被视为广厦的“小外援”,距离他上一次场均得分不到两位数,还要追溯到2018...
意大利要求Meta暂停禁止竞争... 意大利已下令Meta公司暂停其禁止企业在WhatsApp上使用商业工具提供自家AI聊天机器人的政策。...
山西证券(002500)披露现... 截至2025年12月25日收盘,山西证券(002500)报收于6.11元,较前一交易日上涨0.33%...