C++ 迭代器详解(2):迭代器适配器 迭代器失效及自定义迭代器

迭代器适配器(Iterator Adapters)

迭代器适配器(Iterator Adapters)是C++标准库中的一类工具,用于对现有的迭代器进行包装和修改,以提供更灵活的迭代方式。通过这些适配器,可以轻松地将标准迭代器转换为特定需求的迭代器,避免在使用过程中手动处理迭代器行为。常见的迭代器适配器主要包括以下几种:

std::reverse_iterator

std::reverse_iterator用于将一个正向迭代器转换为反向迭代器。通过该适配器,你可以从容器的末尾向前迭代,而不是从头到尾。

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    std::reverse_iterator<std::vector<int>::iterator> rbegin(vec.end());
    std::reverse_iterator<std::vector<int>::iterator> rend(vec.begin());
    
    for (auto it = rbegin; it != rend; ++it) {
        std::cout << *it << " ";
    }
    // Output: 5 4 3 2 1
}

使用方法:
可以通过rbegin()和rend()成员函数快速生成容器的反向迭代器。例如:vec.rbegin()和vec.rend()直接返回反向迭代器。

std::back_insert_iterator

std::back_insert_iterator适配器用于向容器的末尾插入元素。常与std::back_inserter函数配合使用,通过此适配器,算法可以在操作时自动向容器尾部添加元素,而不需要关心容器大小是否合适。

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> vec = {1, 2, 3};
    std::vector<int> additional = {4, 5, 6};

    std::copy(additional.begin(), additional.end(), std::back_inserter(vec));

    for (int n : vec) {
        std::cout << n << " ";
    }
    // Output: 1 2 3 4 5 6
}

使用方法

std::back_inserter是一个辅助函数,用来生成一个back_insert_iterator,用法如std::back_inserter(vec)。

std::front_insert_iterator

std::front_insert_iterator与back_insert_iterator类似,但它用于从容器的前端插入元素。常用于支持前插入的容器,如std::deque或std::list。适配器和std::front_inserter函数一起使用。

#include <iostream>
#include <list>
#include <algorithm>

int main() {
    std::list<int> lst = {1, 2, 3};
    std::vector<int> additional = {4, 5, 6};

    std::copy(additional.begin(), additional.end(), std::front_inserter(lst));

    for (int n : lst) {
        std::cout << n << " ";
    }
    // Output: 6 5 4 1 2 3
}

使用方法

std::front_inserter(lst)可以生成一个front_insert_iterator。

std::insert_iterator

std::insert_iterator用于在指定位置插入元素。不同于back_insert_iterator和front_insert_iterator,它允许插入到容器的任意位置。该适配器常与std::inserter辅助函数配合使用。

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> vec = {1, 2, 3};
    std::vector<int> additional = {4, 5, 6};

    std::copy(additional.begin(), additional.end(), std::inserter(vec, vec.begin() + 1));

    for (int n : vec) {
        std::cout << n << " ";
    }
    // Output: 1 4 5 6 2 3
}

使用方法

使用std::inserter(vec, position)生成insert_iterator,其中position表示插入位置。

std::istream_iterator 和 std::ostream_iterator

std::istream_iterator适配器用于将输入流(如std::cin)与算法连接,便于从流中读取数据。

std::ostream_iterator则将输出流(如std::cout)与算法连接,便于向流中输出数据。

#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};

    std::copy(vec.begin(), vec.end(), std::ostream_iterator<int>(std::cout, " "));
    // Output: 1 2 3 4 5 
}

使用方法

创建时指定流对象(如std::cout)以及分隔符(如" ")即可。

小结

迭代器适配器在STL算法中有广泛应用,通过这些适配器,可以实现更灵活的迭代操作,如反向迭代、前后插入、流输入输出等。了解并掌握这些适配器可以显著简化代码,提高代码的通用性和可读性。

