在C++17及以后的标准中,范围基for循环(range‑based for loop)已经成为遍历容器和自定义数据结构的最直观方式。它的语法看似简单,但背后涉及了迭代器协议、容器适配器以及类型推断等一系列重要概念。本文将从理论与实践两个角度,详细拆解范围基for循环的实现原理,并演示如何为自定义容器实现符合规范的迭代器,从而让自己的类型也能轻松参与范围遍历。
1. 范围基for循环的语法
for (range_declaration : range_expression) {
statement
}
range_declaration:通常是auto&、const auto&或具体类型,决定了遍历时元素的引用方式。range_expression:任意可被begin()与end()函数识别的表达式。C++20 引入std::ranges后,还可以直接接受std::views、std::ranges::subrange等。
核心的实现思路就是:
auto&& __range = range_expression;
auto __begin = std::begin(__range);
auto __end = std::end(__range);
for (; __begin != __end; ++__begin) {
auto&& __elem = *__begin;
// user statement
}
这段伪代码与真正的编译器实现高度一致,关键点在于 std::begin 与 std::end 的使用,而这两者背后正是 迭代器协议 的体现。
2. 迭代器协议概览
迭代器本质上是一个封装了“指向容器元素的指针”与“访问、移动操作”的对象。标准分为多种类别:
| 类别 | 需求 | 典型实现 |
|---|---|---|
| InputIterator | *it, ++it, it != it |
std::istream_iterator |
| ForwardIterator | 以上 + 复制 | std::forward_list::iterator |
| BidirectionalIterator | 以上 + --it |
std::list::iterator |
| RandomAccessIterator | 以上 + it + n, it[n] |
std::vector::iterator |
C++标准要求 std::begin 与 std::end 能够为容器返回相应类别的迭代器。对于自定义类型,只需满足以下两条即可:
- 具备
begin()与end()成员函数或全局函数模板(可以用 ADL)。 - 返回的迭代器类型支持
operator*()、operator++()、operator!=(),并满足相应类别的其他运算。
3. 为自定义容器实现迭代器
下面以一个简单的链表 SimpleList 为例,演示如何实现迭代器,并让其能在范围基for循环中使用。
#include <iostream>
#include <iterator>
#include <memory>
template <typename T>
class SimpleList {
struct Node {
T data;
std::unique_ptr <Node> next;
Node(T val) : data(std::move(val)), next(nullptr) {}
};
std::unique_ptr <Node> head;
size_t sz = 0;
public:
SimpleList() = default;
void push_front(T val) {
auto newNode = std::make_unique <Node>(std::move(val));
newNode->next = std::move(head);
head = std::move(newNode);
++sz;
}
size_t size() const noexcept { return sz; }
// 迭代器的声明
class Iterator {
Node* ptr;
public:
using iterator_category = std::forward_iterator_tag;
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = T*;
using reference = T&;
explicit Iterator(Node* n = nullptr) : ptr(n) {}
reference operator*() const { return ptr->data; }
pointer operator->() const { return &(ptr->data); }
Iterator& operator++() { ptr = ptr->next.get(); return *this; }
Iterator operator++(int) { Iterator tmp(*this); ++(*this); return tmp; }
bool operator==(const Iterator& other) const { return ptr == other.ptr; }
bool operator!=(const Iterator& other) const { return !(*this == other); }
};
Iterator begin() noexcept { return Iterator(head.get()); }
Iterator end() noexcept { return Iterator(nullptr); }
};
3.1 关键点说明
- 节点管理:使用
std::unique_ptr简化内存管理,保证链表析构时自动释放。 - Iterator 类型:实现了
forward_iterator_tag,满足 ForwardIterator 的最小要求。若想支持--或随机访问,可进一步实现相应运算。 - *operator 与 operator++**:核心实现。注意
operator++()必须返回引用,以支持链式递增。 - begin / end:返回
Iterator对象,指向头节点和nullptr。
3.2 使用示例
int main() {
SimpleList <int> lst;
lst.push_front(1);
lst.push_front(2);
lst.push_front(3); // 3 -> 2 -> 1
for (const auto& val : lst) {
std::cout << val << ' ';
}
// 输出:3 2 1
}
4. 更高级的迭代器适配
4.1 自定义视图(View)
C++20 的 std::ranges 允许我们在不改变容器的情况下,创建视图(view)——一种对已有数据结构的“轻量包装”,并提供了更丰富的操作。例如,使用 std::views::filter 对链表中的偶数进行过滤:
#include <ranges>
#include <algorithm>
int main() {
SimpleList <int> lst;
for (int i = 1; i <= 10; ++i) lst.push_front(i);
auto even_view = lst | std::views::filter([](int x){ return x % 2 == 0; });
for (int x : even_view) std::cout << x << ' ';
}
这段代码在底层会使用 begin() 与 end(),并通过适配器生成新的迭代器,确保了 链式迭代 的可行性。
4.2 反向迭代
如果想让自定义容器支持 rbegin() / rend(),只需在容器中提供相应的成员或全局函数即可。迭代器需要实现 operator--() 并返回 reverse_iterator,从而满足 BidirectionalIterator。例如,链表的反向迭代可以通过维护尾指针实现,或者使用 std::reverse_iterator 包装正向迭代器。
5. 性能与安全注意
- 迭代器失效:在遍历过程中修改容器结构(插入/删除节点)会导致迭代器失效,产生未定义行为。为避免此类错误,可使用
std::vector或std::list(提供稳定迭代器)或在自定义容器中显式声明失效规则。 - const 与非 const:实现
begin()与end()的 const 版本,以支持const容器的范围遍历。 - 引用与值:在范围遍历中使用
auto&&可以自动根据元素类型决定引用或移动,减少拷贝开销。
6. 小结
- 范围基for循环依赖
std::begin与std::end,这两者进一步调用迭代器对象。 - 自定义容器只要满足迭代器协议,即可与范围基for无缝结合。
- C++20 的
std::ranges让迭代器适配更为灵活,支持视图、过滤、映射等高级操作。 - 在实现迭代器时,关注
iterator_category与所需操作,确保代码可维护且安全。
掌握上述概念后,你就能为自己的任何数据结构编写出高效、可读、可与标准库交互的迭代器,真正实现“C++ 迭代器无死角”。