C++20 对范围基 for 循环(range‑based for)做了重要改进,使得在处理容器和自定义视图时更加灵活和安全。本文将从语法、编译器实现、范围适配器和实际案例四个方面,系统阐述 C++20 版本的特点,并给出常见使用场景的代码示例,帮助你在项目中更好地运用这一特性。
1. 语法与基本思路
传统的范围基 for 循环语法为:
for (auto&& element : container) {
// ...
}
其本质相当于:
auto&& __range = container;
for (auto __begin = std::begin(__range), __end = std::end(__range);
__begin != __end; ++__begin) {
auto&& element = *__begin;
// ...
}
C++20 在此基础上引入了 范围适配器(range adapters)与 概念(concepts),让 std::begin 与 std::end 的选择变得更加灵活,并支持更丰富的自定义视图(如 std::ranges::view)。
2. 编译器实现优化
C++20 采用了 RVO(返回值优化)和 guaranteed copy elision,结合 range view 的延迟求值特性,编译器能够在循环内部实现惰性求值,避免不必要的拷贝与迭代器状态更新。
- 惰性求值:视图(view)在真正需要时才会产生元素,例如
std::views::filter仅在迭代时检查条件。 - 短路:在
for循环中,如果视图的前缀已满足停止条件,后续元素不再生成。
3. 主要范围适配器
| 适配器 | 作用 | 示例 |
|---|---|---|
std::views::filter |
过滤 | auto even = vec | std::views::filter([](int n){return n%2==0;}); |
std::views::transform |
转换 | auto square = vec | std::views::transform([](int n){return n*n;}); |
std::views::take |
截取前 N 个 | auto first3 = vec | std::views::take(3); |
std::views::drop |
跳过前 N 个 | auto skip5 = vec | std::views::drop(5); |
std::views::reverse |
反向 | auto rev = vec | std::views::reverse; |
std::views::concat |
连接 | auto concat = vec1 | std::views::concat(vec2); |
使用这些适配器可以以链式调用的方式构造复杂的视图,代码简洁且易于维护。
4. 自定义视图的实现
若需要自定义范围适配器,可通过实现 begin()、end() 以及可选的 size() 等函数,满足 std::ranges::range 约束即可。示例:
template<class Iterator>
class my_view {
public:
my_view(Iterator first, Iterator last) : first_(first), last_(last) {}
Iterator begin() const { return first_; }
Iterator end() const { return last_; }
private:
Iterator first_, last_;
};
auto vec = std::vector <int>{1,2,3,4,5};
my_view view(vec.begin(), vec.end());
for (int v : view) { std::cout << v << ' '; }
5. 实际案例
5.1 过滤并平方大于10的元素
#include <iostream>
#include <vector>
#include <ranges>
int main() {
std::vector <int> numbers{1, 3, 5, 7, 9, 11, 13};
auto processed = numbers
| std::views::filter([](int n){ return n > 5; })
| std::views::transform([](int n){ return n * n; });
for (auto val : processed) {
std::cout << val << ' ';
}
// 输出: 49 121 169
}
5.2 在二维数组中寻找最大值
#include <iostream>
#include <ranges>
#include <array>
int main() {
std::array<std::array<int, 3>, 3> mat{{
{1, 5, 9},
{2, 6, 8},
{3, 4, 7}
}};
auto rows = mat | std::views::transform([](auto& row){ return std::ranges::max(row); });
auto max_val = std::ranges::max(rows);
std::cout << "最大值: " << max_val << '\n'; // 输出 9
}
5.3 用 for 循环遍历自定义双向迭代器
#include <iostream>
#include <iterator>
class MyContainer {
public:
struct Iterator {
using iterator_category = std::bidirectional_iterator_tag;
using value_type = int;
using difference_type = std::ptrdiff_t;
using pointer = int*;
using reference = int&;
Iterator(int* ptr) : ptr_(ptr) {}
int& operator*() const { return *ptr_; }
Iterator& operator++() { ++ptr_; return *this; }
Iterator& operator--() { --ptr_; return *this; }
bool operator!=(const Iterator& other) const { return ptr_ != other.ptr_; }
private:
int* ptr_;
};
Iterator begin() { return Iterator(data_); }
Iterator end() { return Iterator(data_ + size_); }
private:
int data_[5] = {10, 20, 30, 40, 50};
static constexpr std::size_t size_ = 5;
};
int main() {
MyContainer c;
for (auto v : c) {
std::cout << v << ' ';
}
// 输出: 10 20 30 40 50
}
6. 性能考虑
- 惰性求值:链式适配器不产生临时容器,降低内存占用。
- 迭代器轻量:大多数视图的迭代器只保存基础容器的迭代器或偏移量,复制成本低。
- 编译器内联:标准库对常见适配器进行了
noexcept与constexpr标记,允许编译器内联展开。
7. 小结
C++20 对范围基 for 循环的改进,配合强大的范围适配器与概念体系,使得容器遍历与组合变得更直观、更安全。通过合理使用 std::views,可以在保持代码简洁的同时实现复杂的数据处理逻辑。希望本文能为你在项目中充分利用 C++20 这一强大特性提供实用参考。