C++STL详解三:迭代器

2023-05-16

C++STL详解三:迭代器


文章目录

  • C++STL详解三:迭代器
  • 前言
  • 一、迭代器是什么
  • 二、迭代器的案例:List迭代器的简化版代码
    • 1.简单分析List的储存方式
    • 2.List的迭代器
  • 三、迭代器的五个型别
    • 1.value_type、pointer和reference
    • 2.difference_type
    • 3.iterator_category
  • 总结


前言

在这个专题开始的时候就已经提及过,STL库中的泛型算法大多数情况下并不是为了某一种容器或某一类数据机构所设计的,他们总是服务于绝大多数的容器。然而在算法的设计中,大多数都需要依赖指针遍历数据结构从而进行某些操作,迭代器就是用来统一所有容器的指针的行为的,可以说他是一种泛化的指针。 通过迭代器,我们可以使得算法的设计和容器的设计分割开来,最后通过迭代器使算法应用于容器之中。


一、迭代器是什么

在前言中说到,迭代器是一种泛化的指针,通过操作符的重载,从而使一个class在使用上像是一个指针。想要实现这并不难,只需要拥有一个指针的数据成员,并且重载解引用操作符*和箭头操作符->就可以令一个class在行为上像是一个指针。

同时,迭代器还需要具备的一个特性是,通过++或 - - 操作可以顺序遍历整个容器,这是以后泛型算法可以利用迭代器去访问整个容器的必要条件,为了满足这个需求我们也需要重载这个class的++和- -操作符。

最后,在将萃取器的时候,我们知道算法有时需要通过萃取器获取迭代器的某些属性,这就需要我们自定义的迭代器定义了自己的五个型别 ,进而满足算法的提问。

总结的说,迭代器是每一个容器都需要定义的一个class,它最起码具有以下特征:

  • 具有一个指向容器空间的指针成员
  • 重载了*和->操作符,使这个class可以像指针一样去访问自己的指针成员
  • 重载了++操作符,通过++可以遍历整个容器
  • 定义了自己的五个标准型别

二、迭代器的案例:List迭代器的简化版代码

1.简单分析List的储存方式

我们这一章的重点是迭代器,所以对于容器行为就不做过多的分析,这里我们只需要知道List的存储结构就行,因为这样我们就可以为他设计出一个迭代器。

List是STL库中一个较为简单的容器,他底层是一个双向链表的结构。 这就意味着:

  • List底层的内存空间是不连续的
  • List的每个节点都具有指向下一个节点和上一个节点的两个指针next和pre
  • 尾节点的next是nullptr,头节点的pre是nullptr

第一条意味着我们不能单纯的通过原生指针的++操作,使迭代器前进到下一个节点。
第二条意味着我们可以通过节点中的指针从而移动迭代器
第三条为我们提供了移动上边界的判断条件。

2.List的迭代器

在上边我们分析了List迭代器最基础的需求,现在我们就来简单的实现以下这些需求

先把大的框架搭出来:

template<class Item>
struct ListIter 
	: public bidirectional_iterator_tag
{
	Item* ptr;//指向容器空间的指针
	
	ListIter(Item* p = nullptr) :ptr(p) {};//构造函数
	//这里不必实现拷贝构造函数、拷贝赋值函数和析构函数
	//因为对于迭代器来说,编辑器默认的已经足够了
}
  • 由于List中存储的元素类型不定,所以我们的迭代器也需要是一个模板类
  • 双向链表的指针应该可以前后移动,但不能进行下标随机访问,所以继承自双向指针
  • 我们需要将迭代器和容器建立一个联系,那就是说需要一个指向容器空间的指针

接下来,我们需要让这个类能够实现指针的解引用操作,那么就需要操作符重载:

