前置知识: C++

C++20范围

00:00
4 min Advanced 2026/6/14

Ranges库与视图组合

概述

C++20 引入的 Ranges 库为标准库算法带来了范式级的变革。它将算法与数据源解耦,通过视图(View)实现惰性求值的链式管道操作,使数据处理代码更加声明式和高效。传统 STL 算法依赖迭代器对,而 Ranges 以”范围”为基本抽象,配合管道操作符 | 实现数据流的组合变换,代码可读性显著提升。

Ranges 的核心设计理念是:算法不应关心数据的存储方式,数据变换应该像流水线一样可组合。视图是轻量级的范围适配器,不会复制底层数据,仅在迭代时按需计算。

基础概念

范围(Range)

范围是对可迭代数据的抽象,任何提供 begin()end() 的类型都是范围。C++20 通过 std::ranges::range 概念约束范围类型,包括容器、数组、初始化列表等。

视图(View)

视图是惰性求值的范围适配器,具有以下特性:

  • 不拥有数据,仅引用底层范围
  • 复制、赋值和销毁的复杂度为 O(1)
  • 惰性求值,仅在迭代时才执行计算
  • 可通过管道操作符 | 链式组合

常用视图一览

视图说明
filter根据谓词过滤元素
transform对每个元素应用转换函数
take取前 n 个元素
drop跳过前 n 个元素
reverse反转元素顺序
sort排序(C++20 ranges 算法)
unique去除连续重复元素
join展平嵌套范围
zip合并多个范围(C++23)
enumerate索引遍历(C++23)
chunk(C++23)
slide滑动窗口(C++23)

快速上手

管道式数据变换

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

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

    // 链式管道:过滤偶数 -> 平方 -> 取前5个
    auto result = nums
        | std::views::filter([](int n) { return n % 2 == 0; })
        | std::views::transform([](int n) { return n * n; })
        | std::views::take(5);

    for (int n : result) {
        std::cout << n << " ";  // 输出: 4 16 36 64 100
    }
    return 0;
}

使用 iota 生成序列

#include <ranges>

// 生成 1 到 99 的整数序列
auto nums = std::views::iota(1, 100);

// 过滤偶数并求平方,取前5个
auto even_squared = nums
    | std::views::filter([](int n) { return n % 2 == 0; })
    | std::views::transform([](int n) { return n * n; })
    | std::views::take(5);

for (int n : even_squared) {
    std::cout << n << " ";  // 输出: 4 16 36 64 100
}

详细用法

filter — 条件过滤

#include <ranges>
#include <vector>
#include <string>

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

int main() {
    std::vector<Student> students = {
        {"张三", 85}, {"李四", 62}, {"王五", 91}, {"赵六", 58}
    };

    // 筛选成绩及格的学生
    auto passed = students
        | std::views::filter([](const Student& s) { return s.score >= 60; });

    for (const auto& s : passed) {
        std::cout << s.name << ": " << s.score << std::endl;
    }
    return 0;
}

transform — 元素转换

#include <ranges>
#include <vector>

int main() {
    std::vector<std::string> words = {"hello", "world", "cpp20"};

    // 转换为大写(简化示例,实际需逐字符转换)
    auto lengths = words
        | std::views::transform([](const std::string& s) { return s.size(); });

    for (size_t len : lengths) {
        std::cout << len << " ";  // 输出: 5 5 4
    }
    return 0;
}

take 和 drop — 截取与跳过

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

int main() {
    std::vector<int> data = {10, 20, 30, 40, 50, 60, 70};

    // 取前3个
    auto first3 = data | std::views::take(3);
    for (int n : first3) std::cout << n << " ";  // 10 20 30
    std::cout << std::endl;

    // 跳过前3个
    auto skip3 = data | std::views::drop(3);
    for (int n : skip3) std::cout << n << " ";  // 40 50 60 70
    std::cout << std::endl;

    // 组合使用:取中间部分
    auto middle = data | std::views::drop(2) | std::views::take(3);
    for (int n : middle) std::cout << n << " ";  // 30 40 50
    return 0;
}

reverse 和 keys/values

#include <ranges>
#include <map>
#include <iostream>

int main() {
    std::map<std::string, int> scores = {
        {"语文", 90}, {"数学", 85}, {"英语", 92}
    };

    // 遍历键
    for (const auto& key : std::views::keys(scores)) {
        std::cout << key << " ";  // 数学 英语 语文(按字典序)
    }
    std::cout << std::endl;

    // 遍历值并反转
    auto reversed_values = std::views::values(scores) | std::views::reverse;
    for (int v : reversed_values) {
        std::cout << v << " ";  // 92 85 90
    }
    return 0;
}

join — 展平嵌套范围

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

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

    // 展平为一维
    auto flat = nested | std::views::join;

    for (int n : flat) {
        std::cout << n << " ";  // 输出: 1 2 3 4 5 6
    }
    return 0;
}

常见场景

数据清洗管道

