C++实战笔记(六)

迭代器

容器一般会提供成员函数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
);

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值