MyTinySTL学习笔记:迭代器iterator(一)

news2024/12/26 0:10:22

前言

本系列文章所学习的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<xxx> 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 模板 定义一个统一的接口
    template<class Category, class T, class Distance = ptrdiff_t//ptrdiff_t是减去两个指针的结果的带符号整数类型
    , class Pointer = T*, class Reference = T&>
    struct 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 traits
	template <class T>
	struct has_iterator_cat
	{
	private:
	  struct two { char a; char b; };
	  template <class U> static two test(...);//C++允许定义形参个数和类型不确定的函数,不确定的形参可以使用省略号
	  template <class U> static char test(typename U::iterator_category* = 0);//Typename关键字 告诉编译把一个特殊的名字解释成一个类型
	public:
	  static const bool value = sizeof(test<T>(0)) == sizeof(char); 
	};
	
	template <class Iterator, bool>
	struct iterator_traits_impl {};
	
	template <class Iterator>
	struct iterator_traits_impl<Iterator, true>
	{
	  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 <class Iterator, bool>
	struct iterator_traits_helper {};
	
	template <class Iterator>
	struct iterator_traits_helper<Iterator, true>
	  : public iterator_traits_impl<Iterator,
	  std::is_convertible<typename Iterator::iterator_category, input_iterator_tag>::value ||//std::is_convertible
																							 //判断:参数一是否能转化成参数二的类型
	  std::is_convertible<typename Iterator::iterator_category, output_iterator_tag>::value>//是否能将传入的类型转换成迭代器
																							//如果能,就返回true,反之false
	{
	};
	
	// 萃取迭代器的特性
	template <class Iterator>
	struct iterator_traits 
	  : public iterator_traits_helper<Iterator, has_iterator_cat<Iterator>::value> {};
	  
	// 针对原生指针的偏特化版本
	template <class T>
	struct iterator_traits<T*>
	{
	  typedef random_access_iterator_tag           iterator_category;
	  typedef T                                    value_type;
	  typedef T*                                   pointer;
	  typedef T&                                   reference;
	  typedef ptrdiff_t                            difference_type;
	};
	
	template <class T>
	struct iterator_traits<const T*>
	{
	  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 <class Iterator>
	struct iterator_traits_impl<Iterator, true>
	{
	  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 <class T>
	struct iterator_traits<T*>
	{
	  typedef random_access_iterator_tag           iterator_category;
	  typedef T                                    value_type;
	  typedef T*                                   pointer;
	  typedef T&                                   reference;
	  typedef ptrdiff_t                            difference_type;
	};
	
	//情况二
	template <class T>
	struct iterator_traits<const T*>
	{
	  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<Iterator>::value_type

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

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

	template <class Iterator>
		struct iterator_traits 
		  : public iterator_traits_helper<Iterator, has_iterator_cat<Iterator>::value> {};

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

	template <class Iterator, bool>
	struct iterator_traits_helper {};
	
	template <class Iterator>
	struct iterator_traits_helper<Iterator, true>
	  : public iterator_traits_impl<Iterator,
	  std::is_convertible<typename Iterator::iterator_category, input_iterator_tag>::value 
	  ||std::is_convertible<typename Iterator::iterator_category, output_iterator_tag>::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 <class T>
	struct has_iterator_cat
	{
	private:
	  struct two { char a; char b; };
	  template <class U> static two test(...);
	  template <class U> static char test(typename U::iterator_category* = 0);
	public:
	  static const bool value = sizeof(test<T>(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。否则不执行任何操作。

最后

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/59011.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

useWindowPrint 自定义的打印HOOK

useWindowPrint 自定义的打印HOOK 1 介绍 useWindowPrint 用于实现页面的打印&#xff0c;打印的效果相对来说比较好。useWindowPrint 支持 onBeforePrint、onBeforePrintContent、onAfterPrint 等回调&#xff0c;可以很方便地在不同阶段进行操作&#xff0c;如果觉得使用回…

基于PHP+MySQL蛋糕甜点销售网站的设计与开发

现如今先进科学技术高速发展&#xff0c;计算机技术已经被社会的各个领域广泛应用。随着计算机技术和通信技术的迅猛发展&#xff0c;互联网的规模也逐步增大&#xff0c;互联网的元素也随之逐渐增加&#xff0c;可以利用其发展通信&#xff0c;也可以利用其进行商业用途&#…

scratch小老鼠偷面包 电子学会图形化编程scratch等级考试二级真题和答案解析2022年9月

目录 scratch小老鼠偷面包 一、题目要求 1、准备工作 2、功能实现 二、案例分析 <

Vue Class与Style绑定

Vue Class与Style绑定1 Class绑定1.1 字符串写法1.2 数组写法1.3 对象写法2 Style绑定2.1 对象写法2.2 数组写法1 Class绑定 在Vue中&#xff0c;如果要为某个元素动态添加某个类&#xff0c;并不会使用document.getElementById等选择器将该元素获得&#xff0c;而是使用v-bin…

Tomcat突然停止运行/Server Tomcat v8.5 Server at localhost fail

Server Tomcat v8.5 Server at localhost failed to start./org.apache.catalina.startup.Catalina start 严重: 所必需的服务组件启动失败&#xff0c;所以无法启动Tomcat 1.使用eclipse写系统时突然无法运行jsp文件 2.查看任务管理器&#xff0c;将其启动&#xff0c;依旧不…

101-115-hive-优化执行计划表优化

101-压缩存储-优化&#xff1a; 执行计划&#xff08;Explain&#xff09; 1&#xff09;基本语法 EXPLAIN [EXTENDED | DEPENDENCY | AUTHORIZATION] query &#xff08;2&#xff09;查看详细执行计划 hive (default)> explain extended select * from emp; hive (de…

OpenGL 图像绿幕抠图

目录 一.OpenGL 图像绿幕抠图 1.IOS Object-C 版本1.Windows OpenGL ES 版本2.Windows OpenGL 版本 二.OpenGL 图像绿幕抠图 GLSL Shader三.猜你喜欢 零基础 OpenGL ES 学习路线推荐 : OpenGL ES 学习目录 >> OpenGL ES 基础 零基础 OpenGL ES 学习路线推荐 : OpenGL E…

Linux 内核(Kernel)组成分析

【好文推荐】 需要多久才能看完linux内核源码&#xff1f; 浅析linux内核网络协议栈--linux bridge 深入理解SR-IOV和IO虚拟化 一文了解Linux上TCP的几个内核参数调优 概述Linux内核驱动之GPIO子系统API接口 一、Linux内核简介 Linux 内核采用宏内核架构&#xff0c;即 Linux …

Springboot利用Security做OAuth2授权验证

OAuth2获取授权令牌&#xff08;token&#xff09;通常有四种方式&#xff1a;授权码模式&#xff0c;简化模式&#xff0c;客户端模式&#xff0c;和密码模式。针对自己系统内用户的登录&#xff0c;通常使用密码模式进行授权。 我们利用Spring Security OAuth2来制作一个授权…

[附源码]Python计算机毕业设计Django健身生活系统论文

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

【Java】从源码分析fail-fast和fail-safe是如何产生的

文章目录fail-fastfail-safe这个问题出现在使用Iterator迭代器的时候。如果某一个集合在使用的时候&#xff0c;另一个线程修改了这个集合&#xff0c;会出现什么情况呢&#xff1f;因此就出现了两种解决策略fail-fast 一旦发现遍历的同时其它人来修改&#xff0c;则立刻抛异常…

【微信小程序】页面跳转、组件自定义、获取页面参数值

&#x1f3c6;今日学习目标&#xff1a;第十七期——页面跳转、组件自定义、获取页面参数值 &#x1f603;创作者&#xff1a;颜颜yan_ ✨个人主页&#xff1a;颜颜yan_的个人主页 ⏰预计时间&#xff1a;25分钟 &#x1f389;专栏系列&#xff1a;我的第一个微信小程序 文章目…

推荐系统相关论文阅读整理

文章题目 基于协同过滤的微信点餐推荐系统的设计与实现 作者 张彭飞 吉林大学 期刊论文在线阅读—中国知网 (cnki.net)摘要关键词协同过滤;推荐系统;Docker;PXC;RedisCluster;名词解释文献研究目的推荐系统应用现状 在1990 年代&#xff0c;为了解决邮件过载问题&#xff0c…

如此简单的k8s,快速玩转ingress

如此简单的k8s&#xff0c;快速玩转ingress NodePort 可以实现的功能和缺陷&#xff1a; 功能、在每一个节点上面都会启动一个端口&#xff0c;在访问的时候通过任何的节点&#xff0c;通过节点加ip的形式实现访问 缺点、也就是说每个端口只能使用一次&#xff0c;一个端口对应…

传输层协议 —— TCP(图解1)

目录 一、TCP的基本认识 1. TCP头部格式 2. TCP协议的特点 3. 什么是TCP连接 4. TCP如何封装与分用 二、通过序列号和确认应答号提高可靠性 1. 32位序列号 2. 32位确认应答号 3. 保证可靠性 4. 为什么序列号和确认应答号是单独的字段 三、窗口大小 1. TCP的发送和…

[附源码]JAVA毕业设计敬老院管理系统(系统+LW)

[附源码]JAVA毕业设计敬老院管理系统&#xff08;系统LW&#xff09; 目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&a…

ubuntu 实现远程开关机

1. 需求 家里厅里有三台linux主机在跑虚拟机, 一台windows主机在跑wsl2 - ubuntu 20.04 分别是 硬件网络连接方式OSip虚拟机sEUC i5 7250U 16Gwifiwin1010.0.1.223wsl2 - 随机ipMineFine S500 R7 5800H 64G网线Zorin OS 16.2 (Ubuntu 20.04 LTS)10.0.1.198vm1 - 10.0.1.156 …

统计信号处理基础 习题解答6-14

题目&#xff1a; 噪声过程由IID零均值&#xff0c;PDF为 的随机变量组成&#xff0c;其中0<ε<1 。这样的PDF成为高斯混合PDF&#xff0c;它用来模拟 具有方差为 的高斯噪声以及剩余的服从方差 的高斯噪声。一般 &#xff0c;且ε≪1 &#xff0c;所以具有方差为 的背…

[Java安全]—Tomcat反序列化注入回显内存马

前言 在之前学的tomcat filter、listener、servlet等内存马中&#xff0c;其实并不算真正意义上的内存马&#xff0c;因为Web服务器在编译jsp文件时生成了对应的class文件&#xff0c;因此进行了文件落地。 所以本篇主要是针对于反序列化进行内存马注入来达到无文件落地的目的…

[附源码]计算机毕业设计JAVA学生信息管理系统

[附源码]计算机毕业设计JAVA学生信息管理系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybati…