C++标准模板库(STL)迭代器的原理与实现

2023-05-16

引言

迭代器(iterator)是一种抽象的设计理念,通过迭代器可以在不了解容器内部原理的情况下遍历容器。除此之外,STL中迭代器一个最重要的作用就是作为容器(vector,list等)与STL算法的粘结剂,只要容器提供迭代器的接口,同一套算法代码可以利用在完全不同的容器中,这是抽象思想的经典应用。

使用迭代器遍历不同容器

如下所示的代码演示了迭代器是如何将容器和算法结合在一起的,其中使用了三种不同的容器,.begin()和.end()方法返回一个指向容器第一个元素和一个指向容器最后一个元素后面一个位置的迭代器,也就是说begin()和end()返回的迭代器是前闭后开的一般用[begin,end)表示。对于不同的容器,我们都使用同一个accumulate函数,原因就在于accumulate函数的实现无需考虑容器的种类,只需要容器传入的begin()和end() 迭代器能够完成标准迭代器的要求即可。

    std::vector<int>vec{ 1, 2, 3 };
    std::list<int>lis{ 4, 5, 6 };
    std::deque<int>deq{ 7, 8, 9 };

    std::cout << std::accumulate(vec.begin(), vec.end(), 0) << std::endl \
        << std::accumulate(lis.begin(), lis.end(), 0) << std::endl \
        << std::accumulate(deq.begin(), deq.end(), 0) << std::endl;
        //6
        //15
        //24

迭代器的实现

迭代器的作用就是提供一个遍历容器内部所有元素的接口,因此迭代器的内部必须保存一个与容器相关联的指针,然后重载各种运算操作来方便遍历,其中最重要的就是<script type="math/tex" id="MathJax-Element-1">*</script>运算符和->运算符,以及++,–等可能需要的运算符重载。实际上这和C++标准库中的智能指针(smart pointer)很像,智能指针也是将一个指针封装,然后通过引用计数或是其它方法完成自动释放内存的功能,为了达到和原有指针一样的功能,也需要对*,->等运算符进行重载,下面参照智能指针实现了一个简单vector的迭代器,其中几个typedef暂时不用管,我们后面会提到。vecIter主要作用就是包裹一个指针,不同容器内部数据结构不相同,因此迭代器操作符重载的实现也会不同。比如++操作符,对于线性分配内存的数组来说,直接对指针执行++操作即可,但是如果容器是List就需要采用元素内部的方法,比如ptr->next()之类的方法访问下一个元素。因此,STL容器都实现了自己的专属迭代器。

template<class Item>
class vecIter{
    Item *ptr;
public:
    typedef std::forward_iterator_tag iterator_category;
    typedef Item value_type;
    typedef Item* pointer;
    typedef Item& reference;
    typedef std::ptrdiff_t difference_type;
public:
    vecIter(Item *p = 0) :ptr(p){}
    Item& operator*()const{
        return *ptr;
    }
    Item* operator->()const{
        return ptr;
    }
    //pre
    vecIter& operator++(){
        ++ptr;
        return *this;
    }
    vecIter operator++(int){
        vecIter tmp = *this;
        ++*this;
        return tmp;
    }

    bool operator==(const vecIter &iter){
        return ptr == iter.ptr;
    }
    bool operator!=(const vecIter &iter){
        return !(*this == iter);
    }

};
int main(){
int a[] = { 1, 2, 3, 4 };
    std::cout << std::accumulate(vecIter<int>(a), vecIter<int>(a + 4), 0);//输出 10

}

迭代器的相应型别

我们都知道type_traits 可以萃取出类型的型别,根据不同型别可以执行不同的处理流程。那么对于迭代器来说,是否有针对不同特性迭代器的优化方法呢?答案是肯定的。拿一个STL算法库中的distance函数来说,distance函数接受两个迭代器参数,然后计算他们两者之间的距离。显然对于不同的迭代器计算效率差别很大。比如对于vector容器来说,由于内存是连续分配的,因此指针直接相减即可获得两者的距离;而list容器是链式表,内存一般都不是连续分配,因此只能通过一级一级调用next()或其他函数,每调用一次再判断迭代器是否相等来计算距离。vector迭代器计算distance的效率为O(1),而list则为O(n),n为距离的大小。

因此,根据迭代器不同的特性,将迭代器分为5类:

  • Input Iterator:这种迭代器所指的对象为只读的。
  • Ouput Iterator: 所指对象只能进行一次写入操作。
  • Forward Iterator: 允许”读写型”算法在迭代器区间内进行读写操作,比如说replace函数需要读取区间内容,根据所读内容决定是否写入
  • Bidirectional Iterator : 可双向移动。某些算法需要反向遍历某个迭代器区间
  • Random Access Iterator : 前四种迭代器只提供部分指针算数能力(前三种支持++运算符,后一种还支持–运算符),第五种则支持所有指针的算术运算,包括p + n,p - n,p[n],p1 - p2,p1 < p2

这五种迭代器的继承关系如下所示。

这里写图片描述

