全面深入了解C++20 范围库(std::ranges)

 更新时间:2026年06月30日 09:28:02   作者:流星雨爱编程  
C++20范围库(std::ranges)是对传统STL的重大升级,它解决了传统STL的繁琐写法、组合困难等问题,支持线性链式调用和惰性求值,感兴趣的可以了解一下

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 等算法

简化示例:值边界哨兵

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

#include <ranges>
#include <iostream>

// 边界哨兵:当迭代器值等于bound时终止
template <typename T>
struct BoundSentinel {
    T bound;
    // 比较函数:迭代器值 == 边界值时终止
    friend bool operator==(const auto& it, const BoundSentinel& sentinel) {
        return *it == sentinel.bound;
    }
    friend bool operator!=(const auto& it, const BoundSentinel& sentinel) {
        return !(it == sentinel);
    }
};

int main() {
    int arr[] = {1, 2, 3, 4, 5, 0, 6, 7}; // 0作为终止标记
    auto range = std::ranges::subrange(std::begin(arr), BoundSentinel<int>{0});
    
    for (int x : range) {
        std::cout << x << " "; // 输出 1 2 3 4 5
    }
    return 0;
}

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 算法:必须传递首尾迭代器

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

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

// 模板原型
template <std::ranges::input_range R, ...>
void 算法名(R&& range, 其他参数...);

std::ranges::sort(vec);

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

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

// 模板原型
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, [](char c){});

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

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

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

语法规则(通用):        

std::ranges::算法(范围, 谓词, 投影函数);
  • 投影函数:接收单个元素,返回用于比较 / 判断的值;
  • 投影不会修改原元素,仅做 “取值映射”。

三种主流写法:

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

struct Student { std::string name; int score; };
// 投影:提取 score 成员
std::ranges::sort(stus, std::ranges::less<>, &Student::score);

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

// 投影:取元素绝对值参与比较
std::ranges::sort(vec, std::ranges::less<>, [](int x){ return std::abs(x); });

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

核心特点:

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

3.3.算法详解

绝大多数范围算法遵循固定参数顺序,按功能分为 5 大类,记住规则就不用死记函数签名:

算法分类通用参数顺序代表算法
等值匹配类范围, 目标值, 投影 = std::identitycount / find / contains
谓词判断类范围, 谓词函数, 投影 = std::identityall_of / find_if / count_if
比较排序类范围, 比较器 = ranges::less<>, 投影 = std::identitysort / max_element / partition
复制 / 变换类源范围, 目标迭代器, [谓词/变换函数]copy / copy_if / transform
条件删除类(专属)容器, [目标值/谓词], 投影 = std::identityerase / erase_if

3.3.1.遍历 & 逻辑判断算法

纯只读操作,不修改原范围,用于遍历元素、批量判断整体条件。

1.std::ranges::for_each 遍历执行

对每个元素执行自定义操作,等价传统 std::for_each。

int main() {
    std::vector<int> vec = {1,2,3,4,5};

    // 基础遍历
    std::ranges::for_each(vec, [](int x){
        std::cout << x << " ";
    });
    return 0;
}

2.all_of / any_of / none_of 全局条件判断

  • all_of:所有元素满足条件 → true
  • any_of:至少一个元素满足条件 → true
  • none_of:没有元素满足条件 → true
struct Student {
    std::string name;
    int score;
};

int main() {
    std::vector<Student> stus = {{"A", 85}, {"B", 59}, {"C", 90}};

    // 投影取 score:判断是否全部及格(>=60)
    bool all_pass = std::ranges::all_of(stus, [](int s){ return s >= 60; }, &Student::score);
    // 判断是否存在满分
    bool has_full = std::ranges::any_of(stus, [](int s){ return s == 100; }, &Student::score);
    // 判断是否无人不及格
    bool no_fail = std::ranges::none_of(stus, [](int s){ return s < 60; }, &Student::score);

    std::cout << std::boolalpha << all_pass << " " << has_full << " " << no_fail;
    return 0;
}

3.3.2.查找算法

定位元素位置,全部返回 subrange,支持投影,分为正向查找、反向查找、存在性判断。

1.find / find_if / find_if_not 正向查找

  • find(range, val):查找第一个等于目标值的元素
  • find_if(range, pred):查找第一个满足谓词的元素
  • find_if_not(range, pred):查找第一个不满足谓词的元素
