如何在C++20中使用Ranges库提升代码可读性?

C++20 的 Ranges 库为 STL 容器和算法提供了全新的视图(views)与适配器(adapters),让我们可以像处理单一元素一样对整个序列进行操作,极大地提升了代码的表达力与可维护性。下面我们从几个典型场景展示如何使用 Ranges 来简化代码,并给出完整可编译的示例。


1. 引入 Ranges 相关头文件

#include <iostream>
#include <vector>
#include <ranges>
#include <algorithm>
#include <numeric>

使用 std::rangesstd::viewsstd::ranges::actions 等命名空间来访问 Ranges 的功能。


2. 传统方式 vs Ranges 方式

2.1 传统方式:查找奇数并累加

std::vector <int> numbers{1,2,3,4,5,6,7,8,9,10};
int sum = 0;
for (auto n : numbers) {
    if (n % 2 == 1)
        sum += n;
}
std::cout << "Sum of odd numbers: " << sum << '\n';

2.2 Ranges 方式

int sum = std::ranges::accumulate(
    numbers | std::views::filter([](int n){ return n % 2 == 1; }),
    0
);
std::cout << "Sum of odd numbers (Ranges): " << sum << '\n';

std::views::filter 只保留满足条件的元素,整个表达式像流水线一样连贯。


3. 组合多个视图

假设我们想对一个字符串序列执行以下链式操作:

  1. 转为全大写
  2. 去掉空格
  3. 只保留长度大于 3 的字符串
  4. 按字典序排序
#include <string>
#include <array>

std::array<std::string, 5> words{"apple", "pie", "banana", "kiwi", "strawberry"};

auto processed = words
    | std::views::transform([](const std::string& s) {
          std::string res = s;
          std::transform(res.begin(), res.end(), res.begin(), ::toupper);
          return res;
      })
    | std::views::transform([](std::string s) {
          s.erase(std::remove(s.begin(), s.end(), ' '), s.end());
          return s;
      })
    | std::views::filter([](const std::string& s){ return s.size() > 3; })
    | std::views::common; // 使其可多次遍历

// 对视图进行排序(需要拷贝到临时容器)
std::vector<std::string> sorted(processed.begin(), processed.end());
std::ranges::sort(sorted);

for (const auto& w : sorted)
    std::cout << w << ' ';
std::cout << '\n';

注意std::views::common 用来把惰性视图转成可重复遍历的形式,以便 std::ranges::sort 能够工作。


4. 使用 Actions 进行原地变换

Actions 允许我们在不产生临时容器的情况下对已有容器进行原地操作,结合 std::ranges::actions::sort

std::vector <int> data{4, 1, 3, 2, 5};

data | std::ranges::actions::sort;
data | std::ranges::actions::transform([](int& n){ n *= 10; });

for (int n : data)
    std::cout << n << ' ';   // 输出: 10 20 30 40 50

5. 与 std::ranges::subrange 结合

如果你只想操作容器的一部分,例如只处理前 5 个元素,可以使用 std::ranges::subrange

auto sub = std::ranges::subrange(data.begin(), data.begin() + 5);
int avg = std::ranges::accumulate(sub, 0) / 5;
std::cout << "Average of first 5: " << avg << '\n';

6. 性能与惰性

Ranges 的视图是惰性的:除非你显式请求一个结果(如 std::ranges::accumulatestd::vector 构造函数),不会产生临时容器。这样可以显著降低内存开销和拷贝次数。对大规模数据时尤为重要。


7. 小结

  • 过滤std::views::filter
  • 映射std::views::transform
  • 子序列std::ranges::subrange
  • 原地变换std::ranges::actions
  • 聚合std::ranges::accumulatestd::ranges::sort

通过这些工具,你可以将一连串笨重的循环、条件分支写成一行简洁的表达式,既提高可读性,又能让编译器更好地优化。C++20 的 Ranges 让现代 C++ 的代码更像是“声明式”而非“命令式”,为日常编码带来了极大便利。

发表评论