C++初学者指南-5.标准库(第二部分)–可组合范围视图
文章目录
不熟悉 C++ 的标准库算法? ⇒ 简介
请注意,C++23 的例子需要最新的编译器(例如 g++13)才能运行,并且一些工具如 ranges::to 或 views::enumerate 可能尚不可用。
介绍
<ranges> Library
返回视图对象的工厂
std::views::NAME (args…) → VIEW-OBJECT
- 像函数一样调用(通常作为函数对象实现)
- 创建视图的首选方式,但并非对所有视图类型都可用
#include <ranges>
std::vector<int> v {7,2,6,3,4};
for (int x : std::views::reverse(v)) { cout << x <<' '; } // 4 3 6 2 7
// using pipeline notation:
for (int x : v | std::views::reverse) { cout << x <<' '; } // 4 3 6 2 7
// storing a view in a variable:
auto rv = std::views::reverse(v);
// 'rv' has type 'std::ranges::reverse_view'
for (int x : rv) { cout << x <<' '; } // 4 3 6 2 7
视图类型
std::ranges::NAME_view {args…}
#include <ranges>
std::vector<int> v {7,2,6,3,4};
std::ranges::reverse_view rv {v};
for (int x : rv) { cout << x <<' '; } // 4 3 6 2 7
组合视图
可以用管道符号 | 将只接受单个输入范围(可能还有一些其他非范围参数)的视图连起来进行联动:
view1 | view2 | view3 | …
示例1
#include <ranges>
std::vector<int> input {1,3,4,2,5,2,7,8};
auto const is_even = [] (int x) { return !(x & 1); };
auto result = input
| std::views::reverse
| std::views::filter(is_even);
for (int x : result) { cout << x <<' '; } // 8 2 2 4
示例2
#include <ranges>
namespace views = std::views; // alias
std::vector<int> input {1,3,2,2,0,1,0,8,5,0};
auto const greater_0 = [] (int x) { return x > 0; };
auto const plus_1 = [] (int x) { return x + 1; };
auto result = input
| views::drop(2)
| views::filter(greater_0)
| views::transform(plus_1);
for (int x : result) { cout << x <<' '; } // 3 3 2 9 6
示例3
#include <ranges>
namespace views = std::views; // alias
std::vector<int> input {8,7,3,3,9};
auto result = views::zip(input, views::iota(1))
| views::reverse;
fmt::print("{}\n", result);
// [(9,5), (3,4), (3,3), (7,2), (8,1)]
转换为容器
cppreference
std::ranges::to 在最新的 g++/libstdc++ 中可能还不可用。
示例1
示例2
元素选择器
返回一个包含输入范围中单个元素的范围
filter
#include <ranges>
std::vector<int> v {7,2,6,3,4};
// custom predicate:
auto const is_even = [] (int x) { return !(x & 1); };
fmt::print("{}\n", std::views::filter(v,is_even)); // [2,6,4]
// using pipeline notation:
fmt::print("{}\n", v | std::views::filter(is_even)); // [2,6,4]
// using an explicit view object:
std::ranges::filter_view fv {v,is_even};
fmt::print("{}\n", fv); // [2,6,4]
stride
#include <ranges>
std::vector<int> v {0,1,2,3,4,5,6,7,8};
fmt::print("{}\n", std::views::stride(v,2)); // [0,2,4,6,8]
fmt::print("{}\n", std::views::stride(v,3)); // [0,3,6]
// using pipeline notation:
fmt::print("{}\n", v | std::views::stride(2)); // [0,2,4,6,8]
// using an explicit view object:
std::ranges::stride_view sv2 {v,2};
fmt::print("{}\n", sv2); // [0,2,4,6,8]
子范围选择器
返回输入范围的连续子范围
counted
#include <ranges>
std::vector<int> v {9,1,3,7,1,2,5,8};
fmt::print("{}\n", std::views::counted(v.begin()+2,4)); // [3,7,1,2]
运行示例代码
请注意,std::views::counted 不能用于管道操作,并且没有 std::ranges::counted_view 类型。
take
#include <ranges>
std::vector<int> v {0,1,2,3,4};
fmt::print("{}\n", std::views::take(v,3)); // [0,1,2]
// using pipeline notation:
fmt::print("{}\n", v | std::views::take(3)); // [0,1,2]
// using an explicit view object:
std::ranges::take_view tv {v,3};
fmt::print("{}\n", tv); // [0,1,2]
drop
#include <ranges>
std::vector<int> v {0,1,2,3,4};
fmt::print("{}\n", std::views::drop(v,2)); // [2,3,4]
// using pipeline notation:
fmt::print("{}\n", v | std::views::drop(2)); // [2,3,4]
// using an explicit view object:
std::ranges::drop_view dv {v,2};
fmt::print("{}\n", dv); // [2,3,4]
take_while
#include <ranges>
std::vector<int> v {2,6,4,3,8};
// custom predicate:
auto const is_even = [] (int x) { return !(x & 1); };
fmt::print("{}\n", std::views::take_while(v,is_even)); // [2,6,4]
// using pipeline notation:
fmt::print("{}\n", v | std::views::take_while(is_even)); // [2,6,4]
// using an explicit view object:
std::ranges::take_while_view tv {v,is_even};
fmt::print("{}\n", tv); // [2,6,4]
drop_while
#include <ranges>
std::vector<int> v {2,6,7,6,5};
// custom predicate:
auto const is_even = [] (int x) { return !(x & 1); };
fmt::print("{}\n", std::views::drop_while(v,is_even)); // [7,6,5]
// using pipeline notation:
fmt::print("{}\n", v | std::views::drop_while(is_even)); // [7,6,5]
// using an explicit view object:
std::ranges::drop_while_view dv {v,is_even};
fmt::print("{}\n", dv); // [7,6,5]
滑动窗口视图
返回输入范围内的一系列元组或连续子范围。
slide
#include <ranges>
std::vector<int> v {0,1,2,3,4};
for (auto x : std::views::slide(v,2)) { fmt::print("{} ",x); }
// [0,1] [1,2] [2,3] [3,4]
for (auto x : std::views::slide(v,3)) { fmt::print("{} ",x); }
// [0,1,2] [1,2,3] [2,3,4]
// using pipeline notation:
for (auto x : v | std::views::slide(3)) { fmt::print("{} ",x); }
// [0,1,2] [1,2,3] [2,3,4]
// using an explicit view object:
std::ranges::slide_view sv3 {v,3};
for (auto x : sv3) { fmt::print("{} ",x); }
// [0,1,2] [1,2,3] [2,3,4]
adjacent
#include <ranges>
std::string s = "abcde";
for (auto p : std::views::adjacent<3>(s)) {fmt::print("{} ",p); } // abc bcd cde
// using pipeline notation:
for (auto p : s | std::views::adjacent<3>) {fmt::print("{} ",p); } // abc bcd cde
for (auto p : s | std::views::pairwise) {fmt::print("{} ",p); } // ab bc cd de
函数应用
transform
#include <ranges>
#include <cctype> // std::toupper
std::string s = "abcd";
// function to be applied:
auto const to_upper = [] (char c) {
return static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
};
fmt::print("{}\n", std::views::transform(s,to_upper)); // [A,B,C,D]
// using pipeline notation:
fmt::print("{}\n", s | std::views::transform(to_upper)); // [A,B,C,D]
// using an explicit view object:
std::ranges::transform_view fv {s,to_upper};
fmt::print("{}\n", fv); // [A,B,C,D]
adjacent_transform
#include <ranges>
std::string s = "abcde";
auto const f3 = [] (char x, char y, char z) { return std::string{'<',x,y,z,'>'}; };
for (auto p : std::views::adjacent_transform<3>(s,f3)) { fmt::print("{} ",p); }
// <abc> <bcd> <cde>
auto const f2 = [] (char x, char y) { return std::string{'<',x,y,'>'}; };
for (auto p : std::views::pairwise_transform(s,f2)) { fmt::print("{} ",p); }
// <ab> <bc> <cd> <de>
// using pipeline notation:
for (auto p : s | std::views::pairwise_transform(f2)) { fmt::print("{} ",p); }
// <ab> <bc> <cd> <de>
标记化视图
返回输入范围的一系列连续子范围
split
#include <ranges>
std::vector<int> v {2,6,1,0,9,5,3,1,0,7};
for (auto x : std::views::split(v,1)) { fmt::print("{} ",x); }
// [2,6] [0,9,5,3] [0,7]
std::array<int,2> s {1,0};
for (auto x : std::views::split(v,s)) { fmt::print("{} ",x); }
// [2,6] [9,5,3] [7]
// using pipeline notation:
for (auto x : v | std::views::split(1)) { fmt::print("{} ",x); }
// [2,6] [0,9,5,3] [0,7]
// using an explicit view object:
std::ranges::split_view dv {v,1};
for (auto x : dv) { fmt::print("{} ",x); }
// [2,6] [0,9,5,3] [0,7]
lazy_split
cppreference
与split主要的区别在于 lazy_split 只有在通过 iterator 访问 lazy_split 返回的范围对象时,才会生成输出元素。
#include <ranges>
std::vector<int> v {2,6,1,0,9,5,3,1,0,7};
for (auto x : std::views::lazy_split(v,1)) { fmt::print("{} ",x); }
// [2,6] [0,9,5,3] [0,7]
std::array<int,2> s {1,0};
for (auto x : std::views::lazy_split(v,s)) { fmt::print("{} ",x); }
// [2,6] [9,5,3] [7]
// using pipeline notation:
for (auto x : v | std::views::lazy_split(1)) { fmt::print("{} ",x); }
// [2,6] [0,9,5,3] [0,7]
// using an explicit view object:
std::ranges::lazy_split_view dv {v,1};
for (auto x : dv) { fmt::print("{} ",x); }
// [2,6] [0,9,5,3] [0,7]
chunk
#include <ranges>
std::vector<int> v {0,1,2,3,4,5,6,7};
for (auto x : std::views::chunk(v,2)) { fmt::print("{} ",x); }
// [0,1] [2,3] [4,5] [6,7]
for (auto x : std::views::chunk(v,3)) { fmt::print("{} ",x); }
// [0,1,2] [3,4,5] [6,7]
// using pipeline notation:
for (auto x : v | std::views::chunk(3)) { fmt::print("{} ",x); }
// [0,1,2] [3,4,5] [6,7]
// using an explicit view object:
std::ranges::chunk_view sv3 {v,3};
for (auto x : sv3) { fmt::print("{} ",x); }
// [0,1,2] [3,4,5] [6,7]
chunk_by
#include <ranges>
std::vector<int> v {1,2,0,2,4,5,8,4,6,3,5,2,4};
for (auto x : std::views::chunk_by(v,std::ranges::less{})) { fmt::print("{} ",x); }
// [1,2] [0,2,4,5,8] [4,6] [3,5] [2,4]
// custom predicate:
auto const diff_less3 = [] (int x, int y) { return std::abs(x-y) < 3; };
for (auto x : std::views::chunk_by(v,diff_less3)) { fmt::print("{} ",x); }
// [1,2,0,2,4,5] [8] [4,6] [3,5] [2,4]
for (auto x : std::views::chunk_by(v,std::ranges::less{})) { fmt::print("{} ",x); }
// [1,2] [0,2,4,5,8] [4,6] [3,5] [2,4]
// using pipeline notation:
for (auto x : v | std::views::chunk_by(std::ranges::less{})) { fmt::print("{} ",x); }
// [1,2] [0,2,4,5,8] [4,6] [3,5] [2,4]
// using an explicit view object:
std::ranges::chunk_by_view sv3 {v,std::ranges::less{}};
for (auto x : sv3) { fmt::print("{} ",x); }
// [1,2] [0,2,4,5,8] [4,6] [3,5] [2,4]
多重范围 → 单一范围
join
#include <ranges>
std::vector<std::string> v = {"I","am","here","now"};
fmt::print("{}\n", std::views::join(v)); // Iamherenow
// using pipeline notation:
fmt::print("{}\n", v | std::views::join); // Iamherenow
// using an explicit view object:
std::ranges::join_view fv {v};
fmt::print("{}\n", fv); // Iamherenow
join_with
#include <ranges>
std::vector<std::string> v = {"I","am","here"};
fmt::print("{}\n", std::views::join_with(v,'#')); // I#am#here
std::vector<std::vector<int>> a {{7},{3,3,7},{2,6}};
std::vector<int> b {0,1};
fmt::print("{}\n", std::views::join_with(a,b)); // 7 0 1 3 3 7 0 1 2 6
// using pipeline notation:
fmt::print("{}\n", v | std::views::join_with('#')); // I#am#here
// using an explicit view object:
std::ranges::join_with_view fv {v,'#'};
fmt::print("{}\n", fv); // I#am#here
zip
#include <ranges>
std::string s1 = "abcd";
std::vector<int> v = {0,1,2,3,5};
fmt::print("{}\n", std::views::zip(s1,v));
// (a,0) (b,1) (c,2) (d,3)
std::array a {0,1};
std::string s2 = "AB";
std::string s3 = "-+";
fmt::print("{}\n", std::views::zip(a,s2,s3));
// (0,A,-) (1,B,+)
// using an explicit view object:
std::ranges::zip_view fv {s1,v};
fmt::print("{}\n", fv);
// (a,0) (b,1) (c,2) (d,3)
zip_transform
#include <ranges>
std::string s = "abcd";
std::vector<int> v = {0,1,2,3,4,5};
auto const f = [] (char c, int x) { return c + std::to_string(x); };
for (auto x : std::views::zip_transform(f,s,v)) { fmt::print("{} ",x); }
// ["a0","b1","c2","d3"]
// using an explicit view object:
std::ranges::zip_transform_view av {f,s,v};
for (auto x : av) { fmt::print("{} ",x); }
// ["a0","b1","c2","d3"]
cartesian_product
#include <ranges>
std::vector a {'a','b'};
std::vector b {1,2,3,4};
std::string c = "♥♣♦";
for (auto p : std::views::cartesian_product(a,b,c)) { fmt::print("{} ",p); }
/* [a,1,♥] [a,1,♣] [a,1,♦] [a,2,♥] [a,2,♣] [a,2,♦] [a,3,♥] [a,3,♣] [a,3,♦] [a,4,♥] [a,4,♣] [a,4,♦] [b,1,♥] [b,1,♣] [b,1,♦] [b,2,♥] [b,2,♣] [b,2,♦] [b,3,♥] [b,3,♣] [b,3,♦] [b,4,♥] [b,4,♣] [b,4,♦] */
助手
subrange
cppreference
将一个迭代器和一个哨兵(或两个迭代器)结合成一个视图。
#include <ranges>
std::vector<int> v {9,1,3,7,4,5,3,8};
auto const srv = std::ranges::subrange(v.begin()+2, v.begin()+6);
for (int x : srv) { std::cout << x << ' '; }
// 3 7 4 5
for (int x : std::views::reverse(srv)) { std::cout << x << ' '; }
// 5 4 7 3
enumerate
cppreference
enumerate 在最新的 g++/libstdc++ 中可能尚不可用
#include <ranges>
std::string s = "ABCD";
for (auto const& [index, value] : std::views::enumerate(s)) {
std::cout << "@" << index << ":" << value << '\n';
}
// @0:A @1:B @2:C @3:D
std::vector<std::string> words = {"This", "is", "a", "test."};
for (auto const& [index, value] : std::views::enumerate(words)) {
std::cout << "@" << index << ":" << value << '\n';
}
// @0:This @1:is @2:a @3:test.
元组投影
从一系列元组中提取出一系列单个元素。
keys
#include <ranges>
std::vector<std::pair<char,int>> v {{'b',3},{'a',4},{'z',2},{'k',9}};
fmt::print("{}\n", std::views::keys(v)); // [b,a,z,k]
// using pipeline notation:
fmt::print("{}\n", v | std::views::keys); // [b,a,z,k]
values
#include <ranges>
std::vector<std::pair<char,int>> v {{'b',3},{'a',4},{'z',2},{'k',9}};
fmt::print("{}\n", std::views::values(v)); // [3,4,2,9]
// using pipeline notation:
fmt::print("{}\n", v | std::views::values); // [3,4,2,9]
elements
#include <ranges>
std::vector<std::tuple<char,int,std::string>> v {
{'b',3,"hearts"}, {'a',4,"clubs"}, {'z',2,"spades"}, {'k',9,"diamonds"} };
fmt::print("{}\n", std::views::elements<2>(v));
// hearts clubs spades diamonds
// using pipeline notation:
fmt::print("{}\n", v | std::views::elements<2>);
// hearts clubs spades diamonds
非范围–>范围
从非范围输入生成范围的工厂
iota
#include <ranges>
auto i37 = std::views::iota(3,8);
for (int x : i37) { std::cout << x << ' '; }
// 3 4 5 6 7
auto i17 = std::views::iota(1) | std::views::take(7);
for (int x : i17) { std::cout << x << ' '; }
// 1 2 3 4 5 6 7
repeat
#include <ranges>
auto const r51 = std::views::repeat(1,5);
for (int x : r51) { std::cout << x << ' '; }
// 1 1 1 1 1
auto const r4a = std::views::repeat('a') | std::views::take(4);
for (char x : r4a) { std::cout << x << ' '; }
// a a a a
istream
empty
single
特殊适配器
ref_view
owning_view
all
common
备忘单
相关内容
Standard Sequence Views
cppreference: Algorithms Library
cppreference: Containers Library
An Overview of Standard Ranges (Tristan Brindle, 2019)
Conquering C++20 Ranges (Tristan Brindle, 2021)
Range Algorithms, Views and Actions - A Comprehensive Guide (Yitzchaki Dvir, 2019)
From STL to Ranges: Using Ranges Effectively (Garland Jeff, 2019)
What a View! Building Your Own (Lazy) Range Adaptors [1/2] (Di Bella Chris, 2019)
What a View! Building Your Own (Lazy) Range Adaptors [2/2] (Di Bella Chris, 2019)
Using C++20 Ranges Effectively (Jeff Garland, 2019)
如果文章对您有用,请随手点个赞,谢谢!^_^