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

C++学习:六个月从基础到就业——C++20:范围(Ranges)基础

C++学习:六个月从基础到就业——C++20:范围(Ranges)基础

本文是我C++学习之旅系列的第五十一篇技术文章,也是第三阶段"现代C++特性"的第十三篇,介绍C++20引入的范围(Ranges)库的基础知识。查看完整系列目录了解更多内容。

引言

STL算法和容器是C++编程中最强大的工具之一,但传统的STL算法接口存在一些使用上的不便:需要显式传递迭代器对、难以组合多个算法操作、代码可读性不佳等。C++20引入的范围(Ranges)库重新设计了算法接口,引入了视图(Views)的概念,并提供了方便的管道操作符,显著改善了这些问题。

想象一下,如果你想获取一个集合中所有偶数的平方,传统STL算法需要这样写:

std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::vector<int> result;// 筛选偶数
std::copy_if(numbers.begin(), numbers.end(), std::back_inserter(result),[](int n) { return n % 2 == 0; });// 计算平方
std::transform(result.begin(), result.end(),result.begin(),[](int n) { return n * n; });

而使用Ranges库,代码变得简洁明了:

std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};auto result = numbers| std::views::filter([](int n) { return n % 2 == 0; })| std::views::transform([](int n) { return n * n; });// 使用结果
for (int n : result) {std::cout << n << " ";  // 输出: 4 16 36 64 100
}

本文将介绍C++20 Ranges库的基础知识,包括其核心概念、范围算法、视图(Views)和管道操作,帮助你理解和应用这一强大的新特性。

目录

  • C++20:范围(Ranges)基础
    • 引言
    • 目录
    • 范围库概述
      • 传统STL算法的局限
      • 范围库的设计理念
      • 核心组件
    • 范围概念
      • 什么是范围
      • 范围类别和需求
      • 迭代器与哨兵
    • 范围算法
      • 算法改进
      • 常用范围算法
      • 投影参数
    • 视图(Views)基础
      • 视图的概念
      • 基本视图类型
      • 视图与容器的区别
    • 管道操作
      • 管道语法
      • 视图组合
      • 惰性求值
    • 实际应用示例
      • 数据过滤与转换
      • 字符串处理
      • 数值计算
    • 总结

范围库概述

传统STL算法的局限

传统STL算法虽然功能强大,但存在几个明显的使用不便:

  1. 迭代器对耦合:必须同时提供起始和结束迭代器
std::vector<int> v = {1, 2, 3, 4, 5};
std::sort(v.begin(), v.end());  // 必须提供两个迭代器
  1. 错误风险:容易传递不匹配的迭代器对
std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = {4, 5, 6};
// 潜在风险:传递了不匹配的迭代器对
std::sort(v1.begin(), v2.end());  // 未定义行为
  1. 组合算法困难:算法间组合需要中间容器,代码冗长
std::vector<int> source = {1, 2, 3, 4, 5};
std::vector<int> temp;
std::vector<int> result;// 筛选
std::copy_if(source.begin(), source.end(), std::back_inserter(temp),[](int n) { return n > 2; });// 转换
std::transform(temp.begin(), temp.end(),std::back_inserter(result),[](int n) { return n * n; });
  1. 返回值不便:许多算法返回迭代器,需要检查合法性
auto it = std::find(v.begin(), v.end(), 42);
if (it != v.end()) {  // 必须检查是否有效// 使用*it
} else {// 未找到
}

范围库的设计理念

C++20范围库基于几个核心理念设计:

  1. 范围作为统一概念:将容器或迭代器对视为一个整体的"范围"
std::vector<int> v = {1, 2, 3, 4, 5};
// 将容器作为范围直接传递
std::ranges::sort(v);  
  1. 组合操作:通过管道操作符(|)组合多个操作
auto result = container| std::views::filter(pred)| std::views::transform(func);
  1. 惰性求值:视图不立即执行操作,而是在需要结果时才计算
// 创建视图只是定义操作,不执行实际计算
auto even_squares = numbers| std::views::filter([](int n) { return n % 2 == 0; })| std::views::transform([](int n) { return n * n; });// 只有遍历时才实际执行操作
for (int n : even_squares) {// 在这里才真正执行过滤和转换std::cout << n << " ";
}
  1. 更清晰的语义:代码更具声明性,意图更加明确
// 传统方式
std::vector<int> result;
std::copy_if(v.begin(), v.end(), std::back_inserter(result), [](int n) { return n > 0; });// Ranges方式
auto positive = v | std::views::filter([](int n) { return n > 0; });