#include <ranges>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> raw_data = {0, -3, 5, 0, 8, -1, 12, 0, 7};

    // 清洗流程:去除零值 -> 过滤负数 -> 排序 -> 去重
    auto cleaned = raw_data
        | std::views::filter([](int n) { return n != 0; })
        | std::views::filter([](int n) { return n > 0; });

    // 排序和去重需要复制到容器(sort 会修改原数据)
    std::vector<int> result(cleaned.begin(), cleaned.end());
    std::ranges::sort(result);
    result.erase(std::ranges::unique(result).begin(), result.end());

    for (int n : result) {
        std::cout << n << " ";  // 输出: 5 7 8 12
    }
    return 0;
}

字符串分割与处理

#include <ranges>
#include <string>
#include <iostream>

int main() {
    std::string text = "hello world from cpp20 ranges";

    // 按空格分割字符串(C++20 lazy split)
    auto words = std::views::split(text, ' ');

    for (const auto& word : words) {
        // word 是一个子范围,需要构造为 string
        std::string s(word.begin(), word.end());
        std::cout << s << std::endl;
    }
    return 0;
}

注意事项

  • 视图惰性求值的,每次迭代都会重新计算,如果需要次遍历结果,应将视图复制到容器
  • 视图不拥有数据,底层容器销毁视图将变为悬空引用,使用时需确保底层容器生命周期
  • std::views::filterstd::views::transform 返回视图不满足 common_range,其 end() 返回哨兵而非迭代器,某些需要双向迭代的场景需注意
  • 部分(如 filter)的迭代器性能略低于原生循环,在性能极其敏感热路径中应进基准测试
  • std::views::split 在 C++20 中返回子范围类型使用不便,C++23 的 std::views::split 改进接口
  • Ranges 算法(如 std::ranges::sort)会直接修改原容器,与视图的惰性语义不同,使用时需区分

进阶用法

自定义视图适配器

#include <ranges>
#include <concepts>

// 自定义视图:每 N 个元素取一个(采样)
template<std::ranges::view V>
struct SampleView : std::ranges::view_interface<SampleView<V>> {
    V base_;
    std::size_t step_;

    // 迭代器实现(简化版)
    class iterator {
        std::ranges::iterator_t<V> current_;
        std::ranges::sentinel_t<V> end_;
        std::size_t step_;
    public:
        iterator(std::ranges::iterator_t<V> cur,
                 std::ranges::sentinel_t<V> end,
                 std::size_t step)
            : current_(cur), end_(end), step_(step) {}

        auto operator*() const { return *current_; }

        iterator& operator++() {
            // 前进 step 步
            for (std::size_t i = 0; i < step_ && current_ != end_; ++i) {
                ++current_;
            }
            return *this;
        }
    };

    auto begin() { return iterator{base_.begin(), base_.end(), step_}; }
    auto end() { return std::default_sentinel; }
};

// 适配器闭包对象(支持管道语法)
struct SampleFn {
    std::size_t step;
    template<std::ranges::viewable_range R>
    auto operator()(R&& r) const {
        return SampleView<std::views::all_t<R>>{
            std::views::all(std::forward<R>(r)), step
        };
    }
};

// 使用方式
// auto sampled = data | SampleFn{3};  // 每3个取1个

C++23 新增视图

#include <ranges>

// enumerate: 带索引遍历
std::vector<std::string> items = {"apple", "banana", "cherry"};
for (auto [index, value] : std::views::enumerate(items)) {
    std::cout << index << ": " << value << std::endl;
}

// zip: 合并多个范围
std::vector<int> ids = {1, 2, 3};
std::vector<std::string> names = {"张三", "李四", "王五"};
for (auto [id, name] : std::views::zip(ids, names)) {
    std::cout << id << " - " << name << std::endl;
}

// chunk: 分块处理
std::vector<int> data = {1, 2, 3, 4, 5, 6, 7};
for (auto chunk : std::views::chunk(data, 3)) {
    // 第一轮: {1, 2, 3},第二轮: {4, 5, 6},第三轮: {7}
    for (int n : chunk) std::cout << n << " ";
    std::cout << std::endl;
}

// slide: 滑动窗口
for (auto window : std::views::slide(data, 3)) {
    // {1,2,3}, {2,3,4}, {3,4,5}, {4,5,6}, {5,6,7}
    for (int n : window) std::cout << n << " ";
    std::cout << std::endl;
}

Ranges 与投影(Projection)

Ranges 算法支持投影参数,避免编写简单lambda

#include <ranges>
#include <algorithm>
#include <vector>

struct Employee {
    std::string name;
    int age;
    double salary;
};

int main() {
    std::vector<Employee> employees = {
        {"张三", 28, 15000.0},
        {"李四", 35, 22000.0},
        {"王五", 24, 12000.0}
    };

    // 按年龄排序,使用投影替代 lambda
    std::ranges::sort(employees, {}, &Employee::age);

    // 按薪资降序排序
    std::ranges::sort(employees, std::greater{}, &Employee::salary);

    // 查找薪资最高的员工
    auto it = std::ranges::max_element(employees, {}, &Employee::salary);
    std::cout << "最高薪资: " << it->name << std::endl;
    return 0;
}

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式