c++ ranges随笔

news/2025/10/28 21:03:01/文章来源:https://www.cnblogs.com/sunmk/p/19172735

ranges

c++20引入,在<ranges>头文件中
建立在 std::algoiterator基础上,并做了进一步的抽象集成
与之前相比更加的 安全、简洁、方便

// ranges concept
template <typename T>
concept range = requires(T& t) {ranges::begin(t);ranges::end(t);
};

从概念看,ranges 就是可以使用 begin(), end() 访问的对象,只要是有 begin(),end() 的就可以算作ranges,比如经常使用的 vector, string 等等

常用类型

由于是在 std::algo 基础上拓展的,所以使用方法是 std::ranges::algo

可以直接处理整个 Range 对象如 vectorarraystring等,不需要显式传入 begin()end()

同时还可以处理 Ranges 提供的 std::views ,但 std::algo 无法处理 views

下面的函数,区别就在于 原STL需要传入 v.begin(),v.end()对,而 ranges版本只需要传入名称 v

原 STL ranges 版本
sort ranges::sort
find_if ranges::find_if
copy ranges::copy
unique ranges::unique
for_each ranges::for_each
count_if ranges::count_if

subrange

在平时想取 (v.begin(), v.begin() + x) 这样的区间对,可能存在写错的风险

在提出 ranges 中使用能够更安全、方便地使用迭代器对,引入了 subrange 的概念

ranges::subrange 是一种安全的 “begin-end 对” 封装,可以代替传统的 pair<Iter, Iter>

比如说:

vector<int>v{1,2,3,4,5,6};
auto sub = std::ranges::subrange(v.begin() + 1, v.begin() + 4);
for (int x : sub) std::cout << x << " "; // 2 3 4

这里的 sub 表示 [v.begin()+1, v.begin()+4) 这段区间,可以像普通容器一样迭代使用,同时避免了直接传递两个迭代器的麻烦与错误风险。

还有经常使用的 sort + unique :

vector<int>v{1,2,3,1,1,2,3,3};
// 之前的写法
sort(v.begin(),v.end());
auto it = unique(v.begin(),v.end());
v.erase(it, v.end());
// 现在的写法
ranges::sort(v);
auto [first, last] = ranges::unique(v);// unique 返回一个 ranges::subrange
v.erase(first, last);

ranges 中有几个概念:视图、管道、投影

Views(视图)

视图故为其名,就是如数据库视图一样,不对实际的数据进行操作,而是建立一个对底层数据的观察逻辑

视图是 ranges 在实际使用中最重要的一个概念

views 是在 std 命名空间下,使用时 std::views::func(),而不是 std::ranges::views::func()

举例

下面是几个常见的 views 视图函数

视图名 功能
std::views::filter 过滤
std::views::transform 元素映射
std::views::take / drop 取/跳过前 N 个
std::views::reverse 反转
vector<int> v{1,2,3,4,5};
// 写法一:views::filter(v, pred); 
auto out = views::filter(v, [](int x){return x%2==0;});
for(auto x: out) cout << x << " ";// 2 4// 写法二:views::transform(pred)(v);
auto out2 = views::transform([](int x){return x*x;})(v);
for(auto x: out2) cout << x << " ";// 1 4 9 16 25// 写法三:v | views::take(3);
auto out3 = v | views::take(3);
for(auto x: out3) cout << x << " ";// 1 2 3auto out4 = v | views::drop(3);
for(auto x: out4) cout << x << " ";// 4 5auto out5 = views::reverse(v);
for(auto x: out5) cout << x << " ";// 5 4 3 2 1

在使用时,有三种写法,分别是 func(v,pred)func(pred)(v)v|func(pred)

其中 | 是管道,在下面会进一步说明,和 linux 命令中的管道作用差不多,简单来说就是 将前面的数据传递到后面

视图是惰性的range,不对数据进行立刻计算,只有在迭代时才会被计算;但依赖于数据。

auto v = std::views::filter([](int x){ return x>0; }); //错误,缺少底层数据

视图一定属于ranges,但ranges还包含其他对象,比如 vector、array、string等的

管道