核心组件

范围库包含四个主要组件:

  1. 范围概念(Range Concepts):定义何为范围的约束
template<typename T>
concept range = requires(T& t) {std::ranges::begin(t);std::ranges::end(t);
};
  1. 范围算法(Range Algorithms):接受范围作为参数的算法
// 算法直接接受范围参数
std::ranges::sort(container);
std::ranges::find(container, value);
  1. 视图(Views):轻量级、不修改原始数据的范围适配器
// 基本视图
std::views::all      // 整个范围的视图
std::views::filter   // 根据谓词筛选元素
std::views::transform // 转换元素
std::views::take     // 取前N个元素
std::views::drop     // 跳过前N个元素
  1. 范围适配器(Range Adaptors):修改范围属性的工具
// 范围适配器
std::views::reverse  // 反转范围
std::views::join     // 连接嵌套范围
std::views::split    // 分割范围

范围概念

什么是范围

在C++20中,范围(Range)是一个抽象概念,表示元素序列。更具体地说,任何可以通过调用std::ranges::begin()std::ranges::end()得到有效迭代器对的类型都是范围。

#include <ranges>
#include <vector>
#include <list>
#include <string>
#include <iostream>void demonstrate_ranges() {// 这些都是范围std::vector<int> vec = {1, 2, 3, 4, 5};        // 向量是范围std::list<double> lst = {1.1, 2.2, 3.3};       // 列表是范围std::string str = "Hello";                     // 字符串是范围int arr[] = {10, 20, 30, 40, 50};              // 数组是范围// 普通指针对也可以是范围const char* cstr = "C-string";auto ptr_range = std::ranges::subrange(cstr, cstr + 8);// 视图也是范围auto even = vec | std::views::filter([](int n) { return n % 2 == 0; });// 使用范围变量的示例std::cout << "Vector elements:";for (int n : vec) std::cout << " " << n;std::cout << std::endl;std::cout << "Even numbers:";for (int n : even) std::cout << " " << n;std::cout << std::endl;
}

范围的关键特性:

  1. 提供了表示元素序列的统一抽象
  2. 支持对整个序列而非迭代器对进行操作
  3. 可以是有限的(如容器)或无限的(如生成器)
  4. 可以是普通容器、视图或迭代器对

范围类别和需求

C++20定义了多种范围概念,根据底层迭代器的能力形成层次结构:

#include <ranges>
#include <vector>
#include <list>
#include <forward_list>
#include <iostream>template<typename R>
void print_range_capabilities() {std::cout << "- range: " << std::ranges::range<R> << std::endl;std::cout << "- input_range: " << std::ranges::input_range<R> << std::endl;std::cout << "- forward_range: " << std::ranges::forward_range<R> << std::endl;std::cout << "- bidirectional_range: " << std::ranges::bidirectional_range<R> << std::endl;std::cout << "- random_access_range: " << std::ranges::random_access_range<R> << std::endl;std::cout << "- contiguous_range: " << std::ranges::contiguous_range<R> << std::endl;std::cout << "- sized_range: " << std::ranges::sized_range<R> << std::endl;std::cout << "- view: " << std::ranges::view<R> << std::endl;
}int main() {std::cout << "std::vector capabilities:" << std::endl;print_range_capabilities<std::vector<int>>();std::cout << "\nstd::list capabilities:" << std::endl;print_range_capabilities<std::list<int>>();std::cout << "\nstd::forward_list capabilities:" << std::endl;print_range_capabilities<std::forward_list<int>>();std::cout << "\nFilter view capabilities:" << std::endl;print_range_capabilities<decltype(std::vector<int>{} | std::views::filter([](int) { return true; }))>();return 0;
}

主要范围类别:

  1. std::ranges::range:基本范围概念,支持begin()end()
  2. std::ranges::input_range:可以读取元素的范围
  3. std::ranges::forward_range:可以多次遍历的范围
  4. std::ranges::bidirectional_range:可以双向遍历的范围
  5. std::ranges::random_access_range:支持随机访问的范围
  6. std::ranges::contiguous_range:内存连续存储的范围
  7. std::ranges::sized_range:可以常数时间获取大小的范围
  8. std::ranges::view:轻量级、非拥有元素的范围

不同容器支持不同级别的范围能力:

  • std::vector 支持所有范围能力(除了view
  • std::list 是双向范围但不支持随机访问
  • std::forward_list 是前向范围但不支持双向遍历
  • 视图(如filter view)通常继承底层范围的能力,但始终是view

迭代器与哨兵

C++20范围库引入了"哨兵"(Sentinel)的概念,允许迭代器和终止条件类型不同:

#include <ranges>
#include <vector>
#include <string>
#include <iostream>int main() {// 传统方式:begin和end类型相同std::vector<int> v = {1, 2, 3, 4, 5};auto it_begin = v.begin();  // std::vector<int>::iteratorauto it_end = v.end();      // 同样类型// 范围库:允许不同类型的终止条件std::string str = "Hello, world!";// 自定义视图:到null字符为止auto null_terminated = std::ranges::subrange(str.c_str(),  // const char*std::unreachable_sentinel  // 特殊哨兵类型);// 计算长度(不包括null终止符)std::cout << "String length: " << std::ranges::distance(null_terminated) << std::endl;// 查找特定字符的视图auto until_comma = std::ranges::subrange(str.begin(),std::ranges::find(str, ',')  // 迭代器指向逗号);std::cout << "Text before comma: ";for (char c : until_comma) {std::cout << c;}std::cout << std::endl;return 0;
}

哨兵概念的优势:

  1. 更灵活的范围表示:终止条件可以是谓词而非具体位置
  2. 无限序列支持:可以表示无限序列,只在需要时检查终止条件
  3. 懒惰计算:哨兵可以推迟终止条件的计算
  4. 优化机会:编译器可以针对特定哨兵类型优化代码

范围算法

算法改进

C++20为标准库算法提供了对应的范围版本,带来几个主要改进:

  1. 直接接受范围参数,而非迭代器对
#include <ranges>
#include <algorithm>
#include <vector>
#include <iostream>int main() {std::vector<int> numbers = {5, 3, 1, 4, 2};// 传统STL算法std::sort(numbers.begin(), numbers.end());// 范围版本算法std::ranges::sort(numbers);  // 更简洁return 0;
}
  1. 返回更有意义的结果
#include <ranges>
#include <algorithm>
#include <vector>
#include <iostream>int main() {std::vector<int> numbers = {1, 2, 3, 4, 5};// 传统STL查找auto it = std::find(numbers.begin(), numbers.end(), 3);if (it != numbers.end()) {std::cout << "Found: " << *it << std::endl;}// 范围版本查找auto it_ranges = std::ranges::find(numbers, 3);if (it_ranges != numbers.end()) {std::cout << "Found with ranges: " << *it_ranges << std::endl;}// 更复杂的例子:找出最大和最小值auto [min_it, max_it] = std::ranges::minmax_element(numbers);std::cout << "Min: " << *min_it << ", Max: " << *max_it << std::endl;return 0;
}
  1. 支持投影(Projections):在应用算法前变换元素
#include <ranges>
#include <algorithm>
#include <vector>
#include <string>
#include <iostream>struct Person {std::string name;int age;
};int main() {std::vector<Person> people = {{"Alice", 30},{"Bob", 25},{"Charlie", 35}};// 使用投影按年龄排序std::ranges::sort(people, {}, &Person::age);// 输出排序后的结果for (const auto& person : people) {std::cout << person.name << ": " << person.age << std::endl;}// 查找年龄最大的人auto oldest = std::ranges::max_element(people, {}, &Person::age);if (oldest != people.end()) {std::cout << "Oldest person: " << oldest->name << " (" << oldest->age << ")" << std::endl;}return 0;
}
  1. 概念约束:算法明确指定了对输入类型的要求
// std::ranges::sort的简化定义
template<std::random_access_range R, typename Comp = std::ranges::less, typename Proj = std::identity>
requires std::sortable<std::ranges::iterator_t<R>, Comp, Proj>
constexpr auto sort(R&& r, Comp comp = {}, Proj proj = {}) -> ...;

常用范围算法

C++20范围库包含了标准库中大部分算法的范围版本:

