C++ 自定义迭代器
什么是迭代器?
迭代器是C++中一种重要的工具,它提供了一种访问容器中元素的统一方式,使我们能够遍历容器内的元素而无需了解容器的底层实现细节。在STL(标准模板库)中,虽然已经提供了丰富的迭代器实现,但有时我们需要为自定义的数据结构实现迭代器,以便能够像使用标准容器一样方便地操作我们的数据。
为什么需要自定义迭代器?
自定义迭代器的主要目的包括:
- 为自定义容器提供标准的访问接口
- 允许自定义容器使用STL算法
- 实现特定的遍历方式或访问策略
- 提供额外的功能,如过滤、转换等
迭代器的类型
在开始创建自定义迭代器之前,让我们了解迭代器的几种主要类型:
- 输入迭代器:只能向前读取元素一次
- 输出迭代器:只能向前写入元素一次
- 前向迭代器:可以多次读取元素
- 双向迭代器:可以前后移动
- 随机访问迭代器:可以随机访问任何元素
不同类型的迭代器需要实现不同的操作符。随着从输入迭代器到随机访问迭代器的顺序,所需实现的操作符越来越多。
创建自定义迭代器的基本步骤
创建一个自定义迭代器通常需要以下几个步骤:
- 定义迭代器类并包含必要的类型定义
- 实现构造函数和析构函数
- 实现解引用操作符(
*
)和箭头操作符(->
) - 实现递增操作符(
++
) - 实现比较操作符(
==
和!=
) - 如果是双向迭代器,还需实现递减操作符(
--
) - 如果是随机访问迭代器,还需实现加减和关系比较操作符
实现一个简单的前向迭代器
让我们通过一个简单的例子来说明如何实现一个前向迭代器。假设我们有一个自定义的链表类MyLinkedList
,我们将为它实现一个迭代器。
#include <iostream>
#include <iterator>
#include <cstddef>
// 定义链表节点
template <typename T>
struct Node {
T data;
Node* next;
Node(const T& value) : data(value), next(nullptr) {}
};
// 自定义链表类
template <typename T>
class MyLinkedList {
private:
Node<T>* head;
Node<T>* tail;
size_t size;
public:
// 前向声明迭代器类
class Iterator;
MyLinkedList() : head(nullptr), tail(nullptr), size(0) {}
~MyLinkedList() {
Node<T>* current = head;
while (current) {
Node<T>* next = current->next;
delete current;
current = next;
}
}
// 在链表尾部添加元素
void push_back(const T& value) {
Node<T>* newNode = new Node<T>(value);
if (!head) {
head = newNode;
tail = newNode;
} else {
tail->next = newNode;
tail = newNode;
}
size++;
}
// 返回开始迭代器
Iterator begin() {
return Iterator(head);
}
// 返回结束迭代器
Iterator end() {
return Iterator(nullptr);
}
// 迭代器类定义
class Iterator {
private:
Node<T>* current;
public:
// 迭代器必要的类型定义
using iterator_category = std::forward_iterator_tag;
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = T*;
using reference = T&;
// 构造函数
Iterator(Node<T>* ptr) : current(ptr) {}
// 解引用操作符
reference operator*() const {
return current->data;
}
// 箭头操作符
pointer operator->() const {
return &(current->data);
}
// 前缀递增操作符
Iterator& operator++() {
current = current->next;
return *this;
}
// 后缀递增操作符
Iterator operator++(int) {
Iterator temp = *this;
current = current->next;
return temp;
}
// 相等比较操作符
bool operator==(const Iterator& other) const {
return current == other.current;
}
// 不等比较操作符
bool operator!=(const Iterator& other) const {
return current != other.current;
}
};
};
使用我们的自定义迭代器
现在让我们看一个使用这个自定义迭代器的例子:
int main() {
MyLinkedList<int> myList;
// 添加一些元素
myList.push_back(10);
myList.push_back(20);
myList.push_back(30);
myList.push_back(40);
// 使用迭代器遍历链表
std::cout << "链表中的元素:";
for (auto it = myList.begin(); it != myList.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 使用范围for循环(C++11及以上)
std::cout << "使用范围for循环:";
for (const auto& value : myList) {
std::cout << value << " ";
}
std::cout << std::endl;
return 0;
}
输出结果:
链表中的元素:10 20 30 40
使用范围for循环:10 20 30 40
更进一步:实现双向迭代器
如果我们想实现一个双向迭代器,需要添加向后移动的能力。以下是如何扩展我们的迭代器类来支持双向操作:
// 假设我们有一个双向链表节点
template <typename T>
struct BiNode {
T data;
BiNode* next;
BiNode* prev;
BiNode(const T& value) : data(value), next(nullptr), prev(nullptr) {}
};
// 双向链表的迭代器
template <typename T>
class BidirectionalIterator {
private:
BiNode<T>* current;
public:
// 迭代器必要的类型定义
using iterator_category = std::bidirectional_iterator_tag;
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = T*;
using reference = T&;
// 构造函数
BidirectionalIterator(BiNode<T>* ptr) : current(ptr) {}
// 解引用和箭头操作符
reference operator*() const { return current->data; }
pointer operator->() const { return &(current->data); }
// 前缀递增和递减
BidirectionalIterator& operator++() {
current = current->next;
return *this;
}
BidirectionalIterator& operator--() {
current = current->prev;
return *this;
}
// 后缀递增和递减
BidirectionalIterator operator++(int) {
BidirectionalIterator temp = *this;
current = current->next;
return temp;
}
BidirectionalIterator operator--(int) {
BidirectionalIterator temp = *this;
current = current->prev;
return temp;
}
// 比较操作符
bool operator==(const BidirectionalIterator& other) const {
return current == other.current;
}
bool operator!=(const BidirectionalIterator& other) const {
return current != other.current;
}
};
实际应用案例:过滤迭代器
有时我们需要在遍历容器时应用某种过滤条件。下面是一个过滤迭代器的例子,它只会返回满足特定条件的元素:
#include <iostream>
#include <vector>
#include <functional>
template <typename Container, typename Predicate>
class FilterIterator {
private:
typename Container::const_iterator current;
typename Container::const_iterator end;
Predicate predicate;
// 找到下一个满足条件的元素
void find_next_valid() {
while (current != end && !predicate(*current)) {
++current;
}
}
public:
using iterator_category = std::forward_iterator_tag;
using value_type = typename Container::value_type;
using difference_type = std::ptrdiff_t;
using pointer = const value_type*;
using reference = const value_type&;
FilterIterator(typename Container::const_iterator start,
typename Container::const_iterator end,
Predicate pred)
: current(start), end(end), predicate(pred) {
find_next_valid();
}
reference operator*() const {
return *current;
}
pointer operator->() const {
return &(*current);
}
FilterIterator& operator++() {
++current;
find_next_valid();
return *this;
}
FilterIterator operator++(int) {
FilterIterator temp = *this;
++(*this);
return temp;
}
bool operator==(const FilterIterator& other) const {
return current == other.current;
}
bool operator!=(const FilterIterator& other) const {
return current != other.current;
}
};
// 辅助函数,用于创建过滤迭代器
template <typename Container, typename Predicate>
class FilterView {
private:
const Container& container;
Predicate predicate;
public:
FilterView(const Container& cont, Predicate pred)
: container(cont), predicate(pred) {}
FilterIterator<Container, Predicate> begin() const {
return FilterIterator<Container, Predicate>(
container.begin(), container.end(), predicate);
}
FilterIterator<Container, Predicate> end() const {
return FilterIterator<Container, Predicate>(
container.end(), container.end(), predicate);
}
};
template <typename Container, typename Predicate>
FilterView<Container, Predicate> filter(const Container& container, Predicate pred) {
return FilterView<Container, Predicate>(container, pred);
}
使用这个过滤迭代器的例子:
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 使用过滤迭代器只遍历偶数
auto isEven = [](int n) { return n % 2 == 0; };
std::cout << "偶数:";
for (const auto& num : filter(numbers, isEven)) {
std::cout << num << " ";
}
std::cout << std::endl;
// 使用过滤迭代器只遍历大于5的数
auto greaterThan5 = [](int n) { return n > 5; };
std::cout << "大于5的数:";
for (const auto& num : filter(numbers, greaterThan5)) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
输出结果:
偶数:2 4 6 8 10
大于5的数:6 7 8 9 10
迭代器的特性标记
为了让STL算法能够正确使用我们的自定义迭代器,我们需要定义几个特性标记类型。这些类型可以告诉STL算法我们的迭代器支持什么操作:
// 迭代器必要的类型定义
using iterator_category = std::forward_iterator_tag; // 或其他适当的标记
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = T*;
using reference = T&;
不同的迭代器类别对应不同的标记:
std::input_iterator_tag
- 输入迭代器std::output_iterator_tag
- 输出迭代器std::forward_iterator_tag
- 前向迭代器std::bidirectional_iterator_tag
- 双向迭代器std::random_access_iterator_tag
- 随机访问迭代器
C++ 20:概念和约束
C++20引入了概念(Concepts),这使得定义迭代器要求更加简单和明确。以下是使用C++20概念的迭代器示例:
#include <concepts>
#include <iterator>
template <typename T>
class MyIterator {
private:
// 迭代器实现...
public:
// 使用C++20的概念定义
using iterator_concept = std::forward_iterator_tag;
// 其他类型定义...
// 迭代器实现...
};
C++20的概念使得编译器能够提供更好的错误消息,帮助你确保你的迭代器满足所需的要求。
总结
自定义迭代器是C++编程中一项强大的技术,它可以让我们为自定义的数据结构提供与STL容器相同的访问接口,从而实现更加通用和可复用的代码。通过实现适当的操作符,我们可以创建不同类型的迭代器,以满足不同的需求。
本文中我们学习了:
- 迭代器的基本概念和类型
- 如何实现一个简单的前向迭代器
- 如何实现双向迭代器
- 如何创建具有特殊功能的迭代器(如过滤迭代器)
- C++20中的迭代器概念
练习
- 实现一个随机访问迭代器,用于自定义的动态数组类。
- 创建一个转换迭代器,可以在遍历容器时对元素进行某种转换(如将整数转换为字符串)。
- 实现一个逆向迭代器适配器,可以反向遍历任何提供双向迭代器的容器。
- 为树形结构(如二叉树)实现一个中序遍历迭代器。
- 使用C++20的概念重构本文中的某个迭代器示例。
进一步阅读
- C++ Reference: Iterator Library
- C++20 Concepts
- Effective STL by Scott Meyers
- The C++ Standard Library by Nicolai M. Josuttis
通过掌握自定义迭代器的创建,你将能够更好地利用STL算法,同时为自己的数据结构提供标准化的访问接口,大大提升代码的可读性和复用性。