管道也是一个很重要的概念,通过管道可以实现对视图的链式组合,

举几个例子方便理解:

// MicroSoft博客例子
auto names = std::views::iota(1, 10)
| std::views::filter([](int i){ return i % 3 == 0; })
| std::views::transform([](int i){ return "User" + std::to_string(i); });
for (auto name : names) cout << name << ' '; // User3 User6 User9// 上面out1+out2链接
std::vector<int> v = {1,2,3,4,5,6};
auto rng = v| std::views::filter([](int x){ return x % 2 == 0; })| std::views::transform([](int x){ return x * x; });
// 直接在视图上用迭代器接口做累加
int sum = accumulate(rng.begin(), rng.end(), 0);
cout<<sum<<"\n"; // 56

通过使用管道将数据与视图连接起来,可以让整个数据处理流程更加直观且可读性更高。

还有一个需要特别注意的地方,管道的输入输出是 range | range,为了更好的理解,下面举个例子:

vector<int>v{1,2,3,4};
auto out = v | ranges::sort // 是错误的

上面例子是错误的,因为 ranges::sort 返回是 void,而管道要求是 range

如果实在需要管道流程,可以自定义返回值的sort视图函数

auto sort_view = [](auto&& r) -> auto& {std::ranges::sort(r);return r;
};
auto v = std::vector{3, 1, 2};
auto res = v | sort_view;

投影参数

投影是一个可选的函数,用来在比较或操作之前先对元素做映射。

这是一个c++20新添的参数,先举一个例子:

struct Student {int id; double score;};
vector<Student> stu = {{1, 21}, {2, 12}, {3, 13}};
// 按 score 排序
ranges::sort(stu, {}, &Student::score);
for (auto s : stu) std::cout << s.id << " "; // 2 3 1//等价于
sort(stu.begin(), stu.end(), [](auto& a, auto& b){ return a.score < b.score; });

其中 {} 为默认的 ranges::less{} 比较器(升序),可以按需切换 ranges::greater{}(降序)

主要作用就是 用来简化写法的,实际上它就是一个 比较之前取值的,比较调用的是 comp(proj(a), proj(b))

ranges::sort(r, comp = {}, proj = {});

可以这么理解,通过添加这个投影参数,可以使一些简单/中等的操作变得更简单,但如果需要复杂的比较逻辑,还需要自己定义比较的规则

投影参数可以用来指定单个成员,例如 &Student::score,也可以将多个成员封装成 pairtuple 等可比较的结构,从而简化多字段排序或去重的写法;并且所有 ranges::func基本上都支持添加投影参数。

投影仅负责提取或转换数据,而不是定义比较规则,不能在投影内部直接编写比较逻辑并依赖比较器去调用,若需要复杂的判断顺序或多条件规则,应该改为自定义比较函数。

自定义投影举例:

ranges::sort(stu, {}, [](const Student& s){ return make_pair(s.score, s.id); 
});

更复杂的,直接舍弃 投影参数,和之前的写法一样:

ranges::sort(stu, [](auto&& a, auto&& b){if (a.score != b.score) return a.score > b.score;return a.id < b.id;
});

其他信息

这里主要介绍一下关注的一些细节信息

性能

简单来说吧,如果觉得方便就尽管用,最慢也是慢几个常数,如果发现因为常数 T 了,就改为原来的写法

不要在循环内部复用同一个惰性视图,要用也要 ranges::to<container>() 物化为实体再用,否则每次遍历 view 时都会重新执行过滤、投影等惰性操作,带来不必要的重复开销

auto out3 = v | views::take(3); 针对 v 的类型,会有不同的时间消耗,如果 v 是 views 类型,基本上与之前的写法没什么差别,并且更安全;如果是其他类型,可能会增加一点视图构建消耗,但很小

视图构造

在使用 不同形式的数据时,会产生不同的视图

这个问题来源于,在最开始看时,有的地方介绍是 views 是惰性 range,不能接受临时数据,但在实际尝试时:

auto out6 = views::filter(vector<int>{1,2,3,4,5}, [](int x){return x%2==0;});
for(auto x: out6) cout << x << " ";// 2 4
auto out7 = vector<int>{1,2,3,4,5}| views::transform([](int x){return x*x;});
for(auto x: out7) cout << x << " ";// 1 4 9 16 25

