如何在 C++20 中使用 std::ranges 进行链式过滤与映射

C++20 引入了 std::ranges,为容器提供了更为灵活、简洁的操作方式。利用 ranges::views,我们可以像链式调用一样,对序列进行过滤、映射、排序等处理,最终一次性获取结果。本文以一个常见的数据处理场景为例,演示如何使用 std::ranges 来完成链式过滤与映射,并比较其与传统 std::algorithm 的差异。

1. 背景与目标

假设我们有一个包含学生信息的 `std::vector

`,其中 `Student` 结构体定义如下: “`cpp struct Student { std::string name; int age; double gpa; }; “` 我们想要完成以下任务: 1. 过滤出年龄大于 18 岁且 GPA 大于 3.0 的学生。 2. 将这些学生的姓名转为大写。 3. 按 GPA 降序排列。 4. 将结果收集到一个新的 `std::vector`。 使用传统的 `std::copy_if`、`std::transform`、`std::sort` 等算法,需要写大量的临时容器或显式迭代器,代码可读性不佳。`std::ranges` 则可以大大简化这一过程。 ## 2. 关键工具与概念 – **views**:对容器的“视图”,不做实际拷贝,而是按需生成元素。 – **filter**:按给定谓词过滤元素。 – **transform**:对元素进行转换。 – **take_while**、**drop_while**:按条件截取或跳过元素。 – **sort**:对视图进行排序(需要提供可变视图)。 – **to**:将视图结果收集到指定容器。 ## 3. 示例实现 “`cpp #include #include #include #include #include #include // std::toupper struct Student { std::string name; int age; double gpa; }; int main() { std::vector students = { {“Alice”, 19, 3.5}, {“Bob”, 17, 3.2}, {“Charlie”, 20, 2.8}, {“Diana”, 22, 3.9}, {“Ethan”, 18, 3.1} }; using namespace std::ranges; using namespace std::views; // 1. 过滤年龄 > 18 且 GPA > 3.0 // 2. 将姓名转为大写 // 3. 按 GPA 降序排序 // 4. 收集到 vector std::vector result = students | filter([](const Student& s) { return s.age > 18 && s.gpa > 3.0; }) | transform([](const Student& s) { std::string upperName = s.name; std::transform(upperName.begin(), upperName.end(), upperName.begin(), [](unsigned char c){ return std::toupper(c); }); return upperName; }) | to>(); // 先暂时收集到 vector,再排序 // 对结果按 GPA 降序排序(需要先得到 GPA 列表) // 这里演示另一种方法:先对 Student 视图排序,然后再提取姓名 std::vector sortedNames = students | filter([](const Student& s){ return s.age > 18 && s.gpa > 3.0; }) | views::transform([](const Student& s){ return s; }) // 先不做转换,保持 Student | views::sort([](const Student& a, const Student& b){ return a.gpa > b.gpa; }) | views::transform([](const Student& s){ std::string upperName = s.name; std::transform(upperName.begin(), upperName.end(), upperName.begin(), [](unsigned char c){ return std::toupper(c); }); return upperName; }) | to>(); std::cout `。 4. 对排序需求,先保持 `Student` 对象视图,再用 `views::sort` 按 GPA 降序,然后再 `transform` 为姓名。 ## 4. 与传统算法的对比 | 需求 | `std::algorithm` 方案 | `std::ranges` 方案 | |——|———————–|——————-| | 过滤 | `copy_if` + 临时容器 | `filter` | | 转换 | `transform` + 临时容器 | `transform` | | 排序 | `sort` + 临时容器 | `sort` | | 收集 | `back_inserter` 或 `inserter` | `to` | | 代码行数 | ~12 | ~8 | | 可读性 | 需要多步操作 | 一行链式调用 | `std::ranges` 通过视图(view)延迟计算的特性,让我们不必为每一步都创建临时容器。所有操作在最终收集时才会真正执行,极大地提升了性能与可维护性。 ## 5. 进一步扩展 – **多条件组合**:`views::filter` 可以与 `views::take_while`、`views::drop_while` 结合,实现更复杂的分段处理。 – **组合视图**:使用 `views::concat`、`views::join` 进行多容器拼接。 – **自定义视图**:通过实现 `view_interface`,可以创建自定义的视图类型,进一步提升复用性。 ## 6. 小结 C++20 的 `std::ranges` 为容器操作带来了全新的表达方式。通过链式调用 `filter`、`transform`、`sort` 等视图,我们可以在保持代码简洁的同时,获得与传统算法相同甚至更高的性能。推荐在新项目中优先使用 `std::ranges`,在旧项目中逐步迁移,以提升代码质量和可维护性。

发表评论