迭代器
容器一般会提供成员函数begin()/end()/cbegin()/cend().调用它们就可以得到表示两个端点的迭代器,有"c"前缀的函数返回的是常量迭代器,但具体类型最好用自动类型推导:
vector<int> v = {1, 2, 3, 4, 5};
anto iter1 = v.begin();
auto iter2 = v.end();
//全局函数,效果一样
auto iter3 = std::begin(v);
auto iter3 = std::end(v);
迭代器和指针类似,也可以前进或者后退,但绝不能假设它一定支持"++" "--"操作,最好用函数来操作:
advance():迭代器前进或者后退指定步数
next()/prev():获得迭代器前后的某个位置,迭代器自身不移动
distance():计算两个迭代器之间的距离
std::array<int, 5> testArray{ 0, 1, 2, 3, 4 };
auto beginIter = testArray.begin();
auto endIter = testArray.end();
int dis = std::distance(beginIter, endIter);
std::cout << "distance " << dis << std::endl;
auto p = std::next(beginIter);
std::cout << "next is " << *p << std::endl;
std::advance(p, 2);
std::cout << "advance is " << *p << std::endl;
STL算法只能通过迭代器去间接访问容器以及元素,所以算法的处理范围和能力是由迭代器决定的。
遍历算法
for_each:
vector<int> v = {3, 5, 1, 7, 10}; //向量容器
auto print = [](const auto& x)
{
cout << x << ",";
};
for_each(cbegin(v), cend(v), print);
for_each(
cbegin(v), cend(v),
[](const auto& x)
{
cout << x << ",";
}
);
C++17新增for_each_n,可以不同遍历容器,只需处理容器的前n个元素,满足了一些特殊的应用场合:
for_each_n(
cbegin(v), 3, //指定起点和个数
[](const auto& x){
...
}
);
排序算法
std::sort(begin(v), end(v)); //快速排序
vector<int> v = {3, 5, 1, 7, 10, 99, 14};
//反转已有次序 reverse
std::reverse(begin(v), end(v), rand);
minstd_rand rand; //随机数发生器
std::shuffle(begin(v), end(v), rand); //随机乱序重排元素
//TopN
std::partial_sort(begin(v), next(begin(v), 3), end(v)); //取前3名
//BestN
std::ntn_element(begin(v), next(begin(v), 3), end(v)); //取前3名。且不对其再排序
//中位数
auto mid_iter = next(begin(v), size(n) / 2);
std::ntn_element(begin(v), mid_iter, 3), end(v));
cout << "median is " << *mid_iter << endl;
//partition按照某种规则把元素划分成两组
auto pos = std::partition(
begin(v), end(v),
[](const auto& x){
return x > 9;
}
);
for_each(begin(v), pos, print); //输出分组后的数据
//min-max找出第一名和最后一名
auto[mi, ma] = std::minmax_element(cbegin(v), cend(v));
在使用这些排序算法的时候要注意,它们对迭代器的要求比较高,通常都是随机访问迭代器,所以最好用在顺序容器 array/vector上调用。
如果对list排序,应该调用成员函数sort,它对链表结构做了优化。set/map本身已经排序好,直接运算。对于无序容器,不要使用排序算法。
查找算法
二分查找:binary_search
vector<int> v = {3, 5, 1, 7, 10, 99, 42};
std::sort(begin(v), end(v));
auto found = std::binary_search(cbegin(v), cend(v), 7); //返回bool,只能确定元素在不在
返回第一个大于等于值的位置:std::lower_bound
decltype(cend(v)) pos; //声明一个迭代器,使用decltype
pos = std::lower_bound(cbegin(v), cend(v), 7); //找到第一个大于等于7的位置
found = (pos != cend(v)) && (*pos == 7);
assert(found);
pos = std::lower_bound(cbegin(v), cend(v), 9);
found = (pos != cend(v)) && (*pos == 9);
assert(!found);
返回大于目标值的最后一个位置:std::upper_bound
pos = std::upper_bound(cbegin(v), cend(v), 9); //找到第一个大于9的位置
获得[lower_bound,upper_bound]这个区间:std::equal_range
auto [lower, upper] = std::equal_range(cbegin(v), cend(v), 7);
查找第一个指定元素出现的位置:find
vector<int> v = {1,9,11,3,5,7}; //向量容器
decltype(v.begin()) pos; //声明一个迭代器,使用decltype
pos = find(begin(v), end(v), 3); //查找第一次出现3的位置
assert(pos != end(v));
使用条件查找:find_if
pos = std::find_if(begin(v), end(v), [](auto x){return x % 2 == 0; });
查找一个子区间:find_first_of
array<int, 2> arr = {3, 5}; //数组容器
pos = std::find_first_of(begin(v), end(v), begin(arr), end(arr)); //查找一个子区间
范围算法
C++20引入了新的range类型,它是容器上的一个抽象概念,可以理解成指明首末位置的迭代器,即pair<begin,end>,这样range自身就包含能用于算法的足够信息,大多数算法只要用一个range参数就可以工作。基于range的概念,C++在名字空间std::ranges提供了与标准算法同名、但却使用range参数的算法,写法很简洁。
vector<int> v = {9, 5,2,3,1};
auto print = [](const auto &x){
cout << x << ",";
}
namespaces ranges = std::ranges; //名字空间别名,用于简化代码
ranges::for_each(v, print); //范围for_each算法
ranges::count_if(v, [auto &x]{return x >= 5;}); //范围count_if算法
ranges::shuffle(v, rand); //范围随机重排元素
ranges::stable_sort(v); //范围保持相对顺序
ranges::binary_search(v, 7); //范围二分查找
auto pos = ranges::lower_bound(v, 3); //范围二分查找是否存在
ranges::partial_sort(v, next(begin(v), 3)); //范围部分排序
auto [mi, ma] = ranges::minmax_element(v); //范围查找最大值和最小值
C++20也引入了view,它是容器的视图,具有一定的数据操作能力,而且还重载了"|",能够实现多个view的串联操作。位于名字空间std::views。
- take:选出view里的前N个元素
- drop:跳过view里的前N个元素
- keys:选出pair里的first成员
- values:选出pair里的second成员
- reverse:逆序view里的所有元素
- filter:使用谓词函数筛选出view里的特定元素
view的使用和传统函数调用方式差异特别大:
vector<int> v ={3,7,2,4,9,6,8,1,5};
namespace views = std::views;
for(auto && x: v | views::drop(3) | views::reverse) //管道操作符串联view操作
{
cout << x << ","; //输出5,1,8,6,9,4
}
v | views::drop(3) :表示drop作用于v,跳过前面3个元素,只保留剩余的元素,也就是{4,9,6,8,1,5},它操作之后得到的仍然是一个view,所以还可以继续使用管道操作。
之后的|views::reverse 表示反转操作,把前面的view逆序,将最终得到的结果传递给for循环。
上面操作等价于:
auto r1 = v | views::drop(3);
auto r2 = r1 | views::reverse;
ranges::for_each(r2, print); //调用范围算法输出结果
进一步示例:
ranges::for_each(v | views::filter(
[](auto &x){
return x % 2 == 0;
}),
print
); //输出2,4,6,8
decltype(v) v2;
ranges::copy(v | views::take(3), //只取前3个元素
back_inserter(v2)); //将元素插入新容器
vector<pair<int, string>> pairs = {{1, "one"}, {2, "two"}, {3, "three"}};
ranges::for_each(
pairs | views::take(2) | views::values, //取前两个元素,在取second成员
print //输出 one two
);