当前位置: 首页 > news >正文

c++ ranges随笔

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.gsyq.cn/news/33183.html

相关文章:

  • P10259 [COCI 2023/2024 #5] Piratski kod
  • 软考复习总结
  • ? #6
  • 集训做题杂记1 - -MornStar
  • 2019 福建省队集训录
  • 实验二 现代C++编程初体验
  • ZKY精选冲刺省选国赛仿真训练题
  • MySQL 查询与更新语句执行过程深度解析:从原理到实践​ - 指南
  • ZKY精选冲刺省选国赛技巧训练题
  • 价值流智能时代:DevOps平台如何成为企业高效交付的核心引擎? - 教程
  • 2025年压力容器品牌综合实力排行榜
  • AI Agent 从零到百万价值迭代之路 - 智慧园区
  • 科幻——面包
  • 10.28代码大全2
  • 别再空谈企业架构!TOGAF的4A模型让你的技术投入至少省50%!
  • 1662
  • 基于PSO粒子群优化算法的64QAM星座图的最优概率整形matlab仿真,对比PSO优化前后整形星座图和误码率
  • C++primer 类的静态成员
  • 2025 年最佳AI智能企业知识管理工具推荐
  • 移动端性能监控探索:可观测 Android 采集探针架构与实现
  • 2025年建站AI工具TOP10盘点:从ChatGPT到Lynx的智能革命
  • CompleteMaintenance点检提交反复超时,日志显示执行中断
  • 为何AI反诈骗防护比以往任何时候都更重要
  • MySQL 数据加密整改文档(TDE + 字段加密 + 密码哈希)
  • KeyShot许可分析软件推荐
  • 2025年U型科氏质量流量计最新推荐榜:微弯型科氏质量流量计/直管型科氏质量流量计/科氏质量流量计助力产业智能化升级
  • 收藏版:Phinx 数据库迁移完全指南
  • 数据库国产化替换后,Oracle还有没有学习的价值?
  • 为什么Android游戏画面在30帧运行时有抖动现象
  • Nginx中正确配置SSE(Server-Sent Events)服务