了解了迭代器的类型,我们就能解释vector的迭代器和list迭代器的区别了。显然vector的迭代器具有所有指针算术运算能力,而list由于是双向链表,因此只有双向读写但不能随机访问元素。故vector的迭代器种类为Random Access Iterator,list 的迭代器种类为Bidirectional Iterator。我们只需要根据不同的迭代器种类,利用traits编程技巧萃取出迭代器种类,然后由C++的重载机制就能够对不同型别的迭代器采用不同的处理流程了。为此,对于每个迭代器都必须定义型别iterator_category,也就是上文代码中的typedef std::forward_iterator_tag iterator_category; 实际中可以直接继承STL中定义的iterator模板,模板后三个参数都有默认值,因此继承时只需要指定前两个模板参数即可。如下所示,STL定义了五个空类型作为迭代器的标签。

template<class Category,class T,class Distance = ptrdiff_t,class Pointer=T*,class Reference=T&>
class iterator{
    typedef Category iterator_category;
    typedef T        value_type;
    typedef Distance difference_type;
    typedef Pointer  pointer;
    typedef Reference reference;
};

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{};

利用迭代器种类更有效的实现distance函数

回到distance函数,有了前面的基础,我们可以根据不同迭代器种类实现distance函数。

    template<class InputIterator>
    inline typename std::iterator_traits<InputIterator>::difference_type distance(InputIterator first, InputIterator last){
        return __distance(first, last, std::iterator_traits<InputIterator>::iterator_category());
    }
    template<class InputIterator>
    inline typename std::iterator_traits<InputIterator>::difference_type __distance(InputIterator first, InputIterator last, std::input_iterator_tag){
            std::iterator_traits<InputIterator>::difference_type n = 0;
            while (first != last){
                ++first; ++n;
            }
            return n;
        }

    template<class InputIterator>
    inline typename std::iterator_traits<InputIterator>::difference_type \
        __distance(InputIterator first, InputIterator last, std::random_access_iterator_tag){
            return last - first;
        }


int main(){
int a[] = { 1, 2, 3, 4 };
    std::vector<int> vec{ 1, 2, 3, 4 };
    std::list<int> lis{ 1, 2, 3, 4 };
    std::cout<<"vec distance:"<<WT::distance(vec.begin(), vec.end())<<std::endl;
    std::cout << "list distance:" << WT::distance(lis.begin(), lis.end())<<std::endl;
    std::cout << "c-array distance:" << WT::distance(a,a + sizeof(a) / sizeof(*a)) << std::endl;
        //输出 vec distance:4
        //    list distance:4
        //    c-array distance:4
}

这里通过STL 定义的iterator_traits模板可以将萃取不同种类的迭代器特性,iterator_traits还对指针和常量指针有特化版本,因此也可以萃取原生指针的特性。具体实现如下:

    template <class Iterator>
    struct iterator_traits{
        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;

    };
    template <class T>
    struct iterator_traits<T*>{
        typedef std::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 std::random_access_iterator_tag iterator_category;
        typedef T value_type;
        typedef const T* pointer;
        typedef const T& reference;
        typedef ptrdiff_t difference_type;
    };

小结

STL使用迭代器将算法和容器结合,利用迭代器型别可以针对不同迭代器编写更加高效的算法,这里一点很重要的思想就是:利用C++重载机制和参数推导机制将运行期决议问题提前到编译期决议,也就是说,我们不需要在运行时判断迭代器的类型,而是在编译期就已经决定。这很符合C++模板编程的理念。在后续的STL学习中,我们会实现自己的各种容器,也必须实现各种各样的迭代器,因此迭代器的学习还远没有停止。

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

C++标准模板库(STL)迭代器的原理与实现 的相关文章

