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算法虽然功能强大,但存在几个明显的使用不便:
- 迭代器对耦合:必须同时提供起始和结束迭代器
std::vector<int> v = {1, 2, 3, 4, 5};
std::sort(v.begin(), v.end()); // 必须提供两个迭代器
- 错误风险:容易传递不匹配的迭代器对
std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = {4, 5, 6};
// 潜在风险:传递了不匹配的迭代器对
std::sort(v1.begin(), v2.end()); // 未定义行为
- 组合算法困难:算法间组合需要中间容器,代码冗长
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; });
- 返回值不便:许多算法返回迭代器,需要检查合法性
auto it = std::find(v.begin(), v.end(), 42);
if (it != v.end()) { // 必须检查是否有效// 使用*it
} else {// 未找到
}
范围库的设计理念
C++20范围库基于几个核心理念设计:
- 范围作为统一概念:将容器或迭代器对视为一个整体的"范围"
std::vector<int> v = {1, 2, 3, 4, 5};
// 将容器作为范围直接传递
std::ranges::sort(v);
- 组合操作:通过管道操作符(
|
)组合多个操作
auto result = container| std::views::filter(pred)| std::views::transform(func);
- 惰性求值:视图不立即执行操作,而是在需要结果时才计算
// 创建视图只是定义操作,不执行实际计算
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 << " ";
}
- 更清晰的语义:代码更具声明性,意图更加明确
// 传统方式
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; });
核心组件
范围库包含四个主要组件:
- 范围概念(Range Concepts):定义何为范围的约束
template<typename T>
concept range = requires(T& t) {std::ranges::begin(t);std::ranges::end(t);
};
- 范围算法(Range Algorithms):接受范围作为参数的算法
// 算法直接接受范围参数
std::ranges::sort(container);
std::ranges::find(container, value);
- 视图(Views):轻量级、不修改原始数据的范围适配器
// 基本视图
std::views::all // 整个范围的视图
std::views::filter // 根据谓词筛选元素
std::views::transform // 转换元素
std::views::take // 取前N个元素
std::views::drop // 跳过前N个元素
- 范围适配器(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;
}
范围的关键特性:
- 提供了表示元素序列的统一抽象
- 支持对整个序列而非迭代器对进行操作
- 可以是有限的(如容器)或无限的(如生成器)
- 可以是普通容器、视图或迭代器对
范围类别和需求
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;
}
主要范围类别:
std::ranges::range
:基本范围概念,支持begin()
和end()
std::ranges::input_range
:可以读取元素的范围std::ranges::forward_range
:可以多次遍历的范围std::ranges::bidirectional_range
:可以双向遍历的范围std::ranges::random_access_range
:支持随机访问的范围std::ranges::contiguous_range
:内存连续存储的范围std::ranges::sized_range
:可以常数时间获取大小的范围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;
}
哨兵概念的优势:
- 更灵活的范围表示:终止条件可以是谓词而非具体位置
- 无限序列支持:可以表示无限序列,只在需要时检查终止条件
- 懒惰计算:哨兵可以推迟终止条件的计算
- 优化机会:编译器可以针对特定哨兵类型优化代码
范围算法
算法改进
C++20为标准库算法提供了对应的范围版本,带来几个主要改进:
- 直接接受范围参数,而非迭代器对
#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;
}
- 返回更有意义的结果
#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;
}
- 支持投影(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;
}
- 概念约束:算法明确指定了对输入类型的要求
// 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;
}
常见范围算法分类:
- 非修改序列算法:
ranges::find
,ranges::count
,ranges::all_of
等 - 修改序列算法:
ranges::copy
,ranges::transform
,ranges::replace
等 - 排序和相关算法:
ranges::sort
,ranges::partial_sort
,ranges::nth_element
等 - 数值算法:
ranges::accumulate
(注意:部分数值算法尚未有范围版本) - 集合算法:
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;
}
投影的优势:
- 代码简洁性:无需创建单独的比较器函数
- 语义清晰:明确表达对哪些属性进行操作
- 可组合性:可以与其他算法参数(如比较器)组合
- 易于维护:当数据结构变化时,只需更改投影函数
视图(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;
}
视图的关键特性:
- 轻量级:创建视图通常是O(1)操作,不涉及元素复制
- 惰性求值:只在遍历时才执行操作
- 非拥有:视图不拥有元素,只引用原始范围
- 可组合:可以通过管道操作符组合多个视图
基本视图类型
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;}
}
常用预定义视图:
std::views::all
:引用整个范围std::views::filter
:根据谓词筛选元素std::views::transform
:变换每个元素std::views::take
:取前N个元素std::views::take_while
:取满足条件的前缀元素std::views::drop
:丢弃前N个元素std::views::drop_while
:丢弃满足条件的前缀元素std::views::reverse
:反转范围std::views::elements
:获取元组元素std::views::keys
/std::views::values
:获取键值对的键或值std::views::iota
:生成连续递增的整数序列std::views::split
:按分隔符分割范围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;
}
主要区别:
-
内存所有权
- 容器拥有并管理其元素的内存
- 视图不拥有元素,只引用其他范围
-
创建成本
- 创建容器副本需要复制元素,时间和空间成本与元素数成正比
- 创建视图是O(1)操作,几乎没有开销
-
修改传播
- 修改容器副本不影响原始数据
- 修改原始数据会反映在引用它的视图中
-
惰性求值
- 容器中的元素是预先计算的
- 视图元素是按需计算的,可能多次计算
-
适用场景
- 容器适用于需要存储和拥有数据的场景
- 视图适用于临时转换和处理数据的场景
管道操作
管道语法
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;
}
管道语法的优势:
- 可读性:从左到右的数据流更符合人类思维习惯
- 组合性:轻松组合多个操作,无需中间变量
- 简洁性:减少样板代码
- 表达力:清晰表达数据转换意图
视图组合
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;
}
视图组合的关键点:
- 顺序重要性:操作按照管道中指定的顺序执行
- 效率考虑:某些组合可能比其他组合更高效
- 视图延迟特性:无论多少操作组合,都只在遍历时执行
- 表达能力:复杂的数据转换可以简洁地表达
惰性求值
视图的一个核心特征是惰性求值,只在需要结果时才执行操作:
#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;
}
惰性求值的特点和优势:
- 按需计算:只计算实际需要的元素
- 节省内存:不需要存储中间结果
- 支持无限序列:可以处理理论上无限的数据流
- 避免不必要的工作:如果只需要前几个结果,不会处理所有元素
- 潜在缺点:多次遍历会重复计算,有时需要具体化(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++带来了更现代、更函数式的编程风格。主要优势包括:
- 更简洁的语法:直接对容器操作,无需显式传递迭代器对
- 更好的可读性:代码表达数据流向,更符合人类思维模式
- 组合能力:通过管道操作符轻松组合多个操作
- 惰性求值:按需执行操作,提高效率
- 视图抽象:轻量级引用原始数据,避免不必要的复制
- 投影参数:简化复杂数据类型的处理
范围库的基础概念、范围算法和基本视图类型为处理各种数据集合提供了强大的工具。通过管道操作和惰性求值,我们可以构建高效的数据处理流程,使代码更加简洁明了。
在下一篇文章中,我们将探讨Ranges库的更多高级特性,包括复杂视图组合、自定义视图、性能优化技巧以及与其他C++20特性的结合使用。
这是我C++学习之旅系列的第五十一篇技术文章。查看完整系列目录了解更多内容。