#include <ranges>
#include <algorithm>
#include <numeric>
#include <vector>
#include <iostream>void demonstrate_range_algorithms() {std::vector<int> numbers = {5, 2, 8, 1, 9, 3, 7, 4, 6};std::vector<int> dest(numbers.size());// 排序算法std::ranges::sort(numbers);std::cout << "排序后: ";for (int n : numbers) std::cout << n << " ";std::cout << std::endl;// 查找算法auto it = std::ranges::find(numbers, 7);if (it != numbers.end()) {std::cout << "找到7,位置: " << std::distance(numbers.begin(), it) << std::endl;}// 计数算法int count = std::ranges::count_if(numbers, [](int n) { return n % 2 == 0; });std::cout << "偶数个数: " << count << std::endl;// 复制算法std::ranges::copy_if(numbers, dest.begin(), [](int n) { return n > 5; });std::cout << "大于5的数: ";for (int i = 0; i < std::ranges::count(numbers, true, [](int n) { return n > 5; }); ++i) {std::cout << dest[i] << " ";}std::cout << std::endl;// 变换算法std::ranges::transform(numbers, dest.begin(), [](int n) { return n * n; });std::cout << "平方值: ";for (int i = 0; i < numbers.size(); ++i) {std::cout << dest[i] << " ";}std::cout << std::endl;// 其他常用算法auto [min, max] = std::ranges::minmax(numbers);std::cout << "最小值: " << min << ", 最大值: " << max << std::endl;std::ranges::reverse(numbers);std::cout << "反转后: ";for (int n : numbers) std::cout << n << " ";std::cout << std::endl;bool all_positive = std::ranges::all_of(numbers, [](int n) { return n > 0; });std::cout << "全部为正: " << (all_positive ? "是" : "否") << std::endl;
}

常见范围算法分类:

  1. 非修改序列算法ranges::find, ranges::count, ranges::all_of
  2. 修改序列算法ranges::copy, ranges::transform, ranges::replace
  3. 排序和相关算法ranges::sort, ranges::partial_sort, ranges::nth_element
  4. 数值算法ranges::accumulate(注意:部分数值算法尚未有范围版本)
  5. 集合算法ranges::set_union, ranges::set_intersection

投影参数

范围算法的一个重要特性是支持投影(Projections),允许在实际应用算法前转换元素:

#include <ranges>
#include <algorithm>
#include <vector>
#include <string>
#include <iostream>struct Student {std::string name;std::vector<int> scores;// 计算平均分double average() const {if (scores.empty()) return 0;int sum = 0;for (int score : scores) sum += score;return static_cast<double>(sum) / scores.size();}
};int main() {std::vector<Student> students = {{"Alice", {85, 90, 82}},{"Bob", {76, 88, 95}},{"Charlie", {90, 92, 98}},{"David", {65, 75, 80}}};// 使用投影基于平均分排序std::ranges::sort(students, {}, [](const Student& s) { return s.average(); });// 显示结果std::cout << "学生按平均分排序:" << std::endl;for (const auto& student : students) {std::cout << student.name << ": " << student.average() << std::endl;}// 找出平均分最高的学生auto top_student = std::ranges::max_element(students, {}, [](const Student& s) { return s.average(); });std::cout << "\n平均分最高的学生: " << top_student->name << " (" << top_student->average() << ")" << std::endl;// 找出有满分(100)的学生auto perfect_student = std::ranges::find_if(students, [](const std::vector<int>& scores) {return std::ranges::any_of(scores, [](int score) { return score == 100; });},&Student::scores  // 投影:获取学生的分数向量);if (perfect_student != students.end()) {std::cout << "\n有满分的学生: " << perfect_student->name << std::endl;} else {std::cout << "\n没有学生获得满分" << std::endl;}return 0;
}

投影的优势:

  1. 代码简洁性:无需创建单独的比较器函数
  2. 语义清晰:明确表达对哪些属性进行操作
  3. 可组合性:可以与其他算法参数(如比较器)组合
  4. 易于维护:当数据结构变化时,只需更改投影函数

视图(Views)基础

视图的概念

视图(View)是范围库中的核心概念,表示一个轻量级、非拥有元素的范围:

#include <ranges>
#include <vector>
#include <iostream>int main() {std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};// 创建视图auto even_numbers = numbers | std::views::filter([](int n) { return n % 2 == 0; });// 视图不修改原始数据std::cout << "原始数据:";for (int n : numbers) std::cout << " " << n;std::cout << std::endl;std::cout << "视图内容:";for (int n : even_numbers) std::cout << " " << n;std::cout << std::endl;// 修改原始数据会影响视图numbers[0] = 0;  // 将1改为0std::cout << "修改后视图:";for (int n : even_numbers) std::cout << " " << n;std::cout << std::endl;return 0;
}

视图的关键特性:

  1. 轻量级:创建视图通常是O(1)操作,不涉及元素复制
  2. 惰性求值:只在遍历时才执行操作
  3. 非拥有:视图不拥有元素,只引用原始范围
  4. 可组合:可以通过管道操作符组合多个视图

基本视图类型

C++20提供了多种预定义视图,位于std::views命名空间:

