在 C++20 之前,处理容器和算法往往需要一系列繁琐的循环、拷贝和手动管理迭代器。随着标准的演进,ranges 与视图(views)被引入,使得对序列的操作更加直观、内存友好且类型安全。本文将以几个实际场景为例,演示如何使用 ranges 与视图来重构传统的 C++ 代码。
1. 什么是 ranges 与视图?
ranges 是对容器、迭代器和算法的抽象,核心概念包括:
- Range:可供迭代的序列对象(如 std::vector、std::array 等);
- View:对 range 的“视图”,可以是过滤、映射、切片等操作,视图本身也是一个 range;
- Algorithm:对 range 进行操作的函数(如 std::ranges::for_each、std::ranges::sort 等)。
View 的延迟求值特性意味着它们在第一次使用时才真正产生元素,避免了不必要的拷贝。
2. 过滤(filter)
假设我们有一个整数列表,需要找出所有偶数并求和:
#include <vector>
#include <numeric>
#include <iostream>
#include <ranges>
int main() {
std::vector <int> data{1,2,3,4,5,6,7,8,9,10};
auto even_sum = std::accumulate(
std::ranges::views::filter(data, [](int x){ return x % 2 == 0; })
.begin(),
std::ranges::views::filter(data, [](int x){ return x % 2 == 0; })
.end(),
0);
std::cout << "偶数之和: " << even_sum << '\n';
}
上述代码虽然已经比传统循环简洁,但仍需要两次 filter 调用。可以利用 views::transform 与 std::ranges::views::filter 组合,或者直接使用 std::ranges::accumulate(C++23 提供)来进一步简化。
3. 映射(transform)
在将一个字符串数组转为大写后输出时:
#include <string>
#include <vector>
#include <ranges>
#include <iostream>
int main() {
std::vector<std::string> words{"hello", "world", "cpp20", "ranges"};
for (auto word : words | std::ranges::views::transform([](auto&& s){
std::string res;
std::transform(std::begin(s), std::end(s), std::back_inserter(res),
[](char c){ return std::toupper(static_cast<unsigned char>(c)); });
return res;
})) {
std::cout << word << ' ';
}
std::cout << '\n';
}
通过 | 管道符,代码流动性更强,易于阅读。
4. 切片(slice)
要取出向量的中间 5 个元素:
#include <vector>
#include <ranges>
#include <iostream>
int main() {
std::vector <int> data{10,20,30,40,50,60,70,80,90,100};
auto middle = data | std::ranges::views::drop(2) | std::ranges::views::take(5);
for (int x : middle) std::cout << x << ' ';
std::cout << '\n';
}
这里 drop(2) 跳过前两个元素,take(5) 只取后续 5 个。
5. 组合视图
更复杂的业务逻辑往往需要多个视图组合。下面的例子演示了如何在一行代码中完成:取出长度大于 3 的单词,转为大写,最后收集到新向量中。
#include <vector>
#include <string>
#include <ranges>
#include <algorithm>
#include <iostream>
int main() {
std::vector<std::string> words{"a", "abc", "abcd", "abcde", "abcde"};
auto processed = words | std::ranges::views::filter([](auto&& s){ return s.size() > 3; })
| std::ranges::views::transform([](auto&& s){
std::string res;
std::transform(std::begin(s), std::end(s), std::back_inserter(res),
[](char c){ return std::toupper(static_cast<unsigned char>(c)); });
return res;
});
std::vector<std::string> result(std::begin(processed), std::end(processed));
for (auto& w : result) std::cout << w << ' ';
std::cout << '\n';
}
6. 性能与内存优势
由于视图是延迟求值的,实际上并不会在每一步创建临时容器。比如在上面的例子中,filter 与 transform 的结果不会单独存储,而是按需产生。与传统一次性拷贝的算法相比,减少了内存占用并提升了缓存友好性。
7. 与传统算法的对比
传统写法:
std::vector <int> data = {1,2,3,4,5,6,7,8,9,10};
std::vector <int> evens;
for (int x : data) {
if (x % 2 == 0) evens.push_back(x);
}
int sum = 0;
for (int x : evens) sum += x;
使用 ranges:
int sum = std::accumulate(
std::ranges::views::filter(data, [](int x){ return x % 2 == 0; })
.begin(),
std::ranges::views::filter(data, [](int x){ return x % 2 == 0; })
.end(),
0);
代码更短、表达更清晰。对于更复杂的链式操作,ranges 甚至可以写成一行。
8. 小结
- ranges 为容器提供了统一、类型安全的接口;
- views 通过延迟求值实现了高效的链式操作;
- 通过
|管道符,可以像 Unix Shell 一样组合操作,提升代码可读性; - 在大多数情况下,ranges 能显著减少临时对象,提升性能。
从 C++20 开始,建议将已有代码逐步迁移到 ranges 语义。随着 C++23 的 std::ranges::accumulate 等新工具加入,写法将更加简洁、直观。祝你在 C++ 之旅中愉快地使用 ranges 与视图,编写更高质量、更高性能的代码!