迭代器适配器适用容器容器要求说明
std::reverse_iteratorstd::vector, std::deque, std::list, std::array必须支持双向迭代(Bidirectional Iterator)使容器反向遍历,std::forward_list不支持双向迭代,不能使用反向迭代器。
std::back_insert_iteratorstd::vector, std::deque, std::list必须支持push_back用于在容器尾部插入元素。std::arraystd::forward_list不支持push_back
std::front_insert_iteratorstd::deque, std::list, std::forward_list必须支持push_front用于在容器头部插入元素。std::vectorstd::array不支持push_front
std::insert_iteratorstd::vector, std::deque, std::list, std::set, std::map必须支持insert在指定位置插入元素,适用于大多数支持插入的容器。
std::move_iterator所有标准容器只需支持解引用(Dereferenceable)以移动语义访问元素,适用于所有标准容器。

迭代器的失效

迭代器的失效是指一个迭代器变得无效,不能再用于访问容器中的元素。迭代器失效通常发生在容器被修改(例如添加或删除元素)时,特别是在容器的结构发生变化时。

迭代器失效的原因

  1. 容器大小变化(如插入和删除)
    插入元素:当你在容器中间或尾部插入元素时,通常会导致容器的重新分配。如果容器是std::vector这类动态数组容器,插入元素可能会导致内部数组重新分配,所有指向原数组元素的迭代器都将失效。
    删除元素:如果删除容器中的元素,原本指向已删除元素的迭代器会失效。同样,删除操作可能使得迭代器指向的容器位置发生变化,特别是对于std::list和std::deque这样的容器。

  2. 容器的重新分配(通常发生在std::vector
    std::vector的容量不足以容纳新的元素时,它会重新分配内存并将元素移动到新的位置。这会导致原来指向容器元素的所有迭代器失效。插入操作可能会触发这种重新分配。

  3. 容器的缩减(例如删除所有元素)
    当容器的所有元素都被删除时,容器的begin()end()迭代器会失效,或者指向的元素会变为空。

迭代器失效的规则

C++标准库对不同容器在执行插入、删除等操作时,对迭代器的失效有不同的规则。以下是几种常见容器的迭代器失效规则:

  1. std::vector:由于std::vector是一个动态数组,当容器增长时,它可能会重新分配内存,导致原有的迭代器失效。如果你在使用迭代器时频繁插入或删除元素,应当避免插入操作导致的大规模内存重分配,或者在插入前先预留足够的空间。

  2. std::dequestd::deque是一个双端队列,插入元素时,头部和尾部的插入可能导致某些迭代器失效,但不像std::vector那样对所有迭代器造成影响。对于中间插入,std::deque通常不会导致迭代器失效。

  3. std::liststd::list是一个双向链表,插入和删除操作不会导致其他位置的迭代器失效。但是,如果删除的元素是迭代器所指向的元素,则该迭代器会失效。

  4. std::setstd::map:这些容器使用红黑树实现,插入和删除操作不会导致所有迭代器失效,但删除的元素对应的迭代器会失效。如果删除的是当前迭代器指向的元素,那么该迭代器将变得无效。

  5. std::forward_list:与std::list类似,但std::forward_list是单向链表。它的插入操作不会使其他位置的迭代器失效,删除操作仅会使指向已删除元素的迭代器失效。

迭代器失效的处理方式

  1. 避免在迭代过程中修改容器大小:尽量避免在遍历容器时进行插入和删除操作,特别是对于std::vectorstd::deque等动态数组容器。如果确实需要修改容器,应考虑先进行所有插入和删除操作,再进行遍历。
    示例:
#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    
    // 先遍历容器
    for (auto it = vec.begin(); it != vec.end(); ++it) {
        std::cout << *it << " ";  // 输出:1 2 3 4 5
    }
    
    // 然后再进行修改(删除操作)
    vec.push_back(6);  // 插入新元素
    vec.erase(vec.begin());  // 删除第一个元素
    
    // 遍历修改后的容器
    for (auto it = vec.begin(); it != vec.end(); ++it) {
        std::cout << *it << " ";  // 输出:2 3 4 5 6
    }
}
  1. 使用std::advance:在某些情况下,可以通过std::advance来避免手动管理迭代器,尤其是在删除元素时,std::advance可以帮助你正确地更新迭代器。
    示例1:
#include <list>
#include <iostream>
#include <iterator>