int main() {
    std::vector<int> vec = {10,20,30,40,50};

    // 查找值为30的元素
    auto r1 = std::ranges::find(vec, 30);
    if (!r1.empty()) std::cout << "找到:" << *r1.begin() << "\n";

    // 查找第一个大于25的元素
    auto r2 = std::ranges::find_if(vec, [](int x){ return x > 25; });
    return 0;
}

2.find_last / find_last_if 反向查找(C++20 新增)

从尾部向前查找,传统 STL 需要手写反向迭代器,此处写法大幅简化。

std::vector<int> vec = {2, 1, 2, 3, 2};
auto last_two = std::ranges::find_last(vec, 2); // 最后一个 2

3.contains 存在性判断(极简接口)

直接返回 bool,判断范围是否包含目标值,无需判断迭代器 / 子范围。

std::vector<int> vec = {1,2,3,4};
bool has_3 = std::ranges::contains(vec, 3);  // true
bool has_9 = std::ranges::contains(vec, 9);  // false

3.3.3.计数算法

统计符合条件的元素数量,支持投影。

1.count 统计等值元素

统计等于目标值的元素个数。

2.count_if 统计条件元素

统计满足谓词的元素个数。

struct User {
    std::string name;
    int age;
};

int main() {
    std::vector<User> users = {{"Tom", 18}, {"Lily", 22}, {"Jack", 18}};

    // 投影 age:统计 18 岁人数
    int cnt18 = std::ranges::count(users, 18, &User::age);
    // 统计年龄 > 20 的人数
    int cntAdult = std::ranges::count_if(users, [](int a){ return a > 20; }, &User::age);

    std::cout << "18岁人数:" << cnt18 << "\n";
    return 0;
}

3.3.4.最值算法

查找最大 / 最小元素,返回 subrange;minmax_element 一次遍历同时获取最值,效率更高。

struct Player {
    std::string name;
    int level;
};

int main() {
    std::vector<Player> players = {{"P1", 10}, {"P2", 20}, {"P3", 15}};

    // 等级最高的玩家
    auto maxLv = std::ranges::max_element(players, std::ranges::less<>, &Player::level);
    // 同时获取最小、最大等级
    auto [minR, maxR] = std::ranges::minmax_element(players, std::ranges::less<>, &Player::level);

    std::cout << "最高等级:" << maxR->level;
    return 0;
}

3.3.5.排序 & 分区算法

原地修改范围顺序,投影在此类算法中价值最高,是工程最常用算法。

1.sort / stable_sort 排序

  • sort:快速排序,不稳定排序(相等元素相对位置改变)
  • stable_sort:稳定排序,保留相等元素原有相对位置

语法:sort(范围, 比较器 = ranges::less<>, 投影 = identity)

struct Goods {
    std::string name;
    double price;
    int stock;
};

int main() {
    std::vector<Goods> goods = {
        {"Apple", 5.5, 100},
        {"Banana", 3.2, 200},
        {"Orange", 4.0, 80}
    };

    // 1. 按价格【升序】(默认 less<>)
    std::ranges::sort(goods, std::ranges::less<>, &Goods::price);
    // 2. 按库存【降序】(使用 greater<>)
    std::ranges::sort(goods, std::ranges::greater<>, &Goods::stock);

    return 0;
}

2.partial_sort 局部排序

仅对前 N 个元素排序,剩余元素无序,适合「取 TopN」场景,性能优于全排序。

std::vector<int> vec = {5,3,9,1,7,2};
// 只排序前 3 个元素
std::ranges::partial_sort(vec, vec.begin() + 3);
// 结果:1 2 3 | 9 7 5(后半段无序)

3.nth_element 第 N 元素定位

将第 N 个位置放置全局第 N 小元素,左侧都 ≤ 它,右侧都 ≥ 它,两侧无序。常用于快速取中位数、TopK。

std::vector<int> vec = {9,5,1,7,3};
// 让第2个位置(下标2)为全局第3小元素
std::ranges::nth_element(vec, vec.begin() + 2);

4.partition / stable_partition 分区

按条件将范围分为两部分:满足条件在前,不满足在后。

  • partition:不稳定分区
  • stable_partition:稳定分区,保留分组内原有顺序