template<class Item>
struct ListIter 
	: public bidirectional_iterator_tag
{
	Item* ptr;//指向容器空间的指针
	
	ListIter(Item* p = nullptr) :ptr(p) {};//构造函数
	//这里不必实现拷贝构造函数、拷贝赋值函数和析构函数
	//因为对于迭代器来说,编辑器默认的已经足够了

	//使这个class具有指针的行为
	Item& operator*() const { return *ptr; }
	Item* operator->() const { return ptr; }
}
  • 重载 * 操作符的目的是获取指针指向的内容,并且需要修改这个内容
  • 重载->的目的是获取指针的成员,由于->可以一直作用下去,所以我们只用返回指针就行

实现了最基本的指针操作,我们就需要重载++和- -,满足遍历整个容器的需求

template<class Item>
struct ListIter 
	: public bidirectional_iterator_tag
{
	Item* ptr;//指向容器空间的指针

	ListIter(Item* p = nullptr) :ptr(p) {};//构造函数
	//这里不必实现拷贝构造函数、拷贝赋值函数和析构函数
	//因为对于迭代器来说,编辑器默认的已经足够了
	
	//使这个class具有指针的行为
	Item& operator*() const { return *ptr; }
	Item* operator->() const { return ptr; }

	//实现对于整个元素的遍历操作
	ListIter& operator++(){//前置++
		ptr = ptr->next;
		return *this;
	}
	ListIter operator++(int) {//后置++
		ListIter temp = *this;
		++*this;
		return temp;
	}
	ListIter& operator--() {//前置--
		ptr = ptr->pre;
		return *this;
	}
	ListIter operator--(int) {//后置--
		ListIter temp = *this;
		--*this;
		return temp;
	}

	bool operator==(const ListIter& i)
	{ return ptr == i.ptr; }
	bool operator!=(const ListIter& i)
	{ return ptr != i.ptr; }
};

需要注意的是 ++和- -操作符都有前置或后置两种情况,我们需要对每种情况都进行重载
细心的话有可能会注意到,前置和后置两种版本的返回值类型不同,这是因为对于原生指针来说,后置++不能重复叠加使用(如 *p++ ++,这是非法的),所以在我们模仿指针的时候,也要向原生的指针靠近,我们就用这种方式阻止后置++的叠加使用。

最后,我们就需要完成对自身型别的定义,也就产生了完整的版本:

template<class Item>
struct ListIter 
	: public bidirectional_iterator_tag
{
	//定义自己的型别
	typedef Item value_type;//指针指向的元素类型
	typedef Item& reference;//指针指向元素类型的引用
	typedef Item* pointer;//指针指向元素类型的指针
	typedef ptrdiff_t difference_type;//记录两个迭代器之间位置差的元素的类型
	typedef bidirectional_iterator_tag iterator_category;//迭代器的移动类型

	Item* ptr;//指向容器空间的指针

	ListIter(Item* p = nullptr) :ptr(p) {};//构造函数
	//这里不必实现拷贝构造函数和拷贝赋值函数
	//因为对于迭代器来说,编辑器默认的已经足够了
	
	//使这个class具有指针的行为
	Item& operator*() const { return *ptr; }
	Item* operator->() const { return ptr; }

	//实现对于整个元素的遍历操作
	ListIter& operator++(){//前置++
		ptr = ptr->next;
		return *this;
	}
	ListIter operator++(int) {//后置++
		ListIter temp = *this;
		++*this;
		return temp;
	}
	ListIter& operator--() {//前置--
		ptr = ptr->pre;
		return *this;
	}
	ListIter operator--(int) {//后置--
		ListIter temp = *this;
		--*this;
		return temp;
	}

	bool operator==(const ListIter& i)
	{ return ptr == i.ptr; }
	bool operator!=(const ListIter& i)
	{ return ptr != i.ptr; }
};

这五个定义在STL的实际应用上只用到了其中的三个,但是为了融入STL,我们仍然需要按照标准去定义所有的型别。

接下来就来分析这五种型别


三、迭代器的五个型别

为了融入STL,我们的迭代器也必须定义自己的五个型别去满足算法的提问,他们分别是:

  • 记录迭代器指向的元素类型的value_type
  • 记录迭代器指向元素类型的指针的 pointer
  • 记录迭代器指向元素类型的引用的 reference
  • 记录两个迭代器之间距离的元素的类型的 difference_type;
  • 记录迭代器的移动类型的iterator_category

