C++20新特性——Range及View
基本概念
Range
可以迭代访问的容器
View
通过视图看到的range内部的元素
View Adaptor
对视图上的元素进行处理,不更改原始值,获得我们需要的展示结果。
实例1
我们需要完成以下操作:
1. 初始化0-5的数组
2. 找出偶数
3. 对每个数进行平方
C++20写法:
cpp
#include <vector>
#include <ranges>
#include <iostream>
int main()
{
std::vector<int> ints{0,1,2,3,4,5};
auto even = [](int i){ return 0 == i % 2; };
auto square = [](int i) { return i * i; };
for (int i : ints | std::views::filter(even) | std::views::transform(square)) {
std::cout << i << ' ';
}
}
确定自己的GCC版本在10.1以上,即可编译运行:
shell
$ g++ -v
gcc 10.2.0
$ g++ --std=c++20 -o range01 range01.cpp
$ ./range01
0 4 16
实例2
我们需要完成以下步骤:
1. 初始化1-100
2. 平方每个元素
3. 找出能被4整除的数
4. 打印前五个数
史前写法🐊
cpp
int main()
{
std::vector<int> v(100), v1;
std::iota(v.begin(), v.end(), 1);
std::transform(v.begin(), v.end(), v.begin(),[] (int i) { return i * i; });
std::copy_if(v.begin(), v.end(), std::back_inserter(v1),[] (int i) { return i % 4 == 0; });
for (int i = 0; i < 5; i ++) {std::cout << v1[i] << ' ';}
std::cout << '\n';
}
C++20写法🚀
cpp
#include <ranges>
int main()
{
using namespace std::ranges;
for (int i : views::iota(1)
| views::transform( [] (int i) { return i*i; } )
| views::filter( [] (int i) { return i % 4 == 0; } )
| views::take(5)) {
std::cout << i << ' ';
}
std::cout << '\n';
}
确定自己的GCC版本在10.1以上,即可编译运行:
shell
$ g++ --std=c++20 -o range02 range02.cpp
$ ./range02
4 16 36 64 100
range库详细
使用
使用range库需要包含#include <ranges>头文件,使用std::ranges::views,std::ranges,std::views命名空间。
工厂函数
std::views::iota(1)表示从1开始,步长为1的view。其并没有终点,而是会根据实际需要生成,类似python3中的range,由lazy evaluation的特性。
访问其中的元素
cpp
int cnt = 0;
auto v1 = std::views::iota(1);
for (auto v1_it = v1.begin(); cnt < 5; cnt ++) {
std::cout << *v1_it++ << ' ';
}
std::cout << '\n';
// 1 2 3 4 5
for (int i : std::views::iota(1, 11)) {
std::cout << i << ' ';
}
std::cout << '\n';
// 1 2 3 4 5 6 7 8 9 10
可以看到,无论是iterator还是range base for loop都可以正确遍历。
串联view adaptor
使用for循环和计数器去限制打印的元素数量有些冗余,因此可使用views::take来限制元素个数:
cpp
using namespace std::ranges;
for (int i : views::iota(1) | views::take(5)) {
std::cout << i << ' ';
}
std::cout << '\n';
// 1 2 3 4 5
range库中,|被重载为了类似管道的效果。
此外还有views::transform(func)可以对每一个元素应用func()操作,views::filter(func)对元素进行筛选。
这里用的func可以使C++中任意的callable对象,包括函数指针,lambda表达式,std::bind()等。
cpp
#include <ranges>
#include <iostream>
using namespace std::ranges;
int main() {
std::string str{"Hello this article is from tjsw@medium."};
for (char c : str
| views::drop(6) // drop first 6 elements
| views::transform([] (char c) { return std::toupper(c); })
| views::filter([] (char c) { return !std::isspace(c); })) {
std::cout << c;
}
std::cout << '\n';
std::cout << str << '\n';
}
实际运行编译:
cpp
$ g++ --std=c++20 -o range03 range03.cpp
$ ./range03
THISARTICLEISFROMTJSW@MEDIUM.
Hello this article is from tjsw@medium.
上面的例子中,我们首先去掉6个字符,然后转为大写最后去除空格。
存储views adaptor
所有的view adaptor都是继承自view_interface<>模板类,再继承view_base类,我们可以将已经构建的串接转换器存储为一个对象。
cpp
using namespace std::ranges;
std::string str1{"Hello this article is from tjsw@medium."};
std::string str2{"keshi - like i need u"};
auto my_adaptor =
views::transform([] (char c) { return std::toupper(c); })
| views::filter([] (char c) { return !std::isspace(c); });
for (char c : str1 | views::drop(6) | my_adaptor) std::cout << c;
std::cout << '\n';
// THISARTICLEISFROMTJSW@MEDIUM.
for (char c : str2 | my_adaptor) std::cout << c;
std::cout << '\n';
// KESHI-LIKEINEEDU
已经存储的转换器对象仍然可以和其他的转换器串联使用。
Split和Join函数
在很多动态语言中,split和join都是很常用的方法,在range库中,提供了这两个adaptor。
cpp
using namespace std::ranges;
std::string str{"Hello this article is from tjsw@medium."};
for (auto word : str | views::split(' ')) {
std::cout << "-> ";
for (char c : word |
views::transform([] (char c) { return std::toupper(c); }) )
std::cout << c;
}
std::cout << '\n';
// -> HELLO-> THIS-> ARTICLE-> IS-> FROM-> TJSW@MEDIUM.
因为`views::split`并没有任何类型,我们可用其去拆解任意的容器。
cpp
std::vector vec{10, 11, 0, 0, 8, 0, 3, 4, 0, 0, 0, 1, 2, 3};
std::vector pat{0, 0};
for (auto nums : vec | views::split(pat)) {
std::cout << “-> “;
for (int i : nums)
std::cout << i << ‘ ‘;
}
std::cout << ‘\n’;
// -> 10 11 -> 8 0 3 4 -> 0 1 2 3
`views::join`是`split`的反操作。
cpp
using namespace std::ranges;
std::vectorstd::string strs{“Netflix”, “iQiyi”, “Spotify”};
for (char c : strs
| views::join // 特別注意:沒有括號
| views::transform([] (char c) { return std::tolower©; })) {
std::cout << c;
}
std::cout << ‘\n’;
// netflixiqiyispotify
> 参考资料:
1. [参考资料1](https://en.cppreference.com/w/cpp/ranges)
2. [参考资料2](http://jimmysue.me/c-20-features/#%E6%96%B0%E5%A2%9E%E6%A0%87%E8%AF%86%E7%AC%A6identifies)
3. [参考资料3](https://devblogs.microsoft.com/cppblog/initial-support-for-c20-ranges/)
4. [参考资料4](https://medium.com/@tjsw/%E6%BD%AE-c-20-ranges-views-%E4%BD%BF%E7%94%A8-stl-%E7%9A%84%E5%85%A8%E6%96%B0%E5%A7%BF%E5%8B%A2-8007777a8da6)
bash
1. https://en.cppreference.com/w/cpp/ranges2. http://jimmysue.me/c-20-features/#%E6%96%B0%E5%A2%9E%E6%A0%87%E8%AF%86%E7%AC%A6identifies3. https://devblogs.microsoft.com/cppblog/initial-support-for-c20-ranges/4. https://medium.com/@tjsw/%E6%BD%AE-c-20-ranges-views-%E4%BD%BF%E7%94%A8-stl-%E7%9A%84%E5%85%A8%E6%96%B0%E5%A7%BF%E5%8B%A2-8007777a8da6
“`