C++20 的 Ranges 库为 STL 容器和算法提供了全新的视图(views)与适配器(adapters),让我们可以像处理单一元素一样对整个序列进行操作,极大地提升了代码的表达力与可维护性。下面我们从几个典型场景展示如何使用 Ranges 来简化代码,并给出完整可编译的示例。
1. 引入 Ranges 相关头文件
#include <iostream>
#include <vector>
#include <ranges>
#include <algorithm>
#include <numeric>
使用 std::ranges、std::views、std::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. 组合多个视图
假设我们想对一个字符串序列执行以下链式操作:
- 转为全大写
- 去掉空格
- 只保留长度大于 3 的字符串
- 按字典序排序
#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::accumulate 或 std::vector 构造函数),不会产生临时容器。这样可以显著降低内存开销和拷贝次数。对大规模数据时尤为重要。
7. 小结
- 过滤:
std::views::filter - 映射:
std::views::transform - 子序列:
std::ranges::subrange - 原地变换:
std::ranges::actions - 聚合:
std::ranges::accumulate、std::ranges::sort等
通过这些工具,你可以将一连串笨重的循环、条件分支写成一行简洁的表达式,既提高可读性,又能让编译器更好地优化。C++20 的 Ranges 让现代 C++ 的代码更像是“声明式”而非“命令式”,为日常编码带来了极大便利。