int main() {
    std::list<int> lst = {1, 2, 3, 4, 5};
    auto it = lst.begin();
    
    // 删除过程中通过保存下一个元素的迭代器来避免失效
    while (it != lst.end()) {
        auto next_it = std::next(it);  // 获取下一个元素的迭代器
        if (*it == 3) {
            lst.erase(it);  // 删除元素
        }
        it = next_it;  // 更新迭代器到下一个元素
    }
    
    // 打印修改后的列表
    for (const auto& num : lst) {
        std::cout << num << " ";  // 输出:1 2 4 5
    }
}

示例2:

#include <iostream>
#include <list>
#include <iterator> // std::advance

int main() {
    std::list<int> lst = {1, 2, 3, 4, 5, 6, 7, 8};

    auto it = lst.begin();
    while (it != lst.end()) {
        if (*it % 2 == 0) {  // 如果元素是偶数
            it = lst.erase(it);  // 删除当前元素,并返回删除元素后的位置
        } else {
            std::advance(it, 1);  // 使用std::advance将迭代器移动到下一个元素
        }
    }

    // 打印修改后的列表
    for (const auto& num : lst) {
        std::cout << num << " ";  // 输出:1 3 5 7
    }
}
  1. 在删除操作后更新迭代器:如果在容器中删除元素,删除后应当重新获取有效的迭代器,特别是当迭代器指向已删除元素时。

示例:

#include <vector>
#include <iostream>
#include <algorithm>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    for (auto it = vec.begin(); it != vec.end(); /*没有++it*/) {
        if (*it == 3) {
            it = vec.erase(it);  // 删除元素并更新迭代器
        } else {
            ++it;
        }
    }
    
    // 打印修改后的容器
    for (auto value : vec) {
        std::cout << value << " ";  // 输出:1 2 4 5
    }
}
  1. 避免依赖失效的迭代器:如果你需要在遍历过程中删除容器中的元素,可以选择先将要删除的元素标记出来,最后在一次遍历结束后统一进行删除,或者使用erase-remove惯用法。
    示例:
#include <vector>
#include <iostream>
#include <algorithm>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 3, 5};
    
    // 使用erase-remove惯用法删除所有值为3的元素
    vec.erase(std::remove(vec.begin(), vec.end(), 3), vec.end());
    
    // 打印修改后的容器
    for (auto value : vec) {
        std::cout << value << " ";  // 输出:1 2 4 5
    }
}
  1. 容器预分配空间:对于std::vector等容器,如果需要频繁插入元素,可以考虑使用reserve()来预分配足够的空间,避免由于容器重新分配内存而导致的迭代器失效。
    示例:
#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec;
    vec.reserve(10);  // 预分配足够的空间以避免重新分配内存

    // 插入元素,不会导致重新分配
    for (int i = 1; i <= 10; ++i) {
        vec.push_back(i);
    }
    
    // 打印容器中的元素
    for (auto value : vec) {
        std::cout << value << " ";  // 输出:1 2 3 4 5 6 7 8 9 10
    }
}

小结

迭代器失效是C++容器中常见的一个问题,尤其是在进行插入和删除操作时。不同容器对迭代器的失效有不同的规则,理解这些规则可以帮助开发者避免潜在的错误。在处理迭代器失效时,最好的做法是尽量避免在遍历过程中修改容器,或者确保在修改容器后重新获得有效的迭代器。

自定义迭代器

自定义迭代器在C++中是指用户定义的特殊迭代器类型,用来满足自定义数据结构或算法的需求。通过自定义迭代器,可以实现对非标准容器的数据访问,以符合C++标准库的迭代器接口,使其在标准算法中使用。

自定义迭代器主要由以下几个关键概念组成:

  1. 迭代器类型:定义迭代器所实现的行为类型,如输入迭代器、输出迭代器、前向迭代器、双向迭代器和随机访问迭代器。

  2. 标准迭代器接口:需要提供特定的操作符支持,如解引用(operator*)、递增(operator++)、相等性比较(operator==)等。

  3. 迭代器适配器:迭代器适配器可用于对已有的迭代器进行包装,为它们添加额外的行为。

自定义迭代器的组成