std::vector<int> vec = {1,2,3,4,5,6};
// 偶数放前面,奇数放后面
std::ranges::partition(vec, [](int x){ return x % 2 == 0; });

3.3.6.二分查找算法(要求范围必须升序有序)

基于二分查找,时间复杂度 (O(log N)),无序范围使用会导致未定义行为。

算法功能返回值
binary_search判断元素是否存在bool
lower_bound第一个 ≥ 目标值 的位置subrange
upper_bound第一个 > 目标值 的位置subrange
equal_range所有等于目标值的连续区间subrange
int main() {
    std::vector<int> vec = {1,2,3,4,5,6}; // 必须有序

    bool exist = std::ranges::binary_search(vec, 4); // true
    auto left  = std::ranges::lower_bound(vec, 3);
    auto right = std::ranges::upper_bound(vec, 3);
    auto eqRange = std::ranges::equal_range(vec, 3);

    return 0;
}

3.3.7.反转、旋转、相邻去重(原地修改)

1.reverse 原地反转

  • std::ranges::reverse(vec):算法,原地修改容器顺序;
  • vec | std::ranges::reverse:视图,仅改变遍历顺序,原数据不变。
std::vector<int> vec = {1,2,3};
std::ranges::reverse(vec); // 原地变为 {3,2,1}

2.rotate 序列旋转

以指定迭代器为新起点,循环平移整个序列。

std::vector<int> vec = {1,2,3,4,5};
// 从第3个元素开始旋转 → 3 4 5 1 2
std::ranges::rotate(vec, vec.begin() + 2);

3.unique 相邻去重

仅删除相邻重复元素(和传统行为一致),重复元素会被移到尾部,通常配合 erase 彻底删除。

std::vector<int> vec = {1,1,2,2,3,3};
auto newEnd = std::ranges::unique(vec); // 去重,尾部残留重复值
vec.erase(newEnd.begin(), newEnd.end()); // 彻底清理

全局去重标准流程:sort → unique → erase

3.3.8.复制、变换、填充算法

用于元素拷贝、类型转换、批量赋值,常配合 std::back_inserter 动态扩容目标容器。

1.copy / copy_if 复制

  • copy:完整复制所有元素
  • copy_if:仅复制满足条件的元素
std::vector<int> src = {1,2,3,4,5};
std::vector<int> dst;

// 复制所有偶数
std::ranges::copy_if(src, std::back_inserter(dst), [](int x){ return x%2 == 0; });

2.transform 元素变换

对每个元素执行映射转换,支持单范围和双范围重载。

std::vector<int> src = {1,2,3};
std::vector<int> dst;

// 所有元素 *2 后复制
std::ranges::transform(src, std::back_inserter(dst), [](int x){ return x * 2; });

3.fill / generate 批量填充

  • fill:用固定值填充范围
  • generate:用生成器函数动态生成元素
std::vector<int> vec(5);
std::ranges::fill(vec, 0);                // 全部填 0
std::ranges::generate(vec, [](){         // 生成 1,2,3,4,5
    static int n = 1;
    return n++;
});

3.3.9.元素删除算法(C++20 重磅优化)

彻底淘汰传统 erase + remove 老旧范式,一键删除,代码极简。

1.std::ranges::erase 删除指定值

删除容器中所有等于目标值的元素,直接修改容器。

std::vector<int> vec = {2,1,2,3,2};
std::ranges::erase(vec, 2); // 删除所有 2 → {1,3}

2.std::ranges::erase_if 条件删除

删除所有满足谓词的元素,支持投影(结构体场景利器)。

struct User { std::string name; int age; };
std::vector<User> users = {{"U1", 16}, {"U2", 20}, {"U3", 15}};

// 投影 age:删除年龄 < 18 的用户
std::ranges::erase_if(users, [](int a){ return a < 18; }, &User::age);

对比传统写法(冗余且易错):

// 传统 STL 写法(已淘汰) vec.erase(std::remove(vec.begin(), vec.end(), 2), vec.end());

3.3.10.有序集合算法(两个输入范围都必须升序有序)

对两个有序范围做集合运算,结果输出到目标迭代器。

  • set_union:并集
  • set_intersection:交集
  • set_difference:差集(A - B)
  • set_symmetric_difference:对称差集(只在其中一个集合出现)
