文章内容:
C++20 引入了 std::ranges 库,彻底改变了我们对容器操作的思维方式。借助 views、actions 以及 ranges 工具,我们可以用更简洁、声明式的代码实现往往在传统算法中需要手动写循环、临时容器等繁琐步骤的功能。下面通过一个完整的示例,展示如何使用 std::ranges 对容器进行链式过滤、映射、排序、去重等操作。
#include <iostream>
#include <vector>
#include <string>
#include <ranges>
#include <algorithm>
int main()
{
// 原始数据:一个包含整数的 vector
std::vector <int> nums{3, 1, 4, 1, 5, 9, 2, 6, 5, 3};
// 目标:
// 1. 只保留偶数
// 2. 每个偶数乘以 2
// 3. 按升序排序
// 4. 去重
// 5. 转换为字符串并拼接成单个字符串
// 使用 ranges 的链式操作
auto result = nums
| std::views::filter([](int n){ return n % 2 == 0; }) // 只保留偶数
| std::views::transform([](int n){ return n * 2; }) // 乘以 2
| std::views::common(); // 转为 common_view(使得可多次遍历)
// 对 common_view 进行排序和去重
;
// 需要一个临时容器来执行排序和去重(因为 views 本身是惰性的)
std::vector <int> temp(result.begin(), result.end());
std::ranges::sort(temp); // 排序
auto last = std::ranges::unique(temp); // 去重
temp.erase(last, temp.end());
// 转换为字符串
std::string joined;
for (auto n : temp) {
joined += std::to_string(n);
joined += ",";
}
if (!joined.empty()) joined.pop_back(); // 去掉最后一个逗号
std::cout << "处理后的结果: " << joined << '\n';
}
代码逐步解析
-
过滤
std::views::filter接受一个谓词,用于筛选符合条件的元素。这里我们保留n % 2 == 0的偶数。 -
映射
std::views::transform对每个元素应用一个函数。我们将每个偶数乘以 2。 -
common_view
views::common把惰性视图转换为一个可多次遍历的common_view。这在后续需要多次遍历(例如排序)时很有用。common_view还会把底层容器的差异化视图统一成一个通用的iterator。 -
排序
`,然后调用 `sort`。
std::ranges::sort需要可随机访问迭代器,因此我们先把视图转换成 `std::vector -
去重
std::ranges::unique在已排序的容器上进行去重,返回去重后元素的最后一个位置的迭代器。随后用erase删除多余部分。 -
转换为字符串
遍历最终结果,使用std::to_string转成字符串并拼接。这里可以进一步使用std::ranges::join结合std::views::transform实现更函数式的拼接(C++23 将进一步完善这一点)。
高级技巧
-
自定义视图
如果你频繁需要某一套操作(如上述四步),可以将它封装成一个自定义视图或一个辅助函数,进一步提升代码可读性。 -
管道操作
C++23 将引入std::ranges::pipeline,让你可以像 Python 的管道一样链式写操作,语法会更加直观。 -
组合视图
你可以通过views::filter、views::transform、views::unique等直接在一个视图链中完成,避免临时容器,提升性能。例如:auto final_view = nums | std::views::filter([](int n){ return n % 2 == 0; }) | std::views::transform([](int n){ return n * 2; }) | std::views::unique(); // unique 在已排序的前提下有效但需要注意,
unique要求底层迭代器是随机访问,或者你先用views::common再views::unique。
性能与资源
-
惰性求值
std::views默认是惰性的,意味着它们只在真正需要时才会产生结果。若链中包含耗时操作,尽量将其放在最前面,或使用views::common将结果缓冲。 -
内存占用
由于视图不存储数据,它们对内存占用友好;但若需要排序、去重等需要随机访问的操作,必须先把结果缓冲到容器中。 -
编译器优化
现代编译器(如 GCC 13、Clang 15、MSVC 19.35+)对ranges的内联与循环合并做了极大优化,实际速度往往和手写循环相当,甚至更快。
小结
C++20 的 std::ranges 让容器操作从繁琐的迭代器手写,转变为声明式、可组合的表达式。通过上述示例,你可以快速搭建起自己的数据处理流水线,既保持代码简洁,又不失高性能。随着未来标准的演进(C++23、C++26 等),ranges 将继续完善,成为 C++ 生态中不可或缺的工具之一。