对迭代器(Iterator)的思考与总结

1.问题引入

今天好哥们突然给我发来一条QQ消息,问迭代器能不能修改集合,用过迭代器的小伙伴应该都知道这一点:迭代器是用来统一对于List和Set集合的遍历方式的,至于能不能修改集合中的元素,讲真的,我没去思考过。不过当时为了快点回复哥们(其实是为了遮掩自己也不知道的惨状),我就建议他去看下Iterator的接口定义,随后悄咪咪的又学习了一波迭代器,于是便有了这篇博文。
在这里插入图片描述

2.迭代器回顾

2.1 迭代器是什么

  • 可以先看下图,总览一下整个集合框架。

在这里插入图片描述

  • 我们知道不同集合的底层实现是不一样的,比如ArrayList的底层是顺序表,LinkedList的底层是链表,HashSet的底层是hash表,它们的底层实现从根本上决定了它们自身的特性,其中就包括遍历的方式。具有索引的集合(比如List集合)可以采用for循环来遍历,但是由于Set集合不包含索引,所以Set集合就不能使用for循环来遍历。对不同集合遍历的前提是必须了解各个集合的底层实现,这使得集合与其遍历方式存在很强的耦合关系。

  • 迭代器的存在就是为了解决上述强耦合的关系,它总是用同一种逻辑来遍历集合,使得用户自身不需要了解集合的内部结构,所有的内部状态都由Iterator来维护。也就是说,用户不用直接和集合进行打交道,而是控制Iterator向它发送向前向后的指令,就可以遍历集合。

  • 我们看一下迭代器接口,可以看到迭代器中定义了四个方法:hasNext(),next(),remove(),forEachRemaining()。
    在这里插入图片描述

2.2 集合框架是如何实现迭代器接口的