上面两个使用临时变量的例子都是可以成功编译的,进一步引出了 views 是怎么构造的问题,如下表所示:

情况 views::all() 是否拷贝
view 原对象
左值容器 ref_view
右值容器 owning_view 拷贝 / move
数组 ref_view
初始化列表 编译错误 错误

对于传入的参数 v,程序会在内部自动将其转化为一个 view 类型,这个转换过程由 std::views::all() 完成,它会根据 v 的具体类型采取不同策略:

v 本身已是一个 view,直接返回原对象,不做任何额外处理;若 v 是左值容器(例如已经定义的 vector<int> v 转化为 ranges::ref_view,仅保存对原容器的引用,不发生拷贝;若 v 是右值容器,比如 vector<int>{1, 2, 3} 这样的临时对象转化为 ranges::owning_view,由该 view 自身持有底层容器的所有权,安全地延长其生命周期;若 v 是数组,比如 int a[] = {1, 2, 3} 同样转化为 ref_view,保存对数组的引用而非拷贝;若 v 是初始化列表比如 {1, 2, 3})无法被接受,std::initializer_list 的底层数据仅在表达式期间有效,无法被安全引用或拥有,因此不能作为 view 的数据源。

随机访问

这个问题来源于在最初调研时,一些 ranges::algo 可能无法处理一些 views,其原因是 一些 views 会改变迭代器的范围,不再支持随机访问,而这些algo必须迭代器支持随机访问,如下表所示:

view 类型 底层要求 保留随机访问?
transform, take, drop 随底层
reverse 双向即可
filter, join 输入/前向

举一个例子:

auto f = v | std::views::filter(...);
std::ranges::sort(f); // 编译不过

由于 filter 是在底层数据上按条件筛选后构建出的一个新的逻辑视图,它并不拥有数据本身,也不再保证连续存储或随机访问能力,无法在常数时间内完成任意位置的访问或交换;ranges::sort 算法在执行时需要对元素进行就地移动和交换,要求底层区间必须支持 随机访问迭代器,因此无法对其直接操作,编译也会报错。

c++23添加

主要是 添加了 ranges:to<container>()这个函数,将views直接物化为指定的 container

注意这个函数需要c++23才可以使用

后续有遇到什么继续添加 🤗

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/949135.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

qoj14458. 调色滤镜

