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函数

在很多动态语言中,splitjoin都是很常用的方法,在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 “`