1.value_type、pointer和reference

这个三个typedef顾名思义,分别记录迭代器所指向的类型、指针和引用。前两个没什么值得注意的地方,只有第三个reference有一点需要注意:

从能否改变只想元素的内容这一角度看,迭代器分为不能改变内容的constant和可以改变内容的mutable。若一个指针p他的value_type是T,如果他是mutable的,那么 * p的类型就不应该是T而应该是T&,因为只有这样才可以保证他是一个可被改变的左值;如果p是constant的,那么* p的类型也应该是const T&

2.difference_type

在对迭代器或指针进行操作时,我们很有可能令两个指向同一个空间的指针相减,从而得到两个指针之间的距离,而difference_type型别的作用就是记录用什么样的元素去保存这个数值。若difference_type的类型是int,那么当两个指针之间的距离超过了int可能承载的数值时,就会出错。

大多数情况下,我们都会使用头文件 xutility 中所定义的类型ptrdiff_t去储存两个原生指针之间的距离,所以在迭代器中,一般情况下也使用这个类型作为difference_type.

3.iterator_category

这个型别记录了迭代器可以怎么进行移动,是最重要也是最经常被算法提问的型别,因为它的不同在很大程度上可以影响算法的复杂程度。STL定义了五种迭代器:

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_access_iterator_tag : public bidirectional_iterator_tag{};

他们的继承关系如下图:
图片来自侯捷C++STL与泛型系列教程讲义
在这里插入图片描述

随机迭代器 继承自 双向迭代器 继承自 前序迭代器 继承自 输入迭代器
在他们的继承体系之外的是输出迭代器。

迭代器的移动类型对于算法效率的影响是很深远的,例如:
当我们需要在排序号的查找容器中的一个元素所在位置时
对于随即迭代器,我们可以采用二分查找,但是对于双向或者前序迭代器,我们只能遍历整个容器去查找。这两者最有情况下的时间复杂度分别是O(logN) 和 O(N),效率差别还是挺大的。

所以说,在设计算法时,对于不同的迭代器的移动类型,最好额外设计一种偏特化的版本,这样有利于提高算法的效率


总结

总结的说,迭代器是每一个容器都需要定义的一个class,它最起码具有以下特征:

  • 具有一个指向容器空间的指针成员
  • 重载了*和->操作符,使这个class可以像指针一样去访问自己的指针成员
  • 重载了++操作符,通过++可以遍历整个容器
  • 定义了自己的五个标准型别

通过迭代器,我们可以实现算法和容器的分开设计,最后使用迭代器将二者联系起来

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++STL详解三:迭代器 的相关文章