随机推荐

  • hadoop 超详细入门wordcount

    概述 今天博客收到了第一条评论 xff0c 感觉很赞哦 xff0c 最近一直在学习hadoop xff0c 主要是结合 实战Hadop xff1a 开启通向云计算的捷径 刘鹏 xff0c 然后apache官网的doc xff08 还是要以官
  • NOIP2018集训总结

    由于语文水平有限 xff0c 精美的桥段 xff0c 跌宕起伏的情节是不可能的了 xff0c 也许看起来会很智障 初赛 前排沙发祝贺墙根火 这次初赛主要在 读程序填空 上失分较多 先找几个不是原因的原因 xff1a 考前那晚宿舍里人巨吵考前
  • Hadoop大数据入门到实战(第四节) - HDFS文件系统(使用)

    这一小节我们来学习 xff1a 1 HDFS的设计 xff0c 2 HDFS常用命令 HDFS的设计 分布式文件系统 客户 xff1a 帮我保存一下这几天的数据 程序猿 xff1a 好嘞 xff0c 有多大呢 xff1f 客户 xff1a
  • HashMap什么时候重写hashcode和equals方法,为什么需要重写

    转载自 xff1a http bdcwl blog 163 com blog static 765222652009112744733937 HashSet内部是通过HashMap实现 只有使用排序的时候才使用TreeMap 否知使用Has
  • 运维如何解决终端部门投诉

    东部某省会城市的联通分公司 xff0c 内部业务系统都运行在VMware为基础的虚拟化环境中 xff0c 但联通的网络运维部在运维时却遇到了很多难题 由于V center的operation manager等云管产品只能监控到虚拟化网络的基
  • Latex并排显示两张图片

    begin figure htbp begin minipage t 0 5 linewidth centering includegraphics width 61 textwidth figures karate graph pdf c
  • HashMap原理深入理解

    hashing 哈希法 的概念 散列法 xff08 Hashing xff09 是一种将字符组成的字符串转换为固定长度 xff08 一般是更短长度 xff09 的数值或索引值的方法 xff0c 称为散列法 xff0c 也叫哈希法 由于通过更
  • ROS2通过话题的发布与订阅进行串口通信

    目录 步骤新建一个cpp header的包进入include xff0c 新建头文件minimal publisher hpp进入src目录 xff0c 新建文件minimal publisher cpp进入src目录 xff0c 新建文件
  • 【增大C盘内存——拓展卷】C盘与未分配空间之间有恢复分区的解决方法——安装diskgenius

    目录 1 简述 2 diskgenius的使用 1 简述 C盘内存告急 xff0c 一般方法是删除 移动 xff0c 还有一类就是拓展C盘的大小 通常来说 xff0c 电脑或笔记本刚买来的时候 xff0c 硬盘分区都是出厂时早就被先划分好的
  • 【使用ubuntu时电脑很卡,连切换个页面都要等好一会儿】

    之前用ubuntu我都是看运气的 xff0c 一打开运行内存必定到达92 左右 xff0c 有时候很卡很卡 xff0c 做个作业花了大半天 xff0c 只有偶尔莫名的顺畅 直至昨天 xff0c 我发现了一个地方 xff0c 将它改过来就好多
  • 使用宝塔部署JavaWeb前后端项目到服务器

    1 我使用的是腾讯云的轻量应用服务器 xff0c 在安装系统的时候可以选择使用宝塔Linux面板 2 安装了宝塔面板以后 xff0c 可以在应用管理中看到宝塔面板的登陆地址 在登录之前需要在用户名和密码那一栏登录 xff0c 来获取宝塔的用
  • VMware USB Arbitration Service无法启动的解决方案

    原文地址 xff1a VMware USB Arbitration Service无法启动的解决方案 作者 xff1a 尔心眼坏坏 问题描述 xff1a 常用VMware虚拟机的朋友们有时应该遇到这种情况 xff0c 就是装完VMware
  • VMware安装linux虚拟机(完整版)

    vmware的安装 去官网进行下载虚拟机 虚拟机下载地址 xff1a VMware16下载 vmware workstation pro 16官方版下载 虚拟机 华军软件园 第一步 打开安装包 第二步 选择稍后安装 是最快的方式 第三步 看
  • AWK中BEGIN和END的使用理解

    awk中begin和end的使用 awk使用 语法 awk 39 script 39 filenames awk使用语法中的script又由多个pattern 43 action组成 单个 pattern actions 应用不通的patt
  • 教你彻底搞懂Cocos Creator Tween

    Cocos 使用了Tween来代替原来的Action系统 今天来给大家讲解Tween如何使用 帮助大家掌握Tween的使用 xff0c 并且对Tween有一个更深入的了解 这里有个cocos creator学习交流点击可以直接进入 1 Tw
  • Hexo的常用指令合集

    Hexo xff1a 一个基于Node js的静态网页生成器 xff0c 常将它与Github Page搭配使用 xff0c 创建个人博客网站 hexo有许多主题 xff0c 其实最火爆的是NexT Matery等 xff0c 复制关键词到
  • (Demo3D 学习笔记)案例2:飞板传输货物,并按指定货位上架

    1 模型描述 通过红色飞板 xff0c 将输送机处的货物逐个传送到货架指定货位 xff0c 货位由货位表指定 2 模型布局 3 解决方案 3 1 载荷发生器 首先给载荷发生器创建自定义属性Loc xff0c 类型为表 在载荷发生器的属性窗口
  • (Demo3D 学习笔记)案例1:自创组件,可以一键自动连接场景中的其他相关组件

    1 模型描述 在场景中自创一个组件 xff0c 通过该组件 xff0c 可以一键自动连接场景中的各相关组件 2 模型布局 首先将自定义组件 xff08 绿色 xff09 分别连接输送机 xff08 左 xff09 xff0c 叉车 xff0
  • Ubuntu18.04安装配置使用Intel RealSense D435i深度相机以及在ROS环境下配置

    最近因为学习开发需要 xff0c 要开始接触一些视觉相关的内容 xff0c 拿到了一个Inter 的D435i深度相机 xff0c 记录一下在Ubuntu18环境下配置SDK 包的历程 目录 写在开头最新的SDK 支持ROS2 Wrappe
  • C++标准模板库(STL)迭代器的原理与实现

    引言 迭代器 iterator 是一种抽象的设计理念 xff0c 通过迭代器可以在不了解容器内部原理的情况下遍历容器 除此之外 xff0c STL中迭代器一个最重要的作用就是作为容器 vector list等 与STL算法的粘结剂 xff0