C++20 引入了 <ranges> 标头,提供了一套完整的视图(view)、适配器(adapter)和算法(algorithm)集合,让我们能够像函数式编程一样以更简洁、更直观的方式操作容器。本文将从基础概念讲起,演示如何使用 ranges 进行高效、可读的代码编写,并讨论其与传统迭代器风格的区别与优势。
1. 视图(View)与适配器(Adapter)
- 视图(view):只读、惰性求值的数据序列,它不会拷贝底层容器,而是基于原始数据按需生成元素。
- 适配器(adapter):对视图进行加工(如过滤、映射、切片等),同样惰性求值。
#include <vector>
#include <ranges>
#include <iostream>
int main() {
std::vector <int> v = {1, 2, 3, 4, 5, 6, 7};
// 取偶数并翻倍
auto result = v | std::views::filter([](int n){ return n % 2 == 0; })
| std::views::transform([](int n){ return n * 2; });
for (int x : result)
std::cout << x << ' '; // 输出: 4 8 12
}
2. 主要适配器
| 适配器 | 功能 | 示例 |
|---|---|---|
views::filter |
过滤 | v | std::views::filter([](int n){ return n > 3; }) |
views::transform |
映射 | v | std::views::transform([](int n){ return n * n; }) |
views::take |
取前 N 个 | v | std::views::take(3) |
views::drop |
跳过前 N 个 | v | std::views::drop(2) |
views::reverse |
反转 | v | std::views::reverse |
views::split |
切割 | std::string s = "a:b:c"; → s | std::views::split(':') |
3. 组合使用与管道式风格
使用管道符号 |,可以将多个适配器串联,形成链式操作。整个链条惰性求值,直到真正需要访问元素时才会执行。
auto filtered = v
| std::views::filter([](int n){ return n % 2 != 0; })
| std::views::transform([](int n){ return n * 3; })
| std::views::take(4);
4. 与传统算法的对比
- 可读性:传统算法往往需要多行
for循环和临时容器,而 ranges 只需一行表达式。 - 性能:视图惰性求值可避免不必要的拷贝与中间容器。
- 可组合性:适配器可随意组合,易于扩展和维护。
5. 常见 pitfalls
- 容器生命周期:视图仅引用底层数据,若容器被销毁,视图失效。
- 多次遍历:某些适配器是一次性使用的(如
views::split),多次迭代会导致错误。 - 标准库实现差异:部分编译器对 ` ` 的支持仍不完整,建议使用 GCC 11+ 或 Clang 13+。
6. 小结
C++20 的 ranges 库为容器操作提供了更直观、更高效的方式。通过视图和适配器,我们可以像使用管道一样构造复杂的数据流,减少样板代码。掌握 ranges 的使用,将使你的 C++ 代码更现代、更易维护。祝编码愉快!