在C++中,自定义迭代器主要包含以下几个必要的成员:
类型定义(type aliases):定义iterator_categoryvalue_typedifference_typepointerreference等类型。
构造函数:用于初始化迭代器。
解引用操作符(operator*:返回迭代器当前指向的元素。
递增和递减操作符:前置递增(operator++)和后置递增(operator++(int))。对于双向迭代器和更高级别的迭代器,通常还需要支持前置和后置递减(operator--operator--(int))。
相等和不相等比较(operator==operator!=:用于比较两个迭代器是否相等。

自定义迭代器的实现步骤

  1. 定义基本迭代器类型

首先,定义迭代器类别,如输入迭代器、双向迭代器等,这决定了它能支持的操作。例如,输入迭代器允许读取元素,但不一定支持修改或双向遍历。以下是一些常见的迭代器类别:

std::input_iterator_tag //输入迭代器

std::output_iterator_tag//输出迭代器

std::forward_iterator_tag//前向迭代器

std::bidirectional_iterator_tag//双向迭代器

std::random_access_iterator_tag//随机访问迭代器
  1. 自定义一个简单的前向迭代器

以下代码实现了一个简单的前向迭代器,适用于遍历一个整数数组。

#include <iterator>

class SimpleIterator {
public:
    using iterator_category = std::forward_iterator_tag;
    using value_type = int;
    using difference_type = std::ptrdiff_t;
    using pointer = int*;
    using reference = int&;

    // 构造函数
    SimpleIterator(pointer ptr) : m_ptr(ptr) {}

    // 解引用操作符,获取当前元素
    reference operator*() const {
        return *m_ptr;
    }

    // 前置递增操作符,返回自增后的迭代器
    SimpleIterator& operator++() {
        ++m_ptr;
        return *this;
    }

    // 后置递增操作符,返回自增前的迭代器
    SimpleIterator operator++(int) {
        SimpleIterator temp = *this;
        ++(*this);  // 调用前置递增
        return temp;
    }

    // 相等性比较操作符
    bool operator==(const SimpleIterator& other) const {
        return m_ptr == other.m_ptr;
    }

    // 不等性比较操作符
    bool operator!=(const SimpleIterator& other) const {
        return m_ptr != other.m_ptr;
    }

private:
    pointer m_ptr;  // 指向当前元素的指针
};

代码解析

  1. 类型定义:iterator_category指定为std::forward_iterator_tag,表示它是一个前向迭代器。另外定义了迭代器的数据类型、指针类型、引用类型等。
  2. 构造函数:构造函数接收一个指针,用于初始化迭代器。
  3. 解引用操作符:operator*返回当前元素的引用,可以用于访问元素值。
  4. 前置递增操作符:operator++将迭代器移动到下一个元素,返回自增后的迭代器自身。
  5. 后置递增操作符:operator++(int)实现后置递增操作,返回自增前的迭代器值。
  6. 相等和不等比较操作符:operator==和operator!=用于比较两个迭代器是否指向相同的元素。

使用示例
下面是如何使用这个SimpleIterator的示例代码:

#include <iostream>

int main() {
    int arr[] = {1, 2, 3, 4, 5};
    SimpleIterator begin(arr);
    SimpleIterator end(arr + 5);

    for (SimpleIterator it = begin; it != end; ++it) {
        std::cout << *it << " ";  // 输出:1 2 3 4 5
    }

    return 0;
}

处理更复杂的迭代器类型

如果想要实现更复杂的迭代器类型(如双向迭代器、随机访问迭代器),需要进一步支持操作符,如:
双向迭代器:除了++,还要实现--操作符(前置和后置)。
随机访问迭代器:需要实现加法(operator+)、减法(operator-)、下标访问(operator[])、比较操作符(<, <=, >, >=),以支持随机访问和更高效的遍历。

自定义迭代器的实际应用

  1. 自定义容器的迭代器:实现特定数据结构的迭代器(如树、图),使它们可以与标准库算法兼容。
  2. 包装现有迭代器:使用迭代器适配器模式增强已有迭代器的功能(如加密、解密数据流,过滤器等)。
  3. 特殊用途的迭代器:如生成器迭代器(生成一系列值)、转换迭代器(转换值)等。

小结

自定义迭代器通过实现标准的迭代器接口,使自定义数据结构或特殊数据流能够与C++标准算法库兼容,实现灵活的元素访问和遍历。同时根据迭代器类型的不同,自定义迭代器可以支持不同的操作和行为。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值