C++ 迭代器与反向迭代器

目录

一,什么是迭代器

1,定义

2,迭代器的设计思维

3,迭代器种类

二,迭代器与容器

1,容器中的迭代器

2,迭代器失效问题

三,迭代器的类型萃取(traits)

四,反向迭代器

1,什么是适配器(adapters)

2,什么是反向迭代器

3,反向迭代器的实现


一,什么是迭代器

1,定义

迭代器(iterators)是一种抽象的设计概念,是一种设计模式,它的定义如下:提供一种方法,使之能够按照顺序依次访问某个容器中的各个元素,而又无需暴露该容器的内部表述方式。

2,迭代器的设计思维

不论是泛型思维或 STL 的实际运用,迭代器(iterators)都扮演着重要的角色。STL 的中心思想在于:将数据容器(containers)和算法(algorithms)分开,彼此独立设计,最后再以一剂粘合剂将它们撮合在一起,而迭代器就是这个粘合剂

迭代器是一种行为类似指针的对象,而指针的各种行为中最常见也最重要的便是解引用(dereference)和成员访问(member access),因此,迭代器最重要的编程工作就是对 operator* 和 operator-> 进行重载(overloading)工作。当我们对一个迭代器对象解引用时,我们就会得到这个迭代器所指向的内容:

vector<int> vec = {1,2,3,4};
auto iter = vec.begin();    // vec.begin() 会返回该容器中的第一个元素的迭代器
*iter;                      // 我们对这个迭代器解引用就会得到 vec 中的第一个元素                    

任何一个 STL 算法,都需要获得由一对迭代器所标示的区间用以表示操作范围。这一对迭代器所标示的是个所谓的左闭右开区间,以 [first, last) 表示。也就是说,整个实际范围从 first 开始,直到 last-1。迭代器 last 所指的是最后一个元素的下一位置。这种左闭右开的设计会带来许多的便利,例如下面这个 STL 算法的循环设计:

// find 接收两个迭代器与一个目标,在这两个迭代器构成的范围内搜寻此目标
template<class InputIterator, class T>
InputIterator find(InputIterator first, InputIterator last, const T& value) {while (first != last and *first != value)++first;return first;
}
// 如果返回的迭代器是 last, 则表示没有找到目标

左闭右开示意图:

下面来演示一下容器、算法、迭代器是怎么合作的,以 find 函数为例:


// 只要我们传入不同的迭代器,find 函数就能够对不同的容器进行查找操作
vector<char> vec = { 'a','b','c','d','e'};
list<int> lt = { 1,2,3,4,5,6 };auto it1 = find(vec.begin(), vec.end(), 'd');
if (it1 == vec.end()) cout << "not found" << endl;
else cout << "found" << endl;auto it2 = find(lt.begin(), lt.end(), 5);
// ...

3,迭代器种类

在 C++ 中,常见的迭代器种类包括以下几种:

① 前向迭代器(Forward Iterator):它们适用于单向遍历容器的所有元素,支持递增操作(++)。例如:单向链表(std::forward list)使用的就是这种迭代器。

② 双向迭代器(Bidirectional Iterator):它们是在前向迭代器的基础上增加了递减操作(--)的迭代器,可以向前或向后遍历容器中的元素,适用于双向遍历容器的所有元素。例如:双向链表(std::list)使用的就是双向迭代器

③  随机访问迭代器(Random Access Iterator):它们是功能最为强大的迭代器,支持任意两个迭代器之间的定位、偏移、比较等操作,可以以常量时间实现跳跃访问。除了支持双向迭代器的所有操作外,还支持迭代器的加减法运算以及比较大小的运算。例如:对 std::vector 容器进行随机访问时使用的就是随机访问迭代器。

二,迭代器与容器

1,容器中的迭代器

在 STL 中几乎每个容器都会支持相应的迭代器类型。我们暂且以 std::list 为例,来看看一个容器要设计出迭代器,需要做哪些事情。前面有提到迭代器的行为类似于指针,所以在迭代器类的模板参数列表中一定要有这个“指针”所指向的类型:

