☰
全面深入了解C++20 范围库(std::ranges)
2026/10/7 10:25:53 网站建设 项目流程

1.核心基础概念

C++20 正式引入范围库 (std::ranges),是对传统 STL 迭代器 + 算法体系的重大升级。它基于概念 (Concept)、范围 (Range)、视图 (View)、管道语法、投影 (Projection) 五大核心能力,解决了传统 STL 写法繁琐、组合困难、易产生临时对象等问题,实现线性链式调用、惰性求值、零开销视图。

1.1.范围 (std::ranges::range)

范围是满足 range 概念的类型:只要类型拥有合法的 begin() 和 end(),返回迭代器 / 哨兵,就是一个范围。

  • 所有标准容器(std::vector/std::string/std::map/ 数组)天然是范围;
  • 范围描述一个可遍历的元素序列,不一定拥有实际数据。

传统 STL 必须传 begin()/end(),Ranges 算法直接接收整个范围,写法大幅简化。

C++标准库之std::begin、std::end、std::pre和std::next

1.2.哨兵 (Sentinel)

C++20 哨兵 (Sentinel) 是范围库 (Ranges) 引入的核心概念之一,是对传统迭代器模型的重大泛化。它彻底改变了序列结束位置的表示方式,允许迭代器与结束标记类型不同,从而支持无界范围、自定义终止条件、优化边界判断等场景,为现代 C++ 序列处理提供了极大灵活性。

哨兵是标记范围 (Range) 结束位置的对象,满足 std::sentinel_for<I> 概念,其中 I 是对应的起始迭代器类型。它的核心作用是与迭代器进行相等性比较,判断遍历是否结束,而不必与迭代器是同一类型。

C++20 范围的定义:一个对象只要提供 begin()(返回迭代器)和 end()(返回哨兵),且满足 sentinel_for<decltype(end()), decltype(begin())>,就是合法的范围。

与传统迭代器的根本区别:

特性传统 STL 迭代器对C++20 迭代器 + 哨兵
类型要求begin() 和 end() 必须返回同类型迭代器end() 可返回任意类型哨兵,只要满足 sentinel_for 概念
结束判断迭代器相等比较迭代器与哨兵的自定义比较逻辑
适用场景有限、已知边界的序列有限 / 无限序列、自定义终止条件、输入流等
性能边界判断固定为指针比较可优化为更高效的判断逻辑(如直接检查值)

核心概念:sentinel_for 与 sized_sentinel_for:

  • std::sentinel_for<S, I>:核心约束,要求 S 类型对象可与 I 类型迭代器比较(operator==),判断是否到达序列末尾
  • std::sized_sentinel_for<S, I>:增强约束,要求哨兵与迭代器之间可计算距离(operator-),支持 std::ranges::distance 等算法

简化示例:值边界哨兵

更简洁的哨兵实现,用于固定值边界判断:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

#include <ranges>

#include <iostream>

// 边界哨兵:当迭代器值等于bound时终止

template<typenameT>

structBoundSentinel {

T bound;

// 比较函数:迭代器值 == 边界值时终止

friendbooloperator==(constauto& it,constBoundSentinel& sentinel) {

return*it == sentinel.bound;

}

friendbooloperator!=(constauto& it,constBoundSentinel& sentinel) {

return!(it == sentinel);

}

};

intmain() {

intarr[] = {1, 2, 3, 4, 5, 0, 6, 7};// 0作为终止标记

auto range = std::ranges::subrange(std::begin(arr), BoundSentinel<int>{0});

for(intx : range) {

std::cout << x <<" ";// 输出 1 2 3 4 5

}

return0;

}

1.3.视图 (std::ranges::view)

视图是 Ranges 最核心的组件,也是和普通范围最大的区别:

  • 轻量级、非拥有:视图不拷贝、不持有原始数据,仅对原有范围做 “遍历规则包装”;
  • 廉价拷贝 / 移动:拷贝视图几乎无开销;
  • 惰性求值 (Lazy):仅在实际遍历时才计算元素,链式组合不会产生中间临时容器;
  • 不修改原数据:所有视图都是只读变换。

区分:范围 = 序列本身,视图 = 序列的 “透 视滤镜”。

1.4.借用范围 (borrowed_range)

用于保证临时范围可以被视图安全引用,避免悬垂引用。

例如 std::vector{1,2,3} | filter(...),临时容器生命周期会被视图合理托管。

1.5.管道语法|

Ranges 专属链式语法,格式:

原始范围 | 适配器1 | 适配器2 | 适配器3

等价于函数嵌套:适配器3(适配器2(适配器1(原始范围))),从左到右依次执行,代码线性可读。

2.std::ranges 整体分类

范围库全部位于 std::ranges 命名空间下,分为四大模块:

  • 范围算法:对标传统 STL 算法,增强参数、支持范围 / 投影;
  • 视图适配器:基于已有范围生成新视图(过滤、变换、截取等);
  • 视图工厂:不依赖原有范围,直接生成全新范围(如连续数列);
  • 范围概念:模板约束(range/view/input_range 等,用于模板编程)。

3.范围算法(std::ranges 算法)

3.1.语法差异

形式1:范围重载(推荐,日常首选)

传统 STL 算法:必须传递首尾迭代器

1

std::sort(vec.begin(), vec.end());

C++20 Ranges 算法:直接传入整个范围

1

2

3

4

5

// 模板原型

template<std::ranges::input_range R, ...>

void算法名(R&& range, 其他参数...);

std::ranges::sort(vec);

形式2:迭代器 + 哨兵重载(兼容传统 / 自定义哨兵)

兼容旧式迭代器写法,同时原生支持迭代器与哨兵类型不同(C++20 哨兵特性),传统 STL 算法强制首尾迭代器同类型。

1

2

3

4

5

6

7

8

9

// 模板原型

template<std::input_iterator I, std::sentinel_for<I> S, ...>

void算法名(I first, S last, 其他参数...);

// 示例:传统迭代器 + 标准哨兵

std::ranges::sort(vec.begin(), vec.end());

// 示例:迭代器 + 自定义哨兵(C字符串)

auto cstr ="hello";

std::ranges::for_each(cstr, std::default_sentinel, [](charc){});

3.2.核心增强:投影 (Projection)

投影是一个可调用对象,接收单个元素,返回参与算法逻辑的值,全程只读、不修改原数据。 专门用于提取结构体成员、计算衍生值,大幅简化结构体 /std::pair 的排序、查找、统计逻辑。

绝大多数 Ranges 算法新增投影参数,用于提取元素的成员 / 属性,替代繁琐的 Lambda,是高频用法。

语法规则(通用):

1

std::ranges::算法(范围, 谓词, 投影函数);

  • 投影函数:接收单个元素,返回用于比较 / 判断的值;
  • 投影不会修改原元素,仅做 “取值映射”。

三种主流写法:

1.成员函数指针(最常用,结构体优先)

1

2

3

structStudent { std::string name;intscore; };

// 投影:提取 score 成员

std::ranges::sort(stus, std::ranges::less<>, &Student::score);

2.Lambda 表达式(自定义计算 / 复杂取值)

1

2

// 投影:取元素绝对值参与比较

std::ranges::sort(vec, std::ranges::less<>, [](intx){returnstd::abs(x); });

3.普通函数 / 函数对象(逻辑复用)

核心特点:

  • 投影不修改原元素,仅做「值映射」;
  • 算法先执行投影,再用投影结果做比较 / 判断;
  • 所有谓词、比较器接收的都是投影后的值。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询