Iterator接口只是在顶层设计了规范,那么这些规范是如何实现的呢?这里以ArrayList为例子,看看它是如何实现迭代器接口的。

  • ArrayList的继承和实现的体系结构
    在这里插入图片描述

  • 从上图中我们并没有看到terator接口,但是看到了Iterable接口,那么这个Iterable接口又和Iterator接口有什么联系呢?到这里我的猜想是Iterable接口内部封装了Iterator接口,之所以这样猜想,是因为没有更加合理的地方来封装Iterator借口了。

  • 如下图所示,我看了下Iterable的源码,发现其内部的确封装了Iterable接口。OK,我们离答案又近了一步,接下继续抽丝剥茧。
    在这里插入图片描述

  • 接下来的问题就是,哪个类又实现了Iterable接口?下面查看Collection接口、AbstractCollection接口、AbstractList接口,看看能不能找到Iterator的踪迹。
    在这里插入图片描述

  • Collection接口继承了Iterable接口,没有对Iterator接口的实现或继承
    在这里插入图片描述

  • List接口继承了Collection接口,同样没有对Iterator接口的实现或继承
    在这里插入图片描述

  • AbstractCollection是一个抽象类,它继承了Collection接口,在它的一些方法里我看到了Iterator接口的方法hasNext()和next(),这说明我们离答案又进了一步。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • 分析到这里我遇到了一个问题:AbstractCollection里只是定义了一个返回值为Iterator的抽象方法,为什么AbstractCollection里面的方法就可以调用Iterator接口的方法了?
    在这里插入图片描述
    在这里插入图片描述
    我的理解是这里的抽象方法虽然没有实现,但是在抽象类中依然可以调用这个抽象方法,把视角放在最终的实现类一切就都合理了。那么这个抽象方法在最终的实现类中是如何实现的呢?继续往下看。

  • 再看一眼继承和实现的结构,我们下一步应该探究一下AbstractList了,那就一起来看一看吧。
    在这里插入图片描述

  • AbstractList类继承了AbstractCollection抽象类,实现了List接口,它内部定义了5个成员类,具体啥作用后面再说。
    在这里插入图片描述
    AbstractList类实现了返回值为Iterator的抽象方法iterator(),它return了一个Itr对象,那么这个Itr对象又是什么呢,我们跳转到Itr处看它的具体定义
    在这里插入图片描述
    可以看到Itr实现了Iterator接口,到这里终于是看到Iterator接口本尊了,答案也就快水落石出了
    在这里插入图片描述
    在这里插入图片描述
    Ltr的源码如下(截图的话篇幅不够,直接上源码,中文注释是自己家的):

 private class Itr implements Iterator<E> {
        /**
         * Index of element to be returned by subsequent call to next.
         */
        int cursor = 0;

        /**
         * Index of element returned by most recent call to next or
         * previous.  Reset to -1 if this element is deleted by a call
         * to remove.
         */
        int lastRet = -1;

        /**
         * The modCount value that the iterator believes that the backing
         * List should have.  If this expectation is violated, the iterator
         * has detected concurrent modification.
         */
        int expectedModCount = modCount;
        
		//实现hasNext()方法
        public boolean hasNext() {
            return cursor != size();
        }

        //实现next()方法
        public E next() {
            checkForComodification();
            try {
                int i = cursor;
                E next = get(i);
                lastRet = i;
                cursor = i + 1;
                return next;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

		//实现remove()方法
        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                AbstractList.this.remove(lastRet);
                if (lastRet < cursor)
                    cursor--;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException e) {
                throw new ConcurrentModificationException();
            }
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
  • 到这里我们已经基本上解决了集合框架是如何实现Iterator接口的问题,但还有一个问题没有解决,就是到现在为止,我们知道了关键在于下图这个方法,实际上我们只需要用到Iterator,但是为什么要用Iterable接口?
    在这里插入图片描述
    先不急着说为什么,可以先看一下LinkedList中是如何实现的
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述在这里插入图片描述
    private class ListItr extends Itr implements ListIterator<E> {
        ListItr(int index) {
            cursor = index;
        }

        public boolean hasPrevious() {
            return cursor != 0;
        }

        public E previous() {
            checkForComodification();
            try {
                int i = cursor - 1;
                E previous = get(i);
                lastRet = cursor = i;
                return previous;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

        public int nextIndex() {
            return cursor;
        }

        public int previousIndex() {
            return cursor-1;
        }

        public void set(E e) {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                AbstractList.this.set(lastRet, e);
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        public void add(E e) {
            checkForComodification();

            try {
                int i = cursor;
                AbstractList.this.add(i, e);
                lastRet = -1;
                cursor = i + 1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
    }

在这里插入图片描述
再来看一下HashSet是如何实现的
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

    abstract class HashIterator {
        Node<K,V> next;        // next entry to return
        Node<K,V> current;     // current entry
        int expectedModCount;  // for fast-fail
        int index;             // current slot

        HashIterator() {
            expectedModCount = modCount;
            Node<K,V>[] t = table;
            current = next = null;
            index = 0;
            if (t != null && size > 0) { // advance to first entry
                do {} while (index < t.length && (next = t[index++]) == null);
            }
        }

        public final boolean hasNext() {
            return next != null;
        }

        final Node<K,V> nextNode() {
            Node<K,V>[] t;
            Node<K,V> e = next;
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (e == null)
                throw new NoSuchElementException();
            if ((next = (current = e).next) == null && (t = table) != null) {
                do {} while (index < t.length && (next = t[index++]) == null);
            }
            return e;
        }

        public final void remove() {
            Node<K,V> p = current;
            if (p == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            current = null;
            removeNode(p.hash, p.key, null, false, false);
            expectedModCount = modCount;
        }
    }

通过上面的两个例子可以知道不同的集合对迭代器的实现也有不同的方法,Iterable接口对Iterator进行了封装,利用接口的多态将是实现的多样性隐藏起来,实际操作时只需调用一个接口就行,这实质上就是设计模式种的工厂模式的体现。

  • 到了这里,对迭代器的底层源码也有了一个大概的掌握,后期有机会再研究研究具体的细节。

3. 迭代器应用实践


		//一、List集合
		List<String> list = new ArrayList<>();
        list.add("henrly");
        list.add("nancy");
        list.add("lucy");
        list.add("jeacy");
    
        //遍历List集合
        //1.使用for循环
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
        //2.使用迭代器
        Iterator<String> it = list.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }
        //3.使用增强for循环
        for (String l : list) {
            System.out.println(l);
        }
        //二、Set集合
 		Set<Integer> set = new TreeSet<>();
        set.add(111);
        set.add(222);
        set.add(333);
        set.add(444);
         //遍历Set集合
        //Set集合无索引,所以无法使用for循环遍历
        //1.使用迭代器
        Iterator<Integer> it = set.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }
        //2.使用增强for循环
        for (Integer i : set) {
            System.out.println(i);
        }

4. 能不能使用迭代器修改集合中的元素

  • Iterator是工作在一个独立的线程中,并且拥有一个 mutex 锁。 Iterator被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出java.util.ConcurrentModificationException异常。所以 Iterator 在工作的时候是不允许被迭代的对象被改变的。
  • 但你可以使用 Iterator 本身的方法 remove() 来删除对象,Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性。

在这里插入图片描述

在这里插入图片描述

总结

通过对源码的解读,我对迭代器有了深入的了解。Java的源码不是凭空而出的,是这个行业的前辈们的设计并实现的,看了这些大佬们的代码,感觉自己现在的技术是真的菜,还有很大的提升空间,以后一定要多读源码,多动手。

<think>好的,用户想了解C++迭代器的概念、用法、使用场景和工作原理。我之前已经回答了关于shared_ptr的问题,现在需要结合用户提供的引用内容来构建关于迭代器的详细回答。 首先,我需要明确用户的需求:理解迭代器的基本概念、如何正确使用、适用场景以及背后的工作原理。用户提供的引用内容涉及迭代器的实现细节、C++20概念、协程异步迭代器以及STL中的示例,这些都需要整合到回答中。 接下来,我需要组织回答的结构。通常,技术详解可以分为几个部分:核心概念、工作原理、分类、使用场景、现代C++增强和注意事项。每个部分需要结合引用中的例子和用户可能关心的问题展开。 核心概念部分要解释迭代器是什么,它的作用,以及为什么它重要。这里可以引用用户提供的STL成功应用的例子,说明迭代器在容器访问中的重要性。 工作原理需要涵盖迭代器的基本操作,比如解引用、递增、比较等,结合用户提供的代码示例(如vector的遍历)来说明。同时,要提到迭代器容器的关系,可能涉及控制块或内部指针的实现细节。 分类部分需要详细说明五种迭代器类型,每种类型的能力和使用场景。用户引用了输入迭代器和输出迭代器的例子,比如istream_iterator,这些可以作为具体例子来补充说明。 使用场景部分要列举常见的应用情况,如容器遍历、算法交互、泛型编程等。用户提到的STL算法(如sort、find)可以作为例子,同时可以结合协程实现异步迭代器的引用,说明现代C++中的扩展应用。 现代C++增强部分需要整合用户提供的RAII管理迭代器生命周期、C++20概念约束和协程异步迭代器。这部分需要详细解释如何利用这些新特性提升迭代器的安全性和表达力,同时给出代码示例,如使用make_unique管理迭代器,或者模板约束的例子。 注意事项部分要提醒用户常见的陷阱,比如迭代器失效问题,特别是在修改容器时如何避免使用无效的迭代器。用户提供的引用中没有直接提到这点,但这是迭代器使用中的重要问题,必须包含。 最后,相关问题需要根据回答内容生成,引导用户进一步思考。比如询问如何避免迭代器失效,C++20概念如何增强迭代器,或者比较不同迭代器类型的差异。 需要确保回答结构清晰,代码示例正确,并且正确引用用户提供的资料。同时,避免使用Markdown格式,使用中文自然表达,符合用户的要求。</think>### C++ 迭代器详解:概念、原理实战应用 --- #### **一、迭代器核心概念** 迭代器是 C++ 中**容器算法之间的桥梁**,提供了一种**统一访问容器元素**的方式。其核心价值在于: 1. **抽象容器结构**:无论容器是数组、链表还是树,迭代器提供一致的遍历接口(如 `++`、`*`)。 2. **解耦算法容器**:标准库算法(如 `std::sort`、`std::find`)通过迭代器操作容器,无需关心具体实现[^3]。 3. **支持泛型编程**:通过模板参数传递迭代器类型,实现类型无关的算法逻辑。 --- #### **二、迭代器工作原理** 1. **基础操作** 迭代器通过以下核心操作实现遍历(以 `std::vector<int>::iterator` 为例): - **解引用** `*it`:获取当前元素值(如 `*it` 返回 `int&`)。 - **递增/递减** `++it`、`--it`:移动迭代器位置。 - **比较** `it1 == it2`:判断是否指向同一位置(如判断是否到达 `end()`)[^2]。 2. **容器的关系** - 容器通过 `begin()` 和 `end()` 返回迭代器范围(左闭右开区间)。 - 示例代码(用户提供): ```cpp std::vector<int> vec = {1, 2, 3}; for (auto it = vec.begin(); it != vec.end(); ++it) { std::cout << *it << " "; // 输出 1 2 3 } ``` 3. **内部实现** 迭代器通常封装容器的**内部指针**或**访问逻辑**。例如: - `std::vector::iterator` 本质是原生指针(随机访问)。 - `std::list::iterator` 封装链表节点的指针(双向移动)。 --- #### **三、迭代器分类能力** C++ 标准定义五类迭代器,按功能递增排序: | 类型 | 支持操作 | 典型容器/场景 | |--------------------|------------------------------------|-------------------------| | **输入迭代器** | 只读、单向移动(`++`) | `istream_iterator`[^4] | | **输出迭代器** | 只写、单向移动 | `ostream_iterator` | | **前向迭代器** | 可读写、单向移动 | `std::forward_list` | | **双向迭代器** | 可读写、双向移动(`++`、`--`) | `std::list` | | **随机访问迭代器** | 支持跳跃访问(`+n`、`it[n]`) | `std::vector`、数组 | --- #### **四、迭代器使用场景** 1. **容器遍历** ```cpp std::list<std::string> names = {"Alice", "Bob"}; for (auto it = names.rbegin(); it != names.rend(); ++it) { // 反向迭代器 std::cout << *it << "\n"; } ``` 2. **算法交互** ```cpp std::vector<int> data = {5, 3, 7, 1}; std::sort(data.begin(), data.end()); // 排序算法通过迭代器操作 ``` 3. **泛型编程** ```cpp template<typename Iter> void print_range(Iter start, Iter end) { while (start != end) { std::cout << *start++ << " "; } } ``` 4. **现代C++扩展** - **RAII管理迭代器**(用户提供引用[^1]): ```cpp auto iter = std::make_unique<CustomIterator>(list); // 自动释放资源 ``` - **C++20概念约束**(用户提供引用[^1]): ```cpp template<typename T> concept Iterable = requires(T t) { { t.begin() } -> std::input_iterator; { t.end() } -> std::sentinel_for<decltype(t.begin())>; }; ``` --- #### **五、迭代器高级应用** 1. **流迭代器** - 输入流迭代器(用户提供引用): ```cpp std::istream_iterator<int> input_it(std::cin), end; while (input_it != end) { std::cout << *input_it++ << " "; } ``` 2. **协程异步迭代器**(用户提供引用[^1]) 结合 C++20 协程实现异步遍历(如从网络流中按需获取数据): ```cpp generator<int> async_fetch() { while (auto data = co_await fetch_next()) { co_yield data; } } ``` --- #### **六、注意事项** 1. **迭代器失效** - 修改容器(如 `vector` 扩容)可能导致迭代器失效。 - 示例:向 `vector` 插入元素后,原有迭代器可能指向已释放内存。 2. **性能差异** - 随机访问迭代器(如 `vector`)比双向迭代器(如 `list`)更适合高频访问。 3. **范围校验** - 解引用 `end()` 迭代器会导致未定义行为(UB)。 --- ### **代码示例:自定义迭代器** ```cpp #include <iostream> #include <algorithm> class Range { int start, end; public: Range(int s, int e) : start(s), end(e) {} class Iterator { int current; public: Iterator(int c) : current(c) {} int operator*() const { return current; } Iterator& operator++() { ++current; return *this; } bool operator!=(const Iterator& other) const { return current != other.current; } }; Iterator begin() const { return Iterator(start); } Iterator end() const { return Iterator(end + 1); } }; int main() { Range r(10, 15); std::for_each(r.begin(), r.end(), [](int n) { std::cout << n << " "; // 输出 10 11 12 13 14 15 }); } ``` --- ### **相关问题** 1. 如何避免迭代器失效导致的程序崩溃? 2. C++20 的 `ranges` 库如何简化迭代器操作? 3. 输入迭代器前向迭代器的核心区别是什么? 4. 如何为自定义容器实现符合 STL 标准的迭代器? --- ### **引用说明** : 现代 C++ 通过 RAII 和概念约束提升迭代器的安全性表达力。 [^2]: 迭代器的相等性比较是遍历结束条件判断的基础。 [^3]: STL 算法迭代器的配合体现了泛型编程的核心思想。 [^4]: 流迭代器扩展了迭代器在 I/O 操作中的应用场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值