C++ ranges/view库简介
C++ Range Library 简介.
文章目录
C++20 引入了 ranges 库, 这是标准模板库 (STL) 的一个重大改进. ranges 库引入了一种新的方式来处理序列(如数组、向量等), 使代码更加简洁和表达性更强.
环境要求
- 编译器需要支持 C++23 标准.
- 安装有 libfmt(version 10.0.0 或更高版本), 用于输出格式化的数据.
使用在线编译器
如果在本地环境无法运行, 可以使用在线编译器 Compiler Explorer.
这里有一个已经配置好的样例, 替换代码即可运行.
Ranges/View 简介
Ranges(范围)
Ranges 是 C++20 引入的新标准, 用于统一处理不同类型的序列容器, 提供了更直观的操作方式.
与传统 STL 对比, STL 通过 std::begin
和 std::end
获取迭代器, 手动遍历序列.
Ranges 直接操作范围对象, 代码更加简洁.
代码示例:
#include <fmt/ranges.h>
#include <algorithm>
#include <ranges>
#include <vector>
int main() {
std::vector<int> vec = {1, 3, 2, 9, 4};
// before c++20
std::sort(vec.begin(), vec.end());
// with c++20
std::ranges::sort(vec);
return 0;
}
Views(视图)
C++ 的 Views 提供惰性计算, 能处理复杂的操作链.
#include <fmt/ranges.h>
#include <ranges>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// 通过 Views 过滤和变换
auto squared_evens = vec | std::views::filter([](int n) { return n % 2 == 0; }) // 过滤偶数
| std::views::transform([](int n) { return n * n; }); // 平方变换
fmt::println("{}", squared_evens);
return 0;
}
ranges/view 的特点
惰性计算
视图操作(如 filter 或 transform)不会立刻执行, 而是在数据实际访问时才触发计算, 避免不必要的开销.
轻量级
只保有原始数据的引用, 不会复制数据, 降低内存开销.
组合式操作
视图支持通过管道运算符 (|
) 将多个操作串联起来, 如:
auto result = data | std::views::filter(condition) | std::views::transform(func);
减少代码冗长性, 提升可读性.
不可变性
视图操作不会直接修改原始数据, 只返回一个新视图, 保持原始数据不变.
使用样例
过滤操作
可以使用 filter
操作过滤序列中的元素,
#include <fmt/ranges.h>
#include <ranges>
#include <vector>
int main() {
std::vector vec = {1, 2, 3, 4, 5, 6};
auto even_view = vec | std::views::filter([](int n) { return n % 2 == 0; });
fmt::println("even numbers: {}", even_view); // even numbers: [2, 4, 6]
}
变换操作
可以使用 transform
操作对每个元素应用一个函数.
#include <fmt/ranges.h>
#include <ranges>
#include <vector>
int main() {
std::vector vec = {1, 2, 3, 4, 5};
auto square_view = vec | std::views::transform([](int n) { return n * n; });
fmt::println("square numbers: {}", square_view); // square numbers: [1, 4, 9, 16, 25]
return 0;
}
截取操作
take
操作可以从序列的开头截取指定数量的元素.drop
操作可以丢弃指定数量的元素.
#include <fmt/ranges.h>
#include <ranges>
#include <vector>
int main() {
std::vector vec = {1, 2, 3, 4, 5};
auto first_three_view = vec | std::views::take(3);
auto drop_three_view = vec | std::views::drop(3);
fmt::println("take first three: {}", first_three_view); // take first three: [1, 2, 3]
fmt::println("drop first three: {}", drop_three_view); // drop first three: [4, 5]
return 0;
}
zip 操作 (C++23)
zip
操作可以将两个序列合并为一个元素对序列.
zip_transform
操作可以将两个序列的元素应用一个函数.
#include <fmt/ranges.h>
#include <ranges>
#include <vector>
int main() {
std::vector v1{1, 2, 3};
std::vector v2{'a', 'b', 'c'};
auto r1{std::views::zip(v1, v2)};
fmt::println("r1: {}", r1); // [(1, 'a'), (2, 'b'), (3, 'c')]
std::vector v3{4, 5, 6};
auto r2{std::views::zip_transform(std::multiplies(), v1, v3)};
fmt::println("r2: {}", r2); // [4, 10, 18]
return 0;
}
枚举操作(C++23)
#include <fmt/ranges.h>
#include <ranges>
#include <vector>
int main() {
std::vector vec = {'a', 'b', 'c', 'd', 'e'};
auto r = vec | std::views::enumerate;
for (auto [i, c] : r) {
fmt::println("index: {}, value: {}", i, c);
}
// output:
// index: 0, value: a
// index: 1, value: b
// index: 2, value: c
// index: 3, value: d
// index: 4, value: e
}
访问键值对
#include <fmt/ranges.h>
#include <ranges>
#include <vector>
int main() {
std::vector<std::pair<int, std::string>> vec = {{1, "Ken"}, {2, "Ben"}, {3, "Alan"}};
auto keys = vec | std::views::keys;
auto values = vec | std::views::values;
fmt::println("keys: {}", keys); // keys: [1, 2, 3]
fmt::println("values: {}", values); // values: ["Ken", "Ben", "Alan"]
return 0;
}
访问 tuple 的元素:
#include <fmt/ranges.h>
#include <ranges>
#include <vector>
int main() {
std::vector<std::tuple<int, std::string, std::string>> vec = {
{1, "Ken", "Briton"}, {2, "Ben", "USA"}, {3, "Alan", "Canada"}};
auto addr = vec | std::views::elements<2>;
fmt::println("addr: {}", addr); // addr: [Briton, USA, Canada]
}
分割操作
使用split
操作可以将字符串分割为子字符串.
#include <fmt/ranges.h>
#include <ranges>
#include <vector>
int main() {
std::string str = "Hello, World!";
auto r15 = str | std::views::split(',');
fmt::println("{}", r15); // [['H', 'e', 'l', 'l', 'o'],
// [' ', 'W', 'o', 'r', 'l', 'd', '!']]
}
合并操作
使用join
操作可以将子字符串合并为一个字符串.
#include <fmt/ranges.h>
#include <ranges>
#include <vector>
#include <string>
using namespace std::string_literals; // from C++14
int main() {
std::vector vs{"hi"s, "you"s, "!"s};
auto s1{vs | std::views::join};
fmt::println("{}", s1); // ['h', 'i', 'y', 'o', 'u', '!']
auto s2{vs | std::views::join_with('\n')}; // from C++23
fmt::println("{}", s2); // ['h', 'i', '\n', 'y', 'o', 'u', '\n', '!']
}
滑动窗口操作
slide
操作可以生成一个滑动窗口序列.
#include <fmt/ranges.h>
#include <ranges>
#include <vector>
int main() {
std::vector v{1, 2, 3, 4, 5};
auto r7{v | std::views::slide(2)}; // from C++23
fmt::println("r7: {}", r7); // [[1, 2], [2, 3], [3, 4], [4, 5]]
}
adjacent
操作与此有点像, 可以生成一个相邻元素对序列.
#include <fmt/ranges.h>
#include <ranges>
#include <vector>
int main() {
std::vector v{1, 2, 3, 4};
auto r3{v | std::views::adjacent<2>}; // from C++23
fmt::println("r3: {}", r3); // [(1, 2), (2, 3), (3, 4)]
}
分块访问
chunk
操作可以将序列分块. 注意 chunk 之间没有重叠.
{
std::vector v{1, 2, 3, 4, 5};
auto r8{v | std::views::chunk(2)};
fmt::println("r8: {}", r8); // [[1, 2], [3, 4], [5]]
}
{
std::vector v{1, 2, 2, 3, 0, 4, 5, 2};
auto r9{v | std::views::chunk_by(std::ranges::less_equal{})}; // from C++23
// explain:
// 1 < 2 <= 2 < 3;
// 0 < 4 <= 5;
// 2
fmt::println("r9: {}", r9); // {(1,2,2,3),(0,4,5),(2)}
}
间隔访问
stride
操作可以按照指定的步长访问序列.
#include <fmt/ranges.h>
#include <ranges>
#include <vector>
int main() {
std::vector v{1, 2, 3, 4, 5};
auto r{v | std::views::stride(2)}; // from C++23
fmt::println("{}", r); // {1, 3, 5}
}
结论和总结
Ranges 和 Views 是 C++20 标准的重要组成部分, 为序列处理提供了更高层的抽象和更高效的实现. 它们通过统一的接口减少了对迭代器的直接依赖, 并通过惰性计算显著优化了性能. 尽管 ranges 提供了强大的功能, 但仍需注意其对编译器支持的要求(如 C++20 以上)以及一些尚未完全覆盖的边界情况.
参考链接
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)