随机推荐

  • 倾斜补偿的电子罗盘(1):地磁场,磁传感器,倾斜补偿

    倾斜补偿的电子罗盘 1 xff1a 地磁场 xff0c 磁传感器 xff0c 倾斜补偿 地磁场和磁传感器 地磁场可以用于获取方位信息 以北半球为例 xff0c 地磁场方向不是与地面水平 xff0c 而是与水平方向有一定的倾角 xff08 指
  • vscode常用插件

    vscode常用插件 1 Markdown All In One 在所有拓展插件中 xff0c 这个插件基础功能最全 xff0c 快捷键多 xff0c 方便使用 2 Markdown Toc 这个插件是用来生成目录 xff0c 这个插件我用
  • typescript中使用字典Dictionary

    key为string value为number var map key string number 61 34 t 34 3 34 o 34 5 34 g 34 10 for let k in map egret log map k
  • Qt Design Studio社区版安装与使用

    Qt Design Studio社区版免费下载 由于登录官网不能下载 xff0c 找到镜像网站进行下载 xff1a 基于msvc 2019 64位 xff1a http iso mirrors ustc edu cn qtproject o
  • vscode用conda配置Python虚拟环境

    1 到官网下载Anaconda2 下载vscode中Python插件3 以管理员身份运行vscode4 利用conda创建python虚拟环境5 在vscode中配置Setting json添加Python路径6 最后检验运行Python项
  • 阿里云搭建ftp服务器+FileZilla客户端查看文件

    文章目录 1 阿里云搭建ftp服务器1 1 安装ftp服务器1 2 设置阿里云ECS的安全组 2 FileZilla客户端查看文件 1 阿里云搭建ftp服务器 1 1 安装ftp服务器 1 安装vsftp xff0c sudo span c
  • QtCreator+windows崩溃定位分析

    文章目录 一 Qt程序Release版本记录崩溃信息 xff0c 并定位问题代码1 Release版本程序中生成pdb调试信息文件2 添加代码将程序崩溃时的堆栈保存为crash dmp文件3 使用 WinDbg 分析crash dmp文件
  • Qt项目入门

    一个简单的项目 该项目涉及tcp服务端客户端通信 xff0c 数据库操作 xff0c Log4Qt日志打印 xff0c 不过是关于工业与上位机的一个简单项目处理 xff0c 过程不一定容易理解 xff0c 附上通讯协议 Qt5 12 Min
  • delete this注意事项

    参考资料 在类中调用delete this问题
  • c++较常用的库函数

    不知道原创是谁 xff0c 转载自 xff1a https blog csdn net laozhuxinlu article details 51878947 C 43 43 常用库函数 如图1所示 xff0c 常用数学函数 头文件 in
  • 学习c++的50个网站

    原 xff1a http blog chinaunix net uid 20548989 id 2979724 html 大家都说学Java好找工作 xff0c 可是Java我都遗忘好久了 xff0c 大一大二荒废了 xff0c 没好好练编
  • 数据预处理-数据清洗之numpy创建及属性

    什么是数据预处理 xff1f 数据预处理 xff08 data preprocessing xff09 是指在主要的处理以前对数据进行的一些处理 一般我们得到的数据会存在有缺失值 重复值等 xff0c 在使用之前需要进行数据预处理 它是一系
  • 数据预处理-数据清洗之numpy访问与计算

    如何访问numpy数组中的元素 xff1f 采用索引或者切片的方式 span class token comment 导入包 span span class token keyword import span numpy span clas
  • Unity Spine 换图(通过外部图片)

    在spine中 xff0c 如果想通过外部一张单独的来替换动画中的某个部位 xff0c 需要手动创建Attachment using UnityEngine using System Collections using Spine Unit
  • stm32 esp8266 ota升级-hex合并-烧录-bin生成

    stm32 esp8266 ota系列文章 xff1a stm32 esp8266 ota 快速搭建web服务器之docker安装openresty stm32 esp8266 ota升级 tcp模拟http stm32 esp8266 o
  • 字符串与字符数组的大小

    验证字符串与字符数组的大小 xff0c 代码如下 xff1a include lt stdio h gt int main 字符串与字符数组的区别 int data 61 1 2 3 4 5 char cdata 61 39 h 39 39
  • 数据预处理-数据清洗之pandas库的简单使用

    创建一个series span class token comment 导入包 span span class token keyword import span numpy span class token keyword as span
  • Minimum Snap轨迹规划详解(1)轨迹规划

    一 轨迹规划是什么 xff1f 在机器人导航过程中 xff0c 如何控制机器人从A点移动到B点 xff0c 通常称之为运动规划 运动规划一般又分为两步 xff1a 1 路径规划 xff1a 在地图 xff08 栅格地图 四 八叉树 RRT地
  • RealsenseD415/D435深度相机常用资料汇总

    1 Realsense SDK 2 0 Ubuntu 16 04 安装指导网址 https github com IntelRealSense librealsense blob master doc distribution linux
  • C++STL详解三:迭代器

    C 43 43 STL详解三 xff1a 迭代器 文章目录 C 43 43 STL详解三 xff1a 迭代器前言一 迭代器是什么二 迭代器的案例 xff1a List迭代器的简化版代码1 简单分析List的储存方式2 List的迭代器 三