C++ 20范围
引言
C++20带来了许多革命性的特性,其中范围库(Ranges library)是最重要的改进之一。在本文中,我们将探索C++20范围的基本概念、用法以及它如何改变我们处理集合数据的方式。
范围(Ranges)是对迭代器的一种高级抽象,它简化了集合数据的操作,使代码更加直观、安全且易于组合。如果你曾经被STL算法繁琐的语法所困扰,范围库将带给你全新的编程体验。
范围的基本概念
什么是范围?
在C++20中,一个范围(Range)是一个概念(Concept),表示一段元素序列。具体来说,一个类型T是一个范围,如果以下表达式是有效的:
std::begin(t)
:返回指向范围起始的迭代器std::end(t)
:返回指向范围结束的迭代器
这意味着许多已有的C++类型已经是范围,例如数组、std::vector
、std::string
等。
范围与迭代器的对比
传统上,我们使用迭代器对来表示一段元素序列:
std::vector<int> v = {1, 2, 3, 4, 5};
// 传统方式:使用迭代器
std::sort(v.begin(), v.end());
使用范围,同样的操作可以简化为:
std::vector<int> v = {1, 2, 3, 4, 5};
// 范围方式:直接传递容器
std::ranges::sort(v);
范围库的主要组件
范围算法
C++20在<algorithm>
的基础上引入了<ranges>
头文件,提供了与STL算法对应的范围版本。这些算法直接接受范围作为参数,而不是迭代器对:
#include <iostream>
#include <vector>
#include <ranges>
#include <algorithm>
int main() {
std::vector<int> v = {5, 3, 1, 4, 2};
// 使用范围版本的排序
std::ranges::sort(v);
for (int i : v) {
std::cout << i << ' ';
}
// 输出: 1 2 3 4 5
}
视图(Views)
视图是范围库中最强大的概念之一。视图是一种特殊的范围,它不持有元素,而是提供对其他范围元素的"视图"。视图具有以下特点:
- 轻量级:通常只是包装原始范围的引用
- 惰性求值:许多操作推迟到需要元素时才执行
- 可组合:多个视图可以通过管道操作符(
|
)链接在一起
C++20提供了多种预定义的视图,如views::filter
、views::transform
、views::take
等。
视图示例
#include <iostream>
#include <vector>
#include <ranges>
#include <string>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 创建一个视图:筛选出所有偶数,然后将它们乘以2
auto result = numbers | std::views::filter([](int n) { return n % 2 == 0; })
| std::views::transform([](int n) { return n * 2; });
// 遍历视图中的元素
for (int n : result) {
std::cout << n << ' ';
}
// 输出: 4 8 12 16 20
}
视图不会修改原始容器,也不会创建新容器。它们只是提供了一种"查看"原始数据的方式。
常用视图
C++20提供了许多有用的视图,下面是一些常用的:
filter 视图
filter
视图用于筛选满足特定条件的元素:
auto even_numbers = numbers | std::views::filter([](int n) { return n % 2 == 0; });
transform 视图
transform
视图用于转换每个元素:
auto squared_numbers = numbers | std::views::transform([](int n) { return n * n; });
take 和 drop 视图
take
视图从范围开始取前n个元素,而drop
视图则跳过前n个元素:
auto first_five = numbers | std::views::take(5); // 取前5个元素
auto after_five = numbers | std::views::drop(5); // 跳过前5个元素
reverse 视图
reverse
视图反转范围中元素的顺序:
auto reversed = numbers | std::views::reverse;
范围适配器(Range Adapters)
范围适配器是用于创建或修改范围的工具。它们通常通过管道操作符(|
)与其他范围组合。视图是一种特殊的范围适配器。
管道语法
C++20引入了管道操作符(|
)来组合范围操作:
auto result = range | adaptor1 | adaptor2 | ... | adaptorN;
这种语法使数据处理变得更加直观,类似于Unix shell中的管道或函数式编程中的链式调用。
实际应用案例
案例1:数据过滤与转换
假设我们有一个学生成绩列表,需要找出所有及格学生(分数>=60),并将他们的姓名转换为大写:
#include <iostream>
#include <vector>
#include <string>
#include <ranges>
#include <algorithm>
struct Student {
std::string name;
int score;
};
std::string to_upper(std::string str) {
std::transform(str.begin(), str.end(), str.begin(),
[](char c) { return std::toupper(c); });
return str;
}
int main() {
std::vector<Student> students = {
{"alice", 85},
{"bob", 55},
{"charlie", 90},
{"david", 45},
{"eva", 75}
};
auto passing_students_uppercase = students
| std::views::filter([](const Student& s) { return s.score >= 60; })
| std::views::transform([](const Student& s) { return to_upper(s.name); });
std::cout << "Passing students (uppercase): ";
for (const auto& name : passing_students_uppercase) {
std::cout << name << " ";
}
// 输出: Passing students (uppercase): ALICE CHARLIE EVA
}
案例2:惰性序列生成
范围可以用来创建无限序列,并通过视图进行操作:
#include <iostream>
#include <ranges>
int main() {
// 创建一个无限整数序列,从1开始
auto integers = std::views::iota(1);
// 取前10个偶数,并输出
auto first_ten_even = integers
| std::views::filter([](int n) { return n % 2 == 0; })
| std::views::take(10);
std::cout << "First 10 even integers: ";
for (int n : first_ten_even) {
std::cout << n << " ";
}
// 输出: First 10 even integers: 2 4 6 8 10 12 14 16 18 20
}
由于std::views::iota
可以创建无限序列,确保使用take
或其他限制视图,以避免无限循环。
性能考虑
范围库的设计目标之一是提供零开销抽象。理想情况下,使用范围的代码应该与手写的等效迭代器代码具有相同的性能。
然而,在某些情况下,特别是涉及复杂的视图组合时,编译器可能无法执行所有可能的优化。如果性能至关重要,建议进行基准测试以确保范围操作符合您的性能要求。
与STL的兼容性
范围库设计为与现有STL组件无缝协作。大多数标准容器都符合范围的要求,可以直接用于范围算法和视图。
此外,C++20还提供了适配器,使得现有的迭代器对可以作为范围使用:
std::vector<int> v = {1, 2, 3, 4, 5};
// 使用迭代器对创建范围
auto range = std::ranges::subrange(v.begin() + 1, v.end() - 1);
// range包含 {2, 3, 4}
总结
C++20范围库带来了一种全新的数据处理方式,使代码更加简洁、安全和可组合。通过范围的概念、范围算法和强大的视图系统,我们可以以更高级别的抽象处理数据集合,同时保持C++的性能特性。
主要优点包括:
- 更简洁的语法
- 更安全的操作
- 更易读的数据处理流程
- 惰性求值提高效率
- 强大的组合能力
尽管范围库在C++20中才成为标准,但它为C++的数据处理开辟了一条新路径,值得每位C++开发者学习和掌握。
练习
- 创建一个范围,筛选出字符串数组中长度大于5的字符串。
- 使用范围视图,找出向量中所有质数。
- 实现一个函数,使用范围视图从一个整数向量中找出连续3个数的和最大值。
- 使用范围适配器,将一个字符串列表按长度排序,然后只取前3个。
附加资源
通过学习C++20范围库,你将掌握一种现代化的数据处理方式,这将大大提高你的C++编程效率和代码质量。