c++ ranges
C++20 引入了 Ranges 库(位于 <ranges>
头文件中),这是对标准模板库 (STL) 的重大扩展和泛化。它使得迭代器和算法更强大、更易用,主要通过引入范围 (range) 的概念来实现统一处理各种数据结构(如数组、向量、列表等)。Ranges 库的核心优势在于算法的懒惰求值 (lazy evaluation)、直接操作容器,以及可组合性 (composability),这大大简化了代码编写,避免了传统 STL 中常见的迭代器对 (begin/end) 显式使用。
std::ranges 关键概念
- Range:一个范围是一个可迭代的序列,必须提供
begin()
和end()
(哨兵)。示例包括std::vector
、std::array
、std::string_view
等,甚至 C 风格数组。 - Views:视图是范围的轻量级、懒惰表示,不会复制数据,只在需要时计算。它们是 Ranges 库的核心,用于管道式组合(如使用
|
操作符)。 - Adaptors:如
views::filter
、views::transform
、views::take
等,用于修改范围而不改变底层数据。 - Algorithms:Ranges 版本的算法(如
std::ranges::sort
、std::ranges::find
)直接接受范围作为参数,支持投影 (projections) 来简化操作特定成员。 - Actions:类似于算法,但会修改容器(如
ranges::sort
会就地排序)。
Ranges 库的算法是懒惰的:它们不会立即执行,而是返回视图,直到被消费(如迭代或转换为容器)。这提高了效率,尤其在处理大数据集时。
案例1:基本过滤和转换
假设我们有一个整数向量,需要过滤偶数并将其平方。
#include <iostream>
#include <vector>
#include <ranges>
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5, 6};
auto even_squares = nums
| std::views::filter([](int n) { return n % 2 == 0; })
| std::views::transform([](int n) { return n * n; });
for (int sq : even_squares) {
std::cout << sq << " "; // 输出: 4 16 36
}
}
这里,使用管道 |
组合了 filter
和 transform
视图。计算是懒惰的,只有在循环中才执行。
案例2:排序和投影
对一个结构体向量按成员排序,使用投影。
#include <iostream>
#include <vector>
#include <ranges>
#include <algorithm>
struct Person {
std::string name;
int age;
};
int main() {
std::vector<Person> people = {
{"Alice", 30},
{"Bob", 25},
{"Charlie", 35}};
// 按年龄排序,使用投影
std::ranges::sort(people, {}, &Person::age);
for (const auto& p : people) {
// 输出: Bob (25) Alice (30) Charlie (35)
std::cout << p.name << " (" << p.age << ") ";
}
}
投影 &Person::age
避免了编写完整 lambda,提高了可读性。
案例3:读取流数据
从输入流读取单词并处理。
#include <iostream>
#include <ranges>
#include <string>
#include <vector>
int main() {
std::string input = "Hello world from C++ ranges";
std::istringstream iss(input);
auto words = std::views::istream<std::string>(iss)
| std::views::transform([](const std::string& s) {
return s.size();
});
for (auto len : words) {
// 输出: 5 5 4 3 6
std::cout << len << " ";
}
}
使用 views::istream
将流转换为范围,结合 transform
计算单词长度。
C++23 的 std::ranges::to
C++23 扩展了 Ranges 库,引入了 std::ranges::to
函数,这是一个便利工具,用于从范围或视图急切地 (eagerly) 构造一个新容器。它接受一个模板参数指定目标容器类型,并自动推导元素类型。to
会强制评估视图,将结果存储到容器中,支持自定义分配器等高级用法。
语法示例:auto container = range | views::... | std::ranges::to<std::vector>();
这解决了 C++20 中需要手动使用 std::ranges::copy
或循环来收集结果的问题,使代码更简洁。
案例4:使用 ranges::to 收集过滤结果
从向量过滤偶数并转换为新向量。
#include <iostream>
#include <vector>
#include <ranges>
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5, 6};
auto evens = nums
| std::views::filter([](int n) { return n % 2 == 0; })
| std::ranges::to<std::vector>();
for (int n : evens) {
std::cout << n << " "; // 输出: 2 4 6
}
}
to<std::vector>()
直接创建新容器。
案例5:结合 zip 和 to 处理多个范围
使用 C++23 的 views::zip
结合两个向量,并转换为 map。
#include <iostream>
#include <vector>
#include <map>
#include <ranges>
int main() {
std::vector<std::string> keys = {"a", "b", "c"};
std::vector<int> values = {1, 2, 3};
auto zipped = std::views::zip(keys, values)
| std::ranges::to<std::map>();
for (const auto& [k, v] : zipped) {
// 输出: a: 1 b: 2 c: 3
std::cout << k << ": " << v << " ";
}
}
zip
结合范围,to<std::map>()
自动推导键值对。
案例6:从生成器收集数据
使用 C++23 的 std::generator
生成序列,并用 to
收集。
#include <iostream>
#include <vector>
#include <ranges>
#include <generator> // C++23
std::generator<int> fib_gen(int n) {
int a = 0, b = 1;
for (int i = 0; i < n; ++i) {
co_yield a;
std::tie(a, b) = std::make_tuple(b, a + b);
}
}
int main() {
auto fibs = fib_gen(5) | std::ranges::to<std::vector>();
for (int f : fibs) {
// 输出: 0 1 1 2 3
std::cout << f << " ";
}
}
generator
产生斐波那契数,to
将其转换为向量。