在 C++20 之前,处理数组和容器的“视图”通常需要自己手动实现或依赖第三方库,例如 Boost。C++20 引入了 std::span 和 std::ranges,这两者结合起来可以极大地简化数组、向量和其他容器的子集处理。本文将从基本概念入手,演示如何使用 std::span 和 std::ranges 对数组进行切片、过滤和变换,帮助你在项目中更高效、更安全地处理数据。
1. 何为 std::span?
std::span 是一个非拥有的“视图”类型,表示一段连续的内存块。它不管理内存生命周期,只提供对已有数据的只读或可写访问。典型用法如下:
#include <span>
#include <vector>
#include <iostream>
void print_span(std::span <int> s) {
for (auto v : s) std::cout << v << ' ';
std::cout << '\n';
}
int main() {
int arr[] = {1,2,3,4,5,6};
std::span <int> whole(arr); // 覆盖整个数组
std::span <int> half(arr, 3); // 覆盖前3个元素
print_span(whole); // 输出: 1 2 3 4 5 6
print_span(half); // 输出: 1 2 3
}
- `std::span ` 与 `std::span` 区分只读/可写。
- 通过
std::as_bytes可以将任意类型转换为字节视图,常用于序列化。
2. std::ranges 的基本概念
std::ranges 引入了一套视图(views)和算法(algorithms),使得链式操作更自然。视图是惰性求值的,不会立即产生中间容器,直到真正需要结果时才计算。常见视图包括 std::views::filter, std::views::transform, std::views::reverse 等。
#include <ranges>
#include <vector>
#include <iostream>
int main() {
std::vector <int> vec{1,2,3,4,5,6,7,8};
// 过滤偶数并乘以 10
auto result = vec | std::views::filter([](int x){ return x % 2 == 0; })
| std::views::transform([](int x){ return x * 10; });
for (auto v : result) std::cout << v << ' ';
std::cout << '\n'; // 输出: 20 40 60 80
}
3. 将 std::span 与 std::ranges 结合使用
结合 std::span 与 std::ranges 可以实现对数组的切片、过滤、变换,且不产生额外拷贝。
#include <span>
#include <ranges>
#include <vector>
#include <iostream>
int main() {
int data[] = {3, 7, 1, 4, 9, 2, 5};
// 创建 span,取下标 2 到 6 的子段
std::span <int> sub(data + 2, 4); // {1, 4, 9, 2}
// 过滤出偶数并累加
int sum = std::accumulate(
sub | std::views::filter([](int x){ return x % 2 == 0; }),
0);
std::cout << "偶数之和: " << sum << '\n'; // 输出: 6
}
小技巧
- 使用
sub | std::views::take(n)可以进一步限定长度。 sub | std::views::reverse可以逆序访问。std::ranges::sort(sub)可以直接对 span 进行原地排序。
4. 性能与安全性
- 无拷贝:
std::span本身不复制数据,视图仅指向原数据。结合视图的惰性求值,整个链式操作在内部只扫描一次,几乎没有额外开销。 - 边界检查:与
std::array或std::vector的索引操作相比,std::span在访问时不会自动检查越界(因为它是一个视图),但如果你使用std::span::at(),则会抛出异常。需要根据场景自行决定是否开启。 - 生命周期:
std::span只安全用于在其生命周期内有效的数据。不要把指向局部数组的 span 返回给外部。
5. 进阶:自定义视图
如果你想实现更复杂的切片规则(如步长、窗口滑动),可以自定义视图:
namespace myviews {
struct step_view : std::ranges::view_interface <step_view> {
std::span <int> base_;
std::size_t step_;
auto begin() const { return base_.begin(); }
auto end() const { return base_.end(); }
// 你可以在此实现自定义迭代器
};
}
这在需要非连续步进访问(如取每隔两个元素)时非常有用。
6. 结语
std::span 与 std::ranges 的组合提供了一种现代、类型安全、无拷贝的数据处理方式。无论是日常的数组切片,还是复杂的过滤和变换,利用它们都能写出更简洁、更易维护的 C++ 代码。建议在新项目中立即启用 C++20 标准,并尝试将旧代码迁移到基于 span 与 ranges 的实现方式,长期来看将获得可读性和性能的双重收益。