int main() {
    std::vector<int> a = {1,2,3,4};
    std::vector<int> b = {3,4,5,6};
    std::vector<int> res;

    // 求交集 {3,4}
    std::ranges::set_intersection(a, b, std::back_inserter(res));
    return 0;
}

4.视图适配器 & 视图工厂

视图是 Ranges 组合能力的核心,惰性求值、无临时内存分配,通过管道 | 链式组合。

  • 视图适配器:作用在已有范围上,生成变换后的视图;
  • 视图工厂:不依赖原有范围,直接生成新序列。

4.1.常用视图适配器(C++20 标准)

1.std::ranges::filter 过滤

保留谓词返回 true 的元素。

std::vector<int> vec = {1,2,3,4,5,6};

// 过滤偶数
auto even_view = vec | std::ranges::filter([](int x) { return x % 2 == 0; });

for (int x : even_view) std::cout << x << " "; // 2 4 6

2.std::ranges::transform 元素变换

对每个元素执行映射转换。

// 所有元素 * 2
auto double_view = vec | std::ranges::transform([](int x) { return x * 2; });

3.take / take_while 截取前缀

  • take(N):取前 N 个元素;
  • take_while(谓词):直到条件不成立为止。
// 取前3个元素
auto take3 = vec | std::ranges::take(3); // 1 2 3

// 取小于4的元素
auto take_less4 = vec | std::ranges::take_while([](int x){ return x < 4; });

4.drop / drop_while 跳过前缀

  • drop(N):跳过前 N 个元素;
  • drop_while(谓词):跳过开头满足条件的元素。
auto drop2 = vec | std::ranges::drop(2); // 3 4 5 6

5.reverse 逆序视图

仅改变遍历顺序,不修改原容器(和 std::ranges::reverse 算法区分)。

auto rev_view = vec | std::ranges::reverse;

6.keys / values 键值视图

专门用于 std::map、std::pair 序列,快速提取键 / 值。

#include <map>
std::map<std::string, int> mp = {{"a",1}, {"b",2}};

// 提取所有 key
auto keys = mp | std::ranges::keys;
// 提取所有 value
auto vals = mp | std::ranges::values;

7.split 分割范围

按分隔符拆分序列,返回范围的范围(二维视图),常配合 join 使用。

#include <string>
std::string str = "hello,world,cpp20";

// 按逗号分割
auto split_view = str | std::ranges::split(',');

8.join 扁平化嵌套范围

将二维 / 多层视图拍平为一维。

// 分割后扁平化遍历
for (auto sub : str | std::ranges::split(',') | std::ranges::join) {
    std::cout << sub;
}

9.unique 相邻去重视图

仅去除相邻重复元素(和 std::unique 行为一致)。

4.2.视图工厂(生成新范围)

1.std::ranges::iota 连续数列

生成递增整数序列,支持有限范围、无限范围。

// 有限范围:[0, 10)
auto nums = std::ranges::iota(0, 10);

// 无限范围:从0开始无限递增(必须搭配 take 截断)
auto inf_nums = std::ranges::iota(0) | std::ranges::take(5);

2.single_view / empty_view

  • single_view(x):仅包含单个元素的视图;
  • empty_view<T>():空视图。

4.3.链式管道组合

多适配器串联,一行实现复杂逻辑,全程无临时容器:

#include <iostream>
#include <vector>
#include <ranges>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    // 逻辑:过滤奇数 → 元素*3 → 跳过前2个 → 取前3个 → 逆序
    auto res_view = vec
        | std::ranges::filter([](int x) { return x % 2 == 1; })  // 奇数:1,3,5,7,9
        | std::ranges::transform([](int x) { return x * 3; })    // 3,9,15,21,27
        | std::ranges::drop(2)                                   // 15,21,27
        | std::ranges::take(3)
        | std::ranges::reverse;

    for (int x : res_view) {
        std::cout << x << " "; // 27 21 15
    }

    return 0;
}

5.std::ranges::subrange子范围

绝大多数查找 / 区间算法返回 subrange,它是轻量级范围包装器:

  • 内部仅存储一对「迭代器 / 哨兵」,不拷贝数据;
  • 本身是合法 range,可直接遍历、接入管道视图、调用范围算法;
  • 常用成员:
    • .empty():判断是否为空(推荐,替代迭代器比较)
    • .begin() / .end():获取首尾迭代器 / 哨兵
    • 支持范围 for 遍历