qoj14458. 调色滤镜 平面 \([1,10^9]\times[1,10^9]\) 上有 \(n\) 个点,点 \(i\) 位于 \((x,y)\),有颜色 \(c_i\in [0,9]\)。 有 \(q\) 次操作,每次对平面上一个矩形范围内的点的颜色作用映射 \(f:[0,9]\rightarrow…

第8天(中等题 不定长滑动窗口、哈希表)

打卡第八天 3道中等题滑动窗口相当于在维护一个队列。右指针的移动可以视作入队,左指针的移动可以视作出队。 熟练度+++ 可以十几分钟独立写出相似题了^O^/ 耗时≈一小时 明天继续

P10259 [COCI 2023/2024 #5] Piratski kod

题链 题意 首先,题目写的很抽象,模拟赛时读了半个小时才读懂 题意概括一下就是枚举长度为k的所有01串 然后对01串进行划分,每遇到两个1就进行一次划分 然后把每段提取出来单独处理 如果把提出来的01串计为\(s[1...r]\)…

巧用 using 作用域(IDisposable)的生命周期包装特性 实现前后置处理

需求:在多个方法前后输出日志 logger.Info("begin"); method(); logger.Info("end");如果需要在方法后输出日志同时加上时长 logger.Info("begin"); var sw= Stopwatch.StartNew(); me…

2025.10.27训练记录

其实是10.28晚上写的。感觉就这个题要记录一下。 上午noip模拟。喜提一道不会。 B 题外话: 7:45 开始考试,广附集训爷大吼一声我做过!声称A完全不可做,但B他场切了。于是我开场看B。 那就看B,7:50闭了一下眼睛,睁…

软考复习总结

距离软考还有不到十天,主要对学习的知识点进行总结回顾(以下知识点无顺序重点): 1.对于尾数用补码进行表示时,要注意如果机器位8位,已知补码包含一位符号位,则补码真值的范围(-2n-1,2n-1 - 1), 则将其转换为…

实用指南:Eclipse 透视图(Perspective)

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

? #6

100 + 0 + 80 + 0 = 180, Rank 2/6.2024暑期CSP-S&NOIP模拟赛第8套题⾯ 链接:link 题解:暂无 时间:4h (2025.10.28 14:00~18:00) 题目数:4 难度:A B C D\(\color{#F39C11} 橙\) \(\color{#FFC116} 黄\)*1000 …

鲜花:不会说明你有抑郁症3

这回是广为人知题了。自然数幂和:给定 \(n,k\),求 \(\sum_{i=1}^{n} i^k\),\(n\le 10^{18},k\le 2000\)。本来以为用扰动法求自然数幂和很猎奇,结果一搜出来十来篇博客。唉我还是太菜了。 尝试扰动法处理。 设: \…

算法竞赛知识点速通手册

1. 基础贪心:邻项交换与证明 贪心算法的精髓在于“局部最优”导向“全局最优”。然而,其正确性并非总是显而易见的,需要严谨的数学证明。邻项交换(Exchange Argument)是证明一类排序相关贪心策略最经典、最强大的…

集训做题杂记1 - -MornStar

[CTS2024] 众生之门 小清新构造题。 观察大样例可以发现答案不大于 \(3\),感性猜测可以在路径长度不超过 \(3\) 的情况下遍历整棵树,事实也确实如此。 进一步考虑答案一般为 \(0\) 和 \(1\),只有 \(n\) 比较小或者图…

CF1909I Short Permutation Problem

CF1909I Short Permutation Problem并非独立切,大量参考题解。 对于排列计数问题,考虑三个方向:容斥、连续段DP、按顺序加数。 发现容斥和连续段DP没前途,考虑按顺序加数。从 \(1\) ~ \(n\) 加数显然是不行的,因为…

ROS1 go2 vlp16 局部避障--3 篇 - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

25.10.28随笔NOIP模拟赛总结

考试 开考看题,秒了 T1,感觉 T2 是简单 dp,T3 有点神秘不知道,T4 一眼有一个 \(\mathcal O(n^2)\)。于是顺序开题。T1 很快写了,T2 看了一个小时还是不会有点难崩,当时是很快想到一个 dp,设 \(f_{i,j,0/1}\) 表…

第二十八篇

今天是10月28号,上了铁道技术认知。

P8269 [USACO22OPEN] Visits S

P8269 [USACO22OPEN] Visits S 题解题目传送门 博客传送门 首先,每头牛牛都只有一个拜访对象,所以如果考虑图论建模的话,相当于每个点出度都是 1。这相当于图是个基环树森林(注意不只有一棵基环树),而且每个基环…

Luogu P13925 [POKATT 2024] 联合猫国 / The Paw-litical Game 题解 [ 蓝 ] [ 线性 DP ] [ 种类数观察 ]

联合猫国 去年模拟赛做过一道几乎一模一样的题,于是一眼秒了。 本题的一个结论:最终可合并的区间数为 \(\bm{O(n\log n)}\) 级别。 证明可以考虑构造出可合并区间数最多的序列,显然是所有数都相同时的区间数,可以取…

深入解析:【STM32项目开源】基于STM32的独居老人监护系统

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

CSP-S 41多校 9

10.28 (虽然但是下发文件NOIP?)10.28 CSP-S 前倒数第二场模拟赛,直接一道都没切出来。。。 再不放信心赛真要没信心了。 t1 dp题。 显然对于每一次行动都是一个背包dp。 变种在于背包更换,更换后容量重新计算。 所…

【25.10.28】模拟赛

T1 code #include<bits/stdc++.h> using namespace std; const int N=5e4+5,M=1e3+5; int n,m,ans=0; char s[N],t[M]; int nxt[M]; int f[N][M]; int g[M][30]; void getnxt(){nxt[1]=0;int j=0;for(int i=2;i&…