#include <ranges>
#include <vector>
#include <string>
#include <iostream>void demonstrate_basic_views() {std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};// all:整个范围的视图auto all_view = std::views::all(numbers);// filter:过滤元素auto even = numbers | std::views::filter([](int n) { return n % 2 == 0; });// transform:变换元素auto squares = numbers | std::views::transform([](int n) { return n * n; });// take:取前N个元素auto first_five = numbers | std::views::take(5);// drop:丢弃前N个元素auto skip_five = numbers | std::views::drop(5);// reverse:反转范围auto reversed = numbers | std::views::reverse;// 输出各种视图std::cout << "原始数据:";for (int n : all_view) std::cout << " " << n;std::cout << std::endl;std::cout << "偶数:";for (int n : even) std::cout << " " << n;std::cout << std::endl;std::cout << "平方值:";for (int n : squares) std::cout << " " << n;std::cout << std::endl;std::cout << "前5个:";for (int n : first_five) std::cout << " " << n;std::cout << std::endl;std::cout << "跳过前5个:";for (int n : skip_five) std::cout << " " << n;std::cout << std::endl;std::cout << "反转:";for (int n : reversed) std::cout << " " << n;std::cout << std::endl;// iota:生成整数序列auto sequence = std::views::iota(1, 6);  // 1到5std::cout << "整数序列:";for (int n : sequence) std::cout << " " << n;std::cout << std::endl;// 字符串相关视图std::string text = "Hello,World,C++,Ranges";auto words = text | std::views::split(',');std::cout << "分割字符串:" << std::endl;for (auto word : words) {for (char c : word) std::cout << c;std::cout << std::endl;}
}

常用预定义视图:

  1. std::views::all:引用整个范围
  2. std::views::filter:根据谓词筛选元素
  3. std::views::transform:变换每个元素
  4. std::views::take:取前N个元素
  5. std::views::take_while:取满足条件的前缀元素
  6. std::views::drop:丢弃前N个元素
  7. std::views::drop_while:丢弃满足条件的前缀元素
  8. std::views::reverse:反转范围
  9. std::views::elements:获取元组元素
  10. std::views::keys/std::views::values:获取键值对的键或值
  11. std::views::iota:生成连续递增的整数序列
  12. std::views::split:按分隔符分割范围
  13. std::views::join:连接嵌套范围

视图与容器的区别

视图和容器有几个关键区别:

#include <ranges>
#include <vector>
#include <string>
#include <iostream>
#include <chrono>void compare_view_and_container() {std::vector<int> numbers(1'000'000);// 填充数据for (int i = 0; i < numbers.size(); ++i) {numbers[i] = i;}// 计时创建副本auto start = std::chrono::high_resolution_clock::now();std::vector<int> filtered_container;for (int n : numbers) {if (n % 2 == 0) {filtered_container.push_back(n);}}auto mid = std::chrono::high_resolution_clock::now();// 创建视图auto filtered_view = numbers | std::views::filter([](int n) { return n % 2 == 0; });auto end = std::chrono::high_resolution_clock::now();// 计算时间auto container_time = std::chrono::duration_cast<std::chrono::milliseconds>(mid - start).count();auto view_time = std::chrono::duration_cast<std::chrono::milliseconds>(end - mid).count();std::cout << "创建容器副本时间: " << container_time << " ms" << std::endl;std::cout << "创建视图时间: " << view_time << " ms" << std::endl;// 内存使用对比std::cout << "容器元素数量: " << filtered_container.size() << std::endl;std::cout << "视图元素数量: " << std::ranges::distance(filtered_view) << std::endl;std::cout << "容器内存占用: " << filtered_container.size() * sizeof(int) << " 字节" << std::endl;std::cout << "视图理论内存占用: < 100 字节" << std::endl;// 修改原始数据影响numbers[0] = 1;  // 改为奇数// 容器不受影响std::cout << "修改原始数据后:" << std::endl;std::cout << "容器首元素: " << filtered_container[0] << std::endl;std::cout << "视图首元素: " << *filtered_view.begin() << std::endl;
}

主要区别:

  1. 内存所有权

    • 容器拥有并管理其元素的内存
    • 视图不拥有元素,只引用其他范围
  2. 创建成本

    • 创建容器副本需要复制元素,时间和空间成本与元素数成正比
    • 创建视图是O(1)操作,几乎没有开销
  3. 修改传播

    • 修改容器副本不影响原始数据
    • 修改原始数据会反映在引用它的视图中
  4. 惰性求值

    • 容器中的元素是预先计算的
    • 视图元素是按需计算的,可能多次计算
  5. 适用场景

    • 容器适用于需要存储和拥有数据的场景
    • 视图适用于临时转换和处理数据的场景