template<class T>    // T 是需要在链表中存放的数据类型
class List_Iterator{typedef List_Iterator<T> self;// 因为 std::list 是双向链表, 那自然我们要设计的迭代器肯定是双向迭代器了// 下面是双向迭代器需要实现的基本功能// 解引用与访问T& operator*();T* operator->();// 前置 ++,-- 与后置 ++,--self operator++();self operator++(int);self operator--();self operator--(int);// 迭代器之间的比较bool operator==();bool operator!=();};

在容器 list 中,我们就可以这样来使用这个迭代器:

// 链表类
template<class T>
class list{
public:typedef List_Iterator<T> iterator;// ...
};

不过,我们还要考虑一下 const 迭代器

typedef List_Iterator<const T> const_iterator;    // 这样行吗?

迭代器最重要的编程工作就是对 operator* 和 operator-> 进行重载工作,所以迭代器中肯定会有返回值为 T& 以及 T* 的函数。因此我们要考虑,按照上面的 const 迭代器的设计,T& 和 T* 符合我们的预期吗?我们来看以下代码:

template<class T>
class List_Iterator {
private:T data = 0;
public:List_Iterator() {}T& reference() { return data; }T* pointer() { return &data; }
};int main() {List_Iterator<const int> cit;// 我们所期望的const int& expect_ref = 0;const int* expect_ptr = nullptr;// 实际的auto& real_ref = cit.reference();auto real_ptr = cit.pointer();return 0;
}

我们调出监视窗口:

 

嗯,这样确实符合我们的预期。

