ranges库在对元素进行逐一操作或者判断时可以省掉很多循环体,使代码的可读性提高。
例如,要从一个vector中拿出所有的偶数并求平方并逆序排列,生成一个新的vector,以前这样写:
vector<int> v1 = {0, 1, 2, 3, 4};
vector<int> v2;
for (auto i : v1) {
if (i % 2 == 0) {
v2.push_back(i * i);
}
}
vector<int> v3(v2.rbegin(), v2.rend());
有了ranges后就可以用管道符(|)的形式写,对数据先后做了怎样的操作一目了然。
for (int i : v1 | views::filter([](int i) { return i % 2 == 0; })
| views::transform([](int i) { return i * i; })
| views::reverse) {
printf("%d ", i);
}
下面我们了解一下这个库
首先是range的概念,它是一个concepts,定义如下:
即任何类型,只要它有首尾迭代器,它就是个range。我们常用的STL、数组、string,都是range。
然后是view(视图)的概念,它是指一段元素从某个“视角”看过去的样子,在这个视图中的真实值都是惰性求值的,拿上面这个例子来说,看似我们对一段range进行了逐元素求平方的操作,实际上这个操作是在遍历过程中计算的,并不是在生成这个view对象时计算的。因此view只是一个壳子,它不具有元素真正的内存空间,可以进行任意拷贝。
下面介绍几种range adapter,它用来对一段range或view生成另一个view
1. filter view,对一段view进行过滤,筛选出符合条件的元素
vector<int> vec{0, 1, 2, 3, 4}; // 用vector作为一个range
auto even = [](int i) { return i % 2 == 0; }; // 过滤出其中的所有偶数
for (int i : vec | views::filter(even)) { // 管道符形式
printf("%d ", i);
}
for (int i : views::filter(vec, even)) { // 函数形式
printf("%d ", i);
}
for (int i : ranges::filter_view(vec, even)) { // 函数形式
printf("%d ", i);
}
auto v = vec | views::filter(even);
vector<int> b(v.begin(), v.end()); // 用view的迭代器来构造一个新的vector
2. transform view,对一段view进行逐元素的变换
string str{"Hello"}; // 用string作为一个range
auto to_upper = [](char c) { return toupper(c); }; // 将每个元素都转为大写
for (char c : str | views::transform(to_upper)) { // 管道符形式
printf("%c", c);
}
for (char c : views::transform(str, to_upper)) { // 函数形式
printf("%c", c);
}
for (char c : ranges::transform_view(str, to_upper)) { // 函数形式
printf("%c", c);
}
auto v = str | views::transform(to_upper);
string s(v.begin(), v.end()); // 用view的迭代器来构造一个新的string
|