示例如下:

std::vector<int> vec = {1,2,3,4,5,6};
// 找到 3 之后的所有元素,再过滤偶数
auto sub = std::ranges::find(vec, 3);
for (int x : sub | std::ranges::filter([](int x){ return x%2==0; })) {
    std::cout << x << " "; // 4 6
}

6.C++20 Ranges vs 传统 STL 写法 对比

C++20 范围库(std::ranges)完全兼容传统 STL 算法语义,但在调用形式、语法简洁度、组合能力、运行效率、容错性上做了全面革新。

6.1.整体对比

对比维度传统 STL(C++11~C++17)C++20 std::ranges
调用参数强制传 begin() + end() 迭代器对支持直接传整个容器 / 范围,也兼容迭代器对
成员取值依赖 Lambda 内部取结构体 / 成员,代码臃肿原生支持投影 (Projection),一行指定字段
结束标记begin/end 迭代器必须同类型支持哨兵 (Sentinel),迭代器与结束标记可不同类型
多步组合多步处理必须创建临时容器存中间结果视图 (View) 惰性求值,无临时容器,管道 `` 链式串联
返回值查找类返回原始迭代器查找 / 区间类统一返回 std::ranges::subrange(可继续遍历 / 链式调用)
删除逻辑固定 erase + remove 嵌套范式,易错专用 erase/erase_if,一键删除,语法极简
代码风格嵌套多、模板长、重复代码多线性链式、声明式风格,逻辑从上到下一目了然
运行开销多步变换伴随容器拷贝 / 内存分配视图零拷贝、惰性执行,性能等价手写循环
边界判断手动比较迭代器,语义晦涩subrange.empty() 直观判空,哨兵封装终止逻辑

6.2.分场景代码对比

1.结构体(投影核心场景)

需求:判断学生是否全部及格(按 score 字段)

struct Student {
    std::string name;
    int score;
};

传统 STL 写法: Lambda 内部手动提取成员,元素多时代码冗余

std::vector<Student> stus = {{"A",85}, {"B",66}, {"C",92}};
bool all_pass = std::all_of(stus.begin(), stus.end(), [](const Student& s){
    return s.score >= 60; // 每次都要手动取成员
});

C++20 Ranges 写法: 使用投影直接指定成员指针,谓词只关心业务逻辑

std::vector<Student> stus = {{"A",85}, {"B",66}, {"C",92}};
// 投影 &Student::score:算法自动提取分数,谓词接收 int 类型
bool all_pass = std::ranges::all_of(stus, [](int s){
    return s >= 60;
}, &Student::score);

差异总结:结构体场景下,投影彻底简化成员取值逻辑,Lambda 职责更单一。

2.元素删除(经典 erase-remove 范式)

这是传统 STL 最易错、最繁琐的场景之一,C++20 做了彻底优化。

传统STL:

users.erase(std::remove_if(users.begin(), users.end(),
    [](const User& u){ return u.age < 18; }),
    users.end());

Ranges 写法(搭配投影)

// 投影 age,谓词直接接收年龄值
std::ranges::erase_if(users, [](int a){ return a < 18; }, &User::age);
  • 传统 erase+remove 嵌套层级深,新手极易写错迭代器;
  • Ranges erase/erase_if 语义直白,一眼看懂逻辑。

3.多步序列流水线(过滤 + 变换 + 截取)

需求:从序列中 过滤奇数 → 元素 ×3 → 跳过前 2 个 → 取前 3 个

每一步都必须新建 vector 存储中间结果,多次内存分配 + 拷贝,性能损耗明显

int main() {
    std::vector<int> src = {1,2,3,4,5,6,7,8,9,10};

    // 第1步:过滤奇数(临时容器1)
    std::vector<int> temp1;
    std::copy_if(src.begin(), src.end(), std::back_inserter(temp1),
        [](int x){ return x % 2 == 1; });

    // 第2步:元素 *3(临时容器2)
    std::vector<int> temp2;
    std::transform(temp1.begin(), temp1.end(), std::back_inserter(temp2),
        [](int x){ return x * 3; });

    // 第3步:跳过前2个,取前3个(最终结果)
    std::vector<int> res;
    auto first = temp2.begin() + 2;
    auto last = first + 3;
    std::copy(first, last, std::back_inserter(res));

    // 遍历输出
    for (int x : res) std::cout << x << " ";
    return 0;
}

