在 C++20 中, 库为我们提供了大量新工具,其中最令人兴奋的是 std::ranges::views。这些视图(views)允许我们以惰性、链式的方式处理容器,而无需显式创建临时容器。下面让我们从几个常见的使用场景,逐步探索视图的优势与使用技巧。
1. 惰性求值与节省空间
传统的 STL 算法往往在中间需要生成临时容器。例如,筛选并平方一个整数序列:
std::vector <int> nums = {1,2,3,4,5,6,7,8,9,10};
std::vector <int> result;
for (auto x : nums) {
if (x % 2 == 0) result.push_back(x * x);
}
如果你使用 std::ranges::views,可以写成:
using namespace std::ranges::views;
auto result = nums | filter([](int x){ return x % 2 == 0; })
| transform([](int x){ return x * x; });
这里 result 并不是一个 `vector
`,而是一个 *view* 对象,它在你真正需要访问元素时才会进行计算。若随后你只想打印前几个元素,仍能保持惰性:
“`cpp
for (auto x : result | take(3)) {
std::cout
#include
#include
#include
#include
std::vector words = {“apple”, “banana”, “cherry”, “date”, “elderberry”, “fig”, “grape”};
auto is_long = [](const std::string& s){ return s.size() > 5; };
auto to_upper = [](const std::string& s){
std::string r = s;
std::transform(r.begin(), r.end(), r.begin(), ::toupper);
return r;
};
auto processed = words
| std::ranges::views::filter(is_long)
| std::ranges::views::transform(to_upper)
| std::ranges::views::unique; // 去重(假设已经排序)
for (auto&& word : processed) {
std::cout v(20);
std::iota(v.begin(), v.end(), 0); // 0..19
// 取中间 5 个元素
auto middle = std::ranges::subrange(v.begin() + 5, v.begin() + 10);
auto sum = std::ranges::accumulate(middle, 0);
“`
通过 `std::span`,你还能将裸指针转为安全的视图:
“`cpp
int arr[] = {10, 20, 30, 40, 50};
std::span
s(arr, 5); // 视图,长度为 5
auto avg = std::ranges::accumulate(s, 0) / static_cast
(s.size());
“`
### 4. 性能考量与实践建议
– **惰性 vs 立即**:默认视图是惰性的,适合链式调用和延迟计算。但如果你需要多次迭代同一序列,最好将视图转换为 `std::vector` 或 `std::array`,避免重复遍历。
– **记忆体占用**:视图本身占用极少空间,只有迭代器和闭包等小结构。但如果闭包捕获了大量数据,也会增加视图大小。
– **与 `std::algorithm` 的互补**:大多数传统算法已提供视图版本,例如 `std::ranges::find_if`。在需要更复杂的链式操作时,视图是最佳选择。
### 5. 小结
C++20 的 `std::ranges::views` 为我们提供了一种更直观、模块化、内存友好的方式处理容器数据。通过惰性求值、链式组合以及与 `subrange`、`span` 的结合,你可以写出既简洁又高效的代码。熟练掌握这些工具后,日常的数据处理任务将变得异常轻松。
如果你还没有尝试过 `std::ranges::views`,不妨把一个现有的项目迁移到视图风格,亲身感受它带来的乐趣与便利。祝编码愉快!