管道操作

管道语法

Ranges库引入了管道操作符(|),使数据处理更加直观:

#include <ranges>
#include <vector>
#include <iostream>int main() {std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};// 使用管道语法auto result = numbers| std::views::filter([](int n) { return n % 2 == 0; })  // 偶数| std::views::transform([](int n) { return n * n; });   // 平方// 输出结果std::cout << "偶数的平方:";for (int n : result) std::cout << " " << n;std::cout << std::endl;// 管道操作符使用规则:// 1. 传统函数调用风格auto even_func = std::views::filter(numbers, [](int n) { return n % 2 == 0; });// 2. 管道风格 (通常更易读)auto even_pipe = numbers | std::views::filter([](int n) { return n % 2 == 0; });// 两种方式等效std::cout << "函数调用风格:";for (int n : even_func) std::cout << " " << n;std::cout << std::endl;std::cout << "管道风格:";for (int n : even_pipe) std::cout << " " << n;std::cout << std::endl;return 0;
}

管道语法的优势:

  1. 可读性:从左到右的数据流更符合人类思维习惯
  2. 组合性:轻松组合多个操作,无需中间变量
  3. 简洁性:减少样板代码
  4. 表达力:清晰表达数据转换意图

视图组合

Ranges库的强大之处在于可以轻松组合多个视图:

#include <ranges>
#include <vector>
#include <string>
#include <iostream>
#include <cctype>  // for toupperint main() {std::vector<std::string> words = {"apple", "banana", "cherry", "date", "elderberry", "fig", "grape", "honeydew"};// 组合多个视图auto processed = words| std::views::filter([](const std::string& s) { return s.length() > 5;  // 只要长词})| std::views::transform([](std::string s) {// 转换为大写for (char& c : s) c = std::toupper(c);return s;})| std::views::take(3);  // 只取前3个// 输出结果std::cout << "处理结果:" << std::endl;for (const auto& word : processed) {std::cout << word << std::endl;}// 更复杂的组合示例std::vector<std::vector<int>> nested = {{1, 2, 3},{4, 5, 6},{7, 8, 9}};auto flattened = nested| std::views::join  // 展平嵌套容器| std::views::filter([](int n) { return n % 2 != 0; })  // 奇数| std::views::transform([](int n) { return n * n; })    // 平方| std::views::reverse;  // 反转顺序std::cout << "\n展平处理:";for (int n : flattened) std::cout << " " << n;std::cout << std::endl;return 0;
}

视图组合的关键点:

  1. 顺序重要性:操作按照管道中指定的顺序执行
  2. 效率考虑:某些组合可能比其他组合更高效
  3. 视图延迟特性:无论多少操作组合,都只在遍历时执行
  4. 表达能力:复杂的数据转换可以简洁地表达

惰性求值

视图的一个核心特征是惰性求值,只在需要结果时才执行操作:

#include <ranges>
#include <vector>
#include <iostream>int main() {std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};// 创建一个包含多个操作的视图std::cout << "创建视图..." << std::endl;auto result = numbers| std::views::filter([](int n) {std::cout << "过滤: " << n << std::endl;return n % 2 == 0;})| std::views::transform([](int n) {std::cout << "变换: " << n << std::endl;return n * n;});std::cout << "视图创建完成,尚未执行任何操作" << std::endl;// 获取首个元素std::cout << "\n获取第一个元素..." << std::endl;auto it = result.begin();std::cout << "第一个元素: " << *it << std::endl;// 获取下一个元素std::cout << "\n获取下一个元素..." << std::endl;++it;std::cout << "下一个元素: " << *it << std::endl;// 遍历剩余元素std::cout << "\n遍历剩余元素..." << std::endl;for (; it != result.end(); ++it) {std::cout << "元素: " << *it << std::endl;}// 重新遍历整个视图std::cout << "\n再次遍历(注意操作会重新执行)..." << std::endl;for (int n : result) {std::cout << "结果: " << n << std::endl;}return 0;
}

惰性求值的特点和优势:

  1. 按需计算:只计算实际需要的元素
  2. 节省内存:不需要存储中间结果
  3. 支持无限序列:可以处理理论上无限的数据流
  4. 避免不必要的工作:如果只需要前几个结果,不会处理所有元素
  5. 潜在缺点:多次遍历会重复计算,有时需要具体化(materialize)结果

实际应用示例

数据过滤与转换

Ranges库特别适合数据过滤和转换操作:

#include <ranges>
#include <vector>
#include <string>
#include <iostream>
#include <algorithm>struct Product {std::string name;double price;int stock;bool discontinued;
};void product_processing() {std::vector<Product> products = {{"Laptop", 1200.0, 5, false},{"Smartphone", 800.0, 12, false},{"Tablet", 400.0, 8, false},{"MP3 Player", 50.0, 0, true},{"Headphones", 120.0, 20, false},{"Camera", 600.0, 3, false},{"Printer", 250.0, 0, false},{"DVD Player", 80.0, 1, true}};// 查找可购买的产品(有库存且未停产)auto available = products | std::views::filter([](const Product& p) {return p.stock > 0 && !p.discontinued;});std::cout << "可购买的产品:" << std::endl;for (const auto& product : available) {std::cout << product.name << " - $" << product.price<< " (库存: " << product.stock << ")" << std::endl;}// 找出价格在一定范围内的产品auto mid_range = products| std::views::filter([](const Product& p) {return p.price >= 100 && p.price <= 500;})| std::views::transform([](const Product& p) {// 返回产品名称和价格return std::make_pair(p.name, p.price);});std::cout << "\n中等价位产品:" << std::endl;for (const auto& [name, price] : mid_range) {std::cout << name << " - $" << price << std::endl;}// 计算所有可用产品的总库存价值double total_value = 0.0;for (const auto& p : available) {total_value += p.price * p.stock;}std::cout << "\n总库存价值: $" << total_value << std::endl;// 按价格排序并显示前3名最贵的产品auto top_priced = products| std::views::filter([](const Product& p) { return p.stock > 0; })| std::ranges::to<std::vector>()  // 具体化为向量| std::views::transform([](const Product& p) {return std::make_pair(p.name, p.price);});// 注意:视图不保证保留原始顺序,需要排序std::vector<std::pair<std::string, double>> sorted_prices(top_priced.begin(), top_priced.end());std::ranges::sort(sorted_prices, std::ranges::greater{}, &std::pair<std::string, double>::second);std::cout << "\n价格最高的3个有库存产品:" << std::endl;for (const auto& [name, price] : sorted_prices | std::views::take(3)) {std::cout << name << " - $" << price << std::endl;}
}

字符串处理

Ranges库使字符串处理更加简洁优雅:

#include <ranges>
#include <vector>
#include <string>
#include <iostream>
#include <algorithm>
#include <cctype>void string_processing() {std::string text = "The quick brown fox jumps over the lazy dog";// 将字符串拆分为单词auto words = text | std::views::split(' ');std::cout << "单词列表:" << std::endl;for (auto word : words) {// 将视图转换为string以便打印std::string w(word.begin(), word.end());std::cout << w << std::endl;}// 找出所有长度大于3的单词auto long_words = text | std::views::split(' ')| std::views::filter([](auto word) {return std::ranges::distance(word) > 3;});std::cout << "\n长度大于3的单词:" << std::endl;for (auto word : long_words) {std::string w(word.begin(), word.end());std::cout << w << std::endl;}// 将每个单词的首字母大写std::string capitalized;bool new_word = true;for (char c : text) {if (new_word && std::isalpha(c)) {capitalized += std::toupper(c);new_word = false;} else {capitalized += c;if (c == ' ') new_word = true;}}std::cout << "\n首字母大写: " << capitalized << std::endl;// 计算文本中不同字母的出现频率(不区分大小写)std::array<int, 26> letter_counts = {0};for (char c : text | std::views::filter(::isalpha) | std::views::transform(::tolower)) {letter_counts[c - 'a']++;}std::cout << "\n字母频率:" << std::endl;for (int i = 0; i < 26; ++i) {if (letter_counts[i] > 0) {std::cout << static_cast<char>('a' + i) << ": " << letter_counts[i] << std::endl;}}
}

数值计算

Ranges库可以简化数值计算和数据分析:

#include <ranges>
#include <vector>
#include <iostream>
#include <numeric>
#include <cmath>
#include <iomanip>void numerical_calculations() {// 生成1到100的序列auto numbers = std::views::iota(1, 101);// 计算平均值double sum = std::accumulate(numbers.begin(), numbers.end(), 0.0);double mean = sum / std::ranges::distance(numbers);std::cout << "平均值: " << mean << std::endl;// 计算平方和double sum_squares = 0.0;for (int n : numbers) {sum_squares += n * n;}std::cout << "平方和: " << sum_squares << std::endl;// 生成斐波那契数列的前20个数std::vector<int> fibonacci;fibonacci.reserve(20);fibonacci.push_back(0);fibonacci.push_back(1);for (int i = 2; i < 20; ++i) {fibonacci.push_back(fibonacci[i-1] + fibonacci[i-2]);}std::cout << "\n斐波那契数列:";for (int n : fibonacci) std::cout << " " << n;std::cout << std::endl;// 查找小于1000的所有斐波那契数中的偶数auto even_fibs = fibonacci | std::views::filter([](int n) { return n < 1000 && n % 2 == 0; });std::cout << "小于1000的偶数斐波那契数:";for (int n : even_fibs) std::cout << " " << n;std::cout << std::endl;// 生成前10个质数std::vector<int> primes;for (int n = 2; primes.size() < 10; ++n) {bool is_prime = true;for (int i = 2; i <= std::sqrt(n); ++i) {if (n % i == 0) {is_prime = false;break;}}if (is_prime) primes.push_back(n);}std::cout << "\n前10个质数:";for (int p : primes) std::cout << " " << p;std::cout << std::endl;// 计算每个质数与下一个质数的差auto prime_gaps = primes| std::views::slide(2)  // C++23功能,在某些编译器可能不可用| std::views::transform([](auto pair) {return *std::next(pair.begin()) - *pair.begin();});// 如果slide不可用,可以使用替代方法std::vector<int> gaps;for (size_t i = 0; i < primes.size() - 1; ++i) {gaps.push_back(primes[i+1] - primes[i]);}std::cout << "质数间隔:";for (int gap : gaps) std::cout << " " << gap;std::cout << std::endl;
}

总结

C++20的范围(Ranges)库彻底改变了我们处理集合和算法的方式,为C++带来了更现代、更函数式的编程风格。主要优势包括:

  1. 更简洁的语法:直接对容器操作,无需显式传递迭代器对
  2. 更好的可读性:代码表达数据流向,更符合人类思维模式
  3. 组合能力:通过管道操作符轻松组合多个操作
  4. 惰性求值:按需执行操作,提高效率
  5. 视图抽象:轻量级引用原始数据,避免不必要的复制
  6. 投影参数:简化复杂数据类型的处理

范围库的基础概念、范围算法和基本视图类型为处理各种数据集合提供了强大的工具。通过管道操作和惰性求值,我们可以构建高效的数据处理流程,使代码更加简洁明了。

在下一篇文章中,我们将探讨Ranges库的更多高级特性,包括复杂视图组合、自定义视图、性能优化技巧以及与其他C++20特性的结合使用。


这是我C++学习之旅系列的第五十一篇技术文章。查看完整系列目录了解更多内容。

http://www.xdnf.cn/news/510373.html

相关文章:

  • 【OpenCV基础 1】几何变换、形态学处理、阈值分割、区域提取和脱敏处理
  • MLLM常见概念通俗解析(一)
  • 【基于Spring Boot 的图书购买系统】深度讲解 用户注册的前后端交互,Mapper操作MySQL数据库进行用户持久化
  • 如何利用内网穿透实现Cursor对私有化部署大模型的跨网络访问实践
  • 【图像生成大模型】CogVideoX-5b:开启文本到视频生成的新纪元
  • lvs-dr部署
  • c++学习之--- list
  • C语言链表的操作
  • 数字人技术的核心:AI与动作捕捉的双引擎驱动(210)
  • defer关键字:延迟调用机制-《Go语言实战指南》
  • 8.1UDP点对点聊天小项目
  • 软件架构之--论微服务的开发方法1
  • 软件工程各种图总结
  • 数据库MySQL基础2
  • 【回溯 剪支 状态压缩】# P10419 [蓝桥杯 2023 国 A] 01 游戏|普及+
  • Java大厂面试:从Web框架到微服务技术的场景化提问与解析
  • FAST-DDS源码分析PDP(一)
  • NoSQL实战指南:MongoDB与Redis企业级开发实战
  • Vue 3 动态 ref 的使用方式(表格)
  • 【Linux高级全栈开发】2.1.3 http服务器的实现
  • AI:NLP 情感分析
  • Filament引擎(一) ——渲染框架设计
  • 中级网络工程师知识点7
  • 课外活动:需了解的海象运算符(:=)
  • HTTPS的工作过程
  • 低延迟与高性能的技术优势解析:SmartPlayer VS VLC Media Player
  • 贪心、分治和回溯算法
  • 当AI自我纠错:一个简单的“Wait“提示如何让模型思考更深、推理更强
  • MySQL(21)如何查询表中的所有数据?
  • ffmpeg -vf subtitles添加字幕绝对路径问题的解决方法