C++20 Ranges 视图 + 管道写法:

使用视图 (View) + 管道 | 链式组合,全程无临时容器、惰性求值,只有遍历瞬间才执行逻辑

int main() {
    std::vector<int> src = {1,2,3,4,5,6,7,8,9,10};

    // 链式流水线:从上到下顺序执行,无中间拷贝
    auto pipeline = src
        | std::ranges::filter([](int x){ return x % 2 == 1; })  // 过滤奇数
        | std::ranges::transform([](int x){ return x * 3; })     // 元素*3
        | std::ranges::drop(2)                                  // 跳过前2个
        | std::ranges::take(3);                                 // 取前3个

    // 仅遍历这一刻才执行所有逻辑
    for (int x : pipeline) std::cout << x << " ";
    return 0;
}
  • 传统:多层临时容器,内存开销大、代码碎片化;
  • Ranges:视图轻量包装、零拷贝,代码线性可读、性能更高;
  • 视图是惰性:定义流水线时不执行,遍历才执行。

4.C 风格字符串(哨兵 Sentinel 典型应用)

需求:遍历 \0 结尾的 C 字符串

传统写法:手动循环判断结束符 \0,或依赖迭代器指针

const char* str = "Hello C++";
while (*str != '\0') {
    std::cout << *str;
    ++str;
}

Ranges 写法(结合默认哨兵 default_sentinel):

将 C 字符串包装为标准范围,哨兵自动识别 \0,无需手动判断终止条件

#include <ranges>
const char* str = "Hello C++";
auto str_range = std::ranges::subrange(str, std::default_sentinel);
std::ranges::for_each(str_range, [](char c){ std::cout << c; });

差异总结:哨兵抹平了 “原生指针序列” 和 “标准容器” 的遍历差异。

6.3.核心特性深度对比(底层能力差异)

1.投影 (Projection)

  • 传统:无原生支持,所有成员取值、字段比较都塞在 Lambda 中,逻辑耦合;
  • Ranges:算法内置投影参数,数据提取、业务判断、比较逻辑三者分离,代码复用率大幅提升。

2. 哨兵 (Sentinel)

  • 传统:强制 begin/end 同类型,无法表达 “自定义终止条件、无限序列、C 字符串 / 流”;
  • Ranges:迭代器与结束标记可异构,原生支持无界范围、EOF、\0 等场景。

3. 视图 (View) 与 临时对象

  • 传统:多步变换 → 必产生临时容器,触发内存分配、元素拷贝;
  • Ranges:视图是轻量包装器,不持有数据、惰性执行,零额外内存开销,性能接近手写循环。

4. 管道语法 |

  • 传统:算法只能嵌套调用,多层处理后代码向右 “阶梯式缩进”,可读性差;
  • Ranges:管道从左到右线性串联,逻辑顺序和代码书写顺序完全一致。

5. 返回值 subrange

  • 传统:原始迭代器,跨函数传递、二次使用易失效、语义模糊;
  • Ranges:subrange 是独立范围类型,支持遍历、判空、再次接入算法 / 视图,链式能力更强

6.4.性能对比

  • 纯算法层:std::ranges 算法底层复用传统 STL 实现,性能完全持平;
  • 多步序列处理:Ranges 视图完胜,消除临时容器拷贝,内存占用更低;
  • 结构体场景:投影只是编译期映射,无运行时开销;
  • 哨兵场景:部分场景可优化边界判断逻辑,小幅提升效率。

一句话:Ranges 是语法糖 + 架构升级,不牺牲性能,只提升开发效率与代码质量。

7.迁移与使用建议

1.新项目

直接全面使用 std::ranges:

  • 容器遍历、排序、删除、筛选优先用范围算法;
  • 多步变换优先用视图 + 管道;
  • 结构体排序 / 统计优先使用投影。