 但是我们还要考虑到,如果迭代器需要使用原来的数据类型呢?例如:

template<class T>
class List_Iterator {
public:// List_Node 是链表的节点类型, 如果我们需要 T 来去拿到相应的节点类型呢?typedef List_Node<T> Node;
};List_Iterator<const int> iter;
// 如果我们传入的模板参数是 const int, 那我们在 List_Iterator 中所取得的 Node
// 就是 List_Node<const int>, 而我们所期望的 Node 是 List_Node<int>

所以很明显,typedef List_Iterator<const T> const_iterator; 按照这样来设计肯定不行。

因此,为了得到我们想要的 const 迭代器,我们需要在迭代器类的模板参数列表中新添加两个参数:引用类型与指针类型。解决方案如下:

// T 是需要在链表中存放的数据类型, Ref 是引用类型, Ptr 是指针类型
template<class T, class Ref, class Ptr>    
class List_Iterator{typedef List_Iterator<T, Ref, Ptr> self;// 因为在某些场景下需要用普通迭代器来构造 const 迭代器,所以我们需要下面这两句typedef List_Iterator<T, T&, T*> iterator;List_Iterator(iterator iter);// 为了提高代码的可读性和可维护性, 使得容器的模板参数更具有可定制性和通用性,// 这里添加几个 typedef 来定义一下相应的类型, 这个非常重要!!!typedef T	value_type;typedef Ref reference;typedef Ptr pointer;// 解引用与访问reference operator*();pointer operator->();// 前置 ++,-- 与后置 ++,--self operator++();self operator++(int);self operator--();self operator--(int);// 迭代器之间的比较bool operator==();bool operator!=();};// 链表类
template<class T>
class list{
public:typedef List_Iterator<T, T&, T*> iterator;typedef List_Iterator<T, const T&, const T*> const_iterator;// ...
};

现在,我们已经设计出来了 list 迭代器类型的框架了。不过要想实现一个针对 list 而设计的迭代器,那必须得先对 list 的实现细节有着非常充分的了解(尤其是实现 ++、-- 操作时),虽然容器迭代器的框架都是类似的,但是迭代器的具体实现工作还是就交给容器的设计者好啦。

对 list 迭代器的实现细节感兴趣的话可以看看这个:C++ 简单模拟实现 STL 中的 list 与 queue-CSDN博客

2,迭代器失效问题

会引起其底层空间改变的操作,都有可能使迭代器失效,我们来看下面这个例子:

list<int> lt = {1,2,3,4,5};
auto iter = lt.begin();
lt.erase(1);    
cout << *iter << endl;    // 此时的 iter 还有效吗?

所以为了避免使用失效的迭代器,建议在进行可能导致迭代器失效的操作之后,重新获取迭代器来确保迭代器的有效性。

list<int> lt = {1,2,3,4,5};
auto iter = lt.begin();
lt.erase(1);    
iter = lt.begin();    // 重新获取迭代器
cout << *iter << endl;   

三,迭代器的类型萃取(traits)

每个迭代器都会有自己的相应类型(associated types),在上文中我们尝试去设计 list 迭代器的时候就已经见识过了,想要为一个容器设计一个专门的迭代器,就至少会有 value_type、pointer、reference 这三种类型。根据大佬们的经验,迭代器相应类型并不只有“迭代器所指向对象的类型(即 value_type)”,最常用的相应类型有五种,而 value_type、pointer、reference 就只是其中的三种(这里就不详细介绍另外两种了)。

在很多的情况下,我们都需要拿到迭代器的相应类型(在下一部分中我们去实现反向迭代器时就能够体会到了),怎么拿到呢?我们通过一个叫做 iterator_traits 的迭代器特性萃取机来拿到。

template<class Iterator>
struct iterator_traits {typedef typename Iterator::value_type	value_type;typedef typename Iterator::pointer		pointer;typedef typename Iterator::reference	reference;// 下面这两种只是提一下, 后文不再出现这两种类型typedef typename Iterator::iterator_categoty	iterator_category;typedef typename Iterator::difference_type		difference_type;
};

这个萃取机依赖着这样一个规则:凡是一个专门为容器设计的迭代器,都有能力(且因该)定义自己的相应类型,就比如在上文中写过的:

// T 是需要在链表中存放的数据类型, Ref 是引用类型, Ptr 是指针类型
template<class T, class Ref, class Ptr>    
class List_Iterator{// ...// 为了提高代码的可读性和可维护性, 使得容器的模板参数更具有可定制性和通用性,// 这里添加几个 typedef 来定义一下相应的类型, 这个非常重要!!!typedef T	value_type;typedef Ref reference;typedef Ptr pointer;// ...
};

但是还有一个特殊的情况,那就是:原生指针。原生指针是一种天然的迭代器,但是它没有能力去定义自己的相应类型。怎么办?我们可以通过模板的特化(partial specialization)来解决这一问题。

// 针对迭代器本身是原生指针类型设计的特化版本
template<class T>
struct iterator_traits<T*> {typedef T    value_type;typedef T*   pointer;typedef T&   reference;
};// 针对迭代器本身是 const 指针类型设计的特化版本
template<class T>
struct iterator_traits<const T*> {typedef T		value_type;typedef const	T* pointer;typedef const	T& reference;
};

iterator_traits 的示意图:

四,反向迭代器

1,什么是适配器(adapters)

反向迭代器是一种迭代器适配器。适配器(adapters)在STL组件的灵活组合运用功能上,扮演着轴承、转换器的角色。适配器这个概念,事实上是一种设计模式。适配器的定义如下:将一个 class 的接口转换为另一个 class 的接口,使原本因接口不兼容而不能合作的 classes,可以一起运作。

STL 所提供的各种配接器中,改变仿函数(functors)接口者,我们称为函数适配器(function adapter),改变容器(containers)接口者,我们称为容器适配器(container adapter),改变迭代器(iterators)接口者,我们称为迭代器适配器(iterator adapter)。

2,什么是反向迭代器

reverse terator,它的功能就是将迭代器的移动行为倒转。如果 STL 算法接受的不是一般正常的迭代器,而是这种反向迭代器,它就会以从尾到头的方向来处理序列中的元素。例如:

vector<char> vec = { 1,2,3,4,5,6 };find(vec.begin(), vec.end(), 7);    // 从 1 开始查找到 6,  1->2->...->6
find(vec.rbegin(), vec.rend(), 7);  // 从 6 开始查找到 1,  6->5->...->1
// rbegin() 返回反向迭代器的第一个元素, rend() 返回反向迭代器的最后一个元素

我们可以先来看一下两段 vecotr 与 list 的部分源码:

template <class T>
class vector {
public:typedef T value_type;typedef value_type* iterator;typedef reverse_iterator<iterator> reverse_iterator;reverse_iterator rbegin() { return reverse_iterator(end()); }reverse_terator rend() { return reverse_iterator(begin()); }// ...
};template <class T>
class list {
public:typedef __list_iterator<T, T&, T*> iterator;typedef reverse_iterator<iterator> reverse_iterator;reverse_iterator rbegin() { return reverse_iterator(end()); }reverse_terator rend() { return reverse_iterator(begin()); }// ...
};

在 STL 的容器中,只要是双向序列容器,就一定支持 rbegin() 与 rend() 操作。

3,反向迭代器的实现

仔细观察一下上面的代码,我们可以发现,反向迭代器的 rbegin(),rend() 分别对应着正向迭代器的 end() 与 begin(),但是 rbegin(),rend() 与 end(),begin() 所指向的并不是同一个元素。如图:

为什么要这么设计呢?这其实主要是为了配合迭代器区间左闭右开的习惯,我们想要将一个正向迭代器区间转换为一个反向迭代器区间的时候不要有任何的额外处理。那么,反向迭代器的内部是怎么实现的呢?

// 反向迭代器类
template<class iterator>
class Reverse_Iterator {
private:iterator _it;	// 反向迭代器所对应的正向迭代器public:typedef Reverse_Iterator<iterator> self;// 迭代器相应类型萃取typedef typename iterator_traits<iterator>::reference	reference;typedef typename iterator_traits<iterator>::pointer		pointer;typedef typename iterator_traits<iterator>::value_type	value_type;Reverse_Iterator(iterator it) :_it(it) {}Reverse_Iterator(const self& re_it):_it(re_it._it){}// 将其他类型的反向迭代器转换为当前类型的反向迭代器iterator base()const { return _it; }template<class iter>Reverse_Iterator(const Reverse_Iterator<iter>& re_it):_it(re_it.base()) {}// 下面这个函数就是反向迭代器的关键所在: 对反向迭代器取值// 就是将其对应的正向迭代器后退一个之后再取值reference operator*()const { iterator tmp = _it;return *(--tmp);}pointer operator->()const { return  &(operator*()); }// ++ 实际上变成 --self operator++() { return --_it; }self operator++(int) {iterator tmp = _it;--_it;return tmp;}// -- 实际上变成 ++self operator--() { return ++_it; }self operator--(int) {iterator tmp = _it;++_it;return tmp;}bool operator==(const self& rit) { return _it == rit._it; }bool operator!=(const self& rit) { return not (_it == rit._it); }// ...
};

对反向迭代器进行 ++ 与 -- 的示意图:

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

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

相关文章

sdwan本地组网分析

随着数字化转型的深入发展&#xff0c;企业对网络架构的要求也不断提高。SDWAN&#xff08;软件定义广域网&#xff09;作为一种创新的网络技术&#xff0c;正在逐渐受到企业的关注和采用。SDWAN本地组网技术可以帮助企业快速搭建高效稳定的企业网络架构&#xff0c;提升企业的…

Linux:基础IO

回顾C文件接口 stdin & stdout & stderr C 默认会打开三个输入输出流&#xff0c;分别是 stdin, stdout, stderr 仔细观察发现&#xff0c;这三个流的类型都是 FILE*, fopen 返回值类型&#xff0c;文件指针 系统文件I/O 接口介绍 open man open #include <sy…

TSINGSEE青犀推出县域治理视频基座数字化、智慧化解决方案

一、方案背景 县域治理方案是我国地方治理体系的重要组成部分&#xff0c;对于促进县域经济社会发展、维护社会稳定、推进全面深化改革具有重要意义。随着科技的不断进步&#xff0c;视频监管已经成为了现代社会治理的重要手段之一。县域治理视频监管方案是通过视频监控、数据…

鸿蒙OS开发实例:【装饰器-@BuilderParam】

背景 这是一个基础概念&#xff0c;其实没有什么原因&#xff0c;练习过程中&#xff0c;自然可以感受到其用法&#xff0c;后期加上真实项目的演练&#xff0c;会形成习惯 功能核心理念 “在自定义组件中添加一个点击跳转操作。若直接在组件内嵌入事件方法&#xff0c;将会…

电商系列之优惠券

> 插&#xff1a;AI时代&#xff0c;程序员或多或少要了解些人工智能&#xff0c;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 坚持不懈&#xff0c;越努力越幸运&#xff0c;大家…

安全上网,防止上网被记录(v2ray实现加密通信)

近期听一位亲威说&#xff0c;她在公司休闲的时候上了哪个网站&#xff0c;浏览了过的网站IT部门的人都会知道&#xff0c;这是因为现在大多数网络设备&#xff0c;像路由与交换机都有记录访问网站地址记录功能&#xff0c;涉及还可以设置成记录到交互的内容。要想保密&#xf…

MySQL面试汇总(一)

MySQL 如何定位慢查询 如何优化慢查询 索引及其底层实现 索引是一个数据结构&#xff0c;可以帮助MySQL高效获取数据。 聚簇索引和非聚簇索引 覆盖索引 索引创建原则 联合索引

Linux系统-----------MySQL 数据类型

目录 MySQL 数据类型 一、数值类型 二、日期和时间类型 三、字符串类型 &#xff08;1&#xff09;CHAR类型 &#xff08;2&#xff09;VARCHAR类型 &#xff08;3&#xff09;CHAR和VARACHAR的比较及其应用场景 MySQL 数据类型 MySQL 中定义数据字段的类型对你数据库的…

代码随想录 Day-25

力扣题目 509.斐波那契数 思路 很理所当然的&#xff0c;可以使用递归的方式其次是用动态规划的方式&#xff0c;动态规划的核心就是递推公式。 那么递推和递归一字之差&#xff0c;有什么区别呢&#xff1f;&#xff08;递推和递归的区别&#xff09; 1、递归 class Solutio…

Karmada 管理有状态应用 Xline 的早期探索与实践

背景与动机 目前随着云原生技术和云市场的不断成熟&#xff0c;越来越多的 IT 厂商开始投入到跨云多集群的怀抱当中。以下是 flexera 在 2023 年中关于云原生市场对多云多集群管理的接受程度的调查报告&#xff08;http://info.flexera.com&#xff09; 从 flexera 的报告中可…

软件杯 深度学习 机器视觉 人脸识别系统 - opencv python

文章目录 0 前言1 机器学习-人脸识别过程人脸检测人脸对其人脸特征向量化人脸识别 2 深度学习-人脸识别过程人脸检测人脸识别Metric Larning 3 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习 机器视觉 人脸识别系统 该项目…

U盘惊变:文件夹竟成应用程序?数据恢复全攻略!

一、U盘突发异状&#xff1a;文件夹秒变应用程序 在数字化时代&#xff0c;U盘作为便携存储设备&#xff0c;在日常生活和工作中扮演着重要角色。然而&#xff0c;近期不少用户反映&#xff0c;他们的U盘突然出现了诡异的现象&#xff1a;原本整齐划一的文件夹图标&#xff0c…

STM32G473之flash存储结构汇总

STM32G4系列单片机&#xff0c;为32位的微控制器&#xff0c;理论上其内部寄存器地址最多支持4GB的命名及查找&#xff08;2的32次方&#xff0c;地址命名为0x00000000至0xFFFFFFFF&#xff09;。STM32官方对4GB的地址存储进行编号时&#xff0c;又分割成了8个block区域&#x…

vulnhub靶场之driftingblues-3

一.环境搭建 1.靶场描述 get flags difficulty: easy about vm: tested and exported from virtualbox. dhcp and nested vtx/amdv enabled. you can contact me by email for troubleshooting or questions. This works better with VirtualBox rather than VMware 2.靶场…

Markdown 编辑器使用

CSDN 在博客开头加上 [TOC](你的目录标题)就可以根据博客内容自动生成如下所示的目录&#xff1a; 你的目录标题 Markdown 编辑器功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表无序列表…

如何压缩视频到最小?教会你压缩原理~

在网上上传视频时&#xff0c;经常会遇到因为视频体积过大上传失败等情况发生&#xff0c;怎么降低视频体积呢&#xff1f;科普一个小知识&#xff1a;视频体积和视频的时长、编码格式、分辨率和比特率&#xff08;又称码率&#xff09;有关。视频文件大小计算公式&#xff1a;…

如何优化财务管理?中小型外贸企业实用指南

在当今全球化的商业环境中&#xff0c;越来越多的中小企业涉足外贸领域&#xff0c;以寻求更广阔的市场和发展空间。在这一过程中&#xff0c;财务管理的重要性尤为凸显&#xff0c;需关注外汇风险、税务合规性、现金流等多个方面的问题。 一、中小企业外贸财务管理难题 币种核…

Python入门练习 - 学生管理系统

Python 实现读书管理系统 """ 实现一个命令行版的读书管理系统 """ import os.path import sys# 使用这个全局变量&#xff0c;来管理所有的学生信息 # 这个列表的每个元素都是一个‘字典’&#xff0c;每 个 字典就分别表示了一个同学students …

利用R语言和curl库实现网页爬虫的技术要点解析

R语言简介 R语言是一种自由、跨平台的编程语言和软件环境&#xff0c;专门用于统计计算和数据可视化。它具有丰富的数据处理、统计分析和图形展示功能&#xff0c;被广泛应用于数据科学、机器学习、统计建模等领域。 R语言技术优势 丰富的数据处理功能&#xff1a; R语言拥有…