2.老项目逐步迁移

  • 简单算法(for_each/sort/find):直接替换为 std::ranges::xxx,改动极小;
  • erase+remove 范式:优先改成 ranges::erase/erase_if,减少 bug;
  • 复杂多步处理:局部改用视图管道,不改动原有架构。

3.避坑要点

  • 区分 范围算法(原地修改) 和 视图(只读);
  • 无限视图(如 iota(0))必须用 take/take_while 截断,防止死循环;
  • 视图不持有数据,保证原始范围生命周期长于视图;
  • 二分、集合算法依然要求范围有序,规则和传统一致;
  • 迭代器失效规则与传统 STL 完全相同。

8.C++23 对 Ranges 的扩展(补充)

C++23 在 C++20 基础上新增大量实用适配器,进一步强化组合能力:

  • zip / zip_transform:多范围并行打包;
  • chunk:按指定大小分块;
  • adjacent:相邻元素视图;
  • slide:滑动窗口视图;
  • 更多算法、投影、常量表达式支持。

9.总结

  • 定位:C++20 Ranges 是现代 C++ 序列处理的新标准,替代传统迭代器 + 算法;
  • 核心优势:语法简洁、链式可读、惰性求值(高性能)、原生投影、组合能力极强;
  • 使用原则:优先用视图做变换,必要时物化容器;区分「视图(只读)」和「算法(原地修改)」;

到此这篇关于全面深入了解C++20 范围库(std::ranges)的文章就介绍到这了,更多相关C++20 范围库内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Python绘图之详解matplotlib

    Python绘图之详解matplotlib

    这篇文章主要介绍了Python绘图之详解matplotlib,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-07-07
  • C++实现教职工信息管理系统课程设计

    C++实现教职工信息管理系统课程设计

    这篇文章主要为大家详细介绍了C++实现教职工信息管理系统课程设计,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • C语言中指针 int *p=0;和int *p;*p=0;和”&“的关系和区别详解

    C语言中指针 int *p=0;和int *p;*p=0;和”&“的关系和区别详解

    这篇文章主要介绍了C语言中指针 int *p=0;和int *p;*p=0;和”&“有什么关系和区别,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-02-02
  • C语言详细分析贪心策略中最小生成树的Prime算法设计与实现

    C语言详细分析贪心策略中最小生成树的Prime算法设计与实现

    最小生成树的问题还是比较热门的,最经典的莫过于Prime算法和Kruskal算法了,这篇博文我会详细讲解Prime算法的设计思想与具体代码的实现,不要求数据结构学的有多好,只要跟着我的思路来,一步一步的分析,调试,终能成就自己,那就让我们开始吧
    2022-05-05
  • C++ 程序抛出异常后执行顺序说明

    C++ 程序抛出异常后执行顺序说明

    这篇文章主要介绍了C++ 程序抛出异常后执行顺序说明,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-02-02
  • Qt Design Studio创建工程的实现方法

    Qt Design Studio创建工程的实现方法

    Qt Design Studio它允许设计人员和开发人员使用通用的设计、开发、分析和调试工具在不同的开发平台上共享一个项目,本文主要介绍了Qt Design Studio创建工程的实现方法,具有一定的参考价值,感兴趣的可以了解一下
    2022-05-05
  • C语言实现页面置换 先进先出算法(FIFO)

    C语言实现页面置换 先进先出算法(FIFO)

    这篇文章主要为大家详细介绍了C语言实现页面置换,先进先出算法(FIFO),文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-12-12
  • C++条件及循环语句的综合运用实例

    C++条件及循环语句的综合运用实例

    这篇文章主要介绍了C++条件及循环语句的综合运用实例,能够帮助C++初学者更好地掌握C++的逻辑语句用法,需要的朋友可以参考下
    2015-09-09
  • Qt之实现圆形进度条的示例代码

    Qt之实现圆形进度条的示例代码

    在平时做页面开发时,有些时候会用到圆形进度条,本文主要介绍了Qt之实现圆形进度条的示例代码,具有一定的参考价值,感兴趣的可以了解一下
    2023-10-10
  • 详解C++中const_cast与reinterpret_cast运算符的用法

    详解C++中const_cast与reinterpret_cast运算符的用法

    这篇文章主要介绍了C++中const_cast与reinterpret_cast运算符的用法,经常被用于表达式中的类型转换,需要的朋友可以参考下
    2016-01-01

最新评论