0. 写在最前面

本文持续更新地址:https://haoqchen.site/2020/06/08/all-kind-of-loop-2/

上一篇文章C++各种循环方式梳理及对比之深入到汇编看while和for深入到汇编对比了while和for的效率问题,这篇将集中在另外几种看上去比较高大上的循环写法。

这些写法一般只是for或者while的一层封装,效率与自己实现的for循环相当,甚至要差。但他们优势在于简化了代码,并且减少了代码出错的可能。另外,C++17之后的algorithm库实现了并行运算的功能,可以快捷地通过参数配置并行计算,不用自己敲多线程。我暂时还没有到C++17,没能力介绍这方面的内容,有兴趣可以看看对应的官网链接,在参考中有给出。

如果觉得写得还不错,可以找我其他文章来看看哦~~~可以的话帮我github点个赞呗。
你的Star是作者坚持下去的最大动力哦~~~

1. std::for_each与std::for_each_n

1.1 定义

1.1.1 std::for_each

template <class InputIterator, class Function>
Function for_each (InputIterator first, InputIterator last, Function fn);

第一和第二个参数分别是迭代器的首尾地址,最后一个传入的是函数对象。这就要求:

  1. 遍历的对象必须是实现了迭代器的结构,比如std::vector、std::queue等。
  2. 要将处理方法封装成函数对象,包括lambda表达式、仿函数对象、函数指针、std::function等。

官网说了,这个函数的功能跟下面的代码是等效的:

template<class InputIterator, class Function>
Function for_each(InputIterator first, InputIterator last, Function fn)
{while (first!=last) {fn (*first);++first;}return fn;      // or, since C++11: return move(fn);
}

说白了就是一个利用迭代器实现的while遍历,这是在C++11的auto之前出现的。

1.1.2 std::for_each_n

template< class InputIt, class Size, class UnaryFunction > // since C++17
InputIt for_each_n( InputIt first, Size n, UnaryFunction f );

std::for_each只遍历n个的版本,与下面的代码等效:

template<class InputIt, class Size, class UnaryFunction>
InputIt for_each_n(InputIt first, Size n, UnaryFunction f)
{for (Size i = 0; i < n; ++first, (void) ++i) {f(*first);}return first;
}

在不设置并行执行规则ExecutionPolicy的情况下,这两个函数的执行是保证按顺序执行的。

1.2 用法

最简单就是使用lambda表达式来实现了:

#include <vector>
#include <algorithm>
std::vector<int> container(10, 0);std::for_each(container.begin(), container.end(), [](int& i){i+= 10;
});std::for_each_n(container.begin(), 10, [](int& i){i+= 10;
});

比如求平均等更多的应用可以参考如何使用std::for_each以及基于范围的for循环 这篇文章。

我尝试去找这种用法跟我们最原始的for-loop的区别,各位大佬的意思是,for_each是auto之前的产物,主要防止新手用for-loop各种出错,而且能避免不会用而导致性能下降。还有降低圈复杂度的???

比如很多人会写成for(auto it = c.begin(); it <= c.end(); ++it),但不是所有迭代器都实现了小于、大于号,要写成for(auto it = c.begin(); it != c.end(); ++it)

2. 基于范围(range-based)的for循环

2.1 定义

这是C++ 11新增的一种循环,主要作用是简化一种常见的循环任务:对数组或容器类(如vector和array)的每个元素执行相同的操作。

attr(optional) for ( range_declaration : range_expression )
loop_statement                                      // (until C++20)attr(optional) for ( init-statement(optional)range_declaration : range_expression )
loop_statement                                      // (since C++20)
  • attr:函数前缀,貌似声明一些特性有用的,可选。目前不是很清楚,有兴趣可了解attribute specifier sequence(since C++11)
  • init-statement(optional:这个是C++20才加上的,一个以分号;结尾的表达式。一般是一个初始化表达式
  • range_declaration:声明一个变量,变量的类型为range_expression的类型或者这个类型的引用,一般用auto来自动匹配即可。这个可以是结构化绑定声明(Structured binding declaration)。
  • range_expression:需要循环的数组、容器或花括号初始化列表,如果为容器,必须要实现begin函数和end函数。

基于范围的for循环可等效成下面的for:

{auto && __range = range_expression ;for (auto __begin = begin_expr, __end = end_expr; __begin != __end; ++__begin) {range_declaration = *__begin;loop_statement} // (until C++17)
}

结构化绑定声明:

for (auto&& [first,second] : mymap) { // since C++17// 使用 first 和 second
}

注意

  • range_expression不能返回临时变量,例如不能是一个返回值的函数,否则将导致不确定行为。
  • 如果range_declaration不是引用,而且存在copy-on-write特性,基于范围的for循环可能会触发一个深拷贝

2.2 用法

借用cppreference的一个例子来说明:

#include <iostream>
#include <vector>int main() {std::vector<int> v = {0, 1, 2, 3, 4, 5};for (const int& i : v) // 以 const 引用访问std::cout << i << ' ';std::cout << '\n';for (auto i : v) // 以值访问,i 的类型是 intstd::cout << i << ' ';std::cout << '\n';for (auto& i : v) // 以引用访问,i 的类型是 int&std::cout << i << ' ';std::cout << '\n';for (int n : {0, 1, 2, 3, 4, 5}) // 初始化器可以是花括号初始化器列表std::cout << n << ' ';std::cout << '\n';int a[] = {0, 1, 2, 3, 4, 5};for (int n : a) // 初始化器可以是数组std::cout << n << ' ';std::cout << '\n';for (int n : a)  std::cout << 1 << ' '; // 不必使用循环变量std::cout << '\n';}

3. std::transform

3.1 定义

这个函数的作用是将输入的,具有迭代器的1个或2个容器InputIterator做一定的操作,并将结果保存到result的起始位置中,执行顺序不做保证

// 定义1
template <class InputIterator, class OutputIterator, class UnaryOperation>
OutputIterator transform (InputIterator first1, InputIterator last1,OutputIterator result, UnaryOperation op);
// 定义2
template <class InputIterator1, class InputIterator2,class OutputIterator, class BinaryOperation>
OutputIterator transform (InputIterator1 first1, InputIterator1 last1,InputIterator2 first2, OutputIterator result,BinaryOperation binary_op);
  • unary operation将[first1,last1)范围内的每一个元素进行op操作,并将每个op的的返回值存储到result中
  • binary operation将[first1,last1)的每一个元素和起始地址为first2对应的元素,分别作为参数1和参数2放到binary_op中,并将每个返回值放到result中

根据官网的介绍,这个函数等效与一下循环:

template <class InputIterator, class OutputIterator, class UnaryOperator>
OutputIterator transform (InputIterator first1, InputIterator last1,OutputIterator result, UnaryOperator op)
{while (first1 != last1) {*result = op(*first1);  // or: *result=binary_op(*first1,*first2++);++result; ++first1;}return result;
}

3.2 用法

借鉴官方的例子:

// transform algorithm example
#include <iostream>     // std::cout
#include <algorithm>    // std::transform
#include <vector>       // std::vector
#include <functional>   // std::plusint op_increase (int i) { return ++i; }int main () {std::vector<int> foo;std::vector<int> bar;// set some values:for (int i=1; i<6; i++)foo.push_back (i*10);                         // foo: 10 20 30 40 50bar.resize(foo.size());                         // allocate spacestd::transform (foo.begin(), foo.end(), bar.begin(), op_increase);// bar: 11 21 31 41 51// std::plus adds together its two arguments:std::transform (foo.begin(), foo.end(), bar.begin(), foo.begin(), std::plus<int>());// foo: 21 41 61 81 101std::cout << "foo contains:";for (std::vector<int>::iterator it=foo.begin(); it!=foo.end(); ++it)std::cout << ' ' << *it;std::cout << '\n';return 0;
}

注意

  • result可以指向输入
  • result容器的size要大于等于[first1,last1)的大小,如果result为空时,需要使用std::back_inserter(result),std::back_inserter要求result实现了push_back函数。这个时候会导致性能下降,详情请参考我另一篇文章emplace_back VS push_back

4. std::transform、std::for_each、for的区别

  1. for_each返回的是函数,所以可以通过函数对象来对数据求和,比如:
class MeanValue
{public:MeanValue() : count_(0), sum_(0) {}void operator() (int val){sum_ += val;++count_;}operator double(){if ( count_ <= 0 ){return 0;}return sum_ / count_;}
private:double      sum_;int         count_;
};
//for_each returns a copy of MeanValue(), then use operator double().
// same with:
// MeanValue mv = for_each(coll2.begin(), coll2.end(), MeanValue());
// double meanValue = mv;
// for_each返回传入MeanValue()的副本,然后调用operator double()转换为double.
double meanValue = for_each(coll2.begin(), coll2.end(), MeanValue());
  1. transform的参数要求更严格点,他要求操作有返回值,而for_each忽略了操作返回值,所以没有这个要求
  2. C++17之后algorithm相关算法都支持并行计算,修改一个参数就行,如果是for循环,需要自己实现多线程。
  3. 需要注意一点,调用函数是有压栈、出栈的性能损失的,循环地调用函数性能会受很大影响。可以将整个vertor传入到函数中,再在函数中进行for循环,这样可减少这样的性能损失,这只能通过自己实现最原始的for循环实现。
  4. 不并行运算的情况下,for_each保证执行的顺序,而transform不能保证执行的顺序。
  5. for_each和transform都默认使用迭代器,原始for循环可以使用索引[],在一些编译器上,这两者的效率是有很大区别的。具体可以参考这个测试:c++ - bool数组上的Raw循环比transform或for_each快5倍
  6. 在循环次数很大时,algorithm的一些实现就可以忽略不计,各种的效率几乎是一样的。

参考

  • 文中连接
  • std::for_each
  • Range-based for loop (since C++11)
  • std::for_each_n
  • std::transform

喜欢我的文章的话Star一下呗Star

版权声明:本文为白夜行的狼原创文章,未经允许不得以任何形式转载

C++各种循环方式梳理及对比(2)高级循环相关推荐

  1. 冷却水的循环方式有哪几种_循环冷却水系统,按照通风方式可分为 和 两种。...

    循环冷却水系统,按照通风方式可分为 和 两种. 答: 自然通风冷却系统 机械通风冷却系统 创业过程是一个充满了不确定性的过程,团队中可能因为能力.观念等多种原因不断有人在离开,同时也有人在要求加入.因 ...

  2. 三种方式细胞评分对比

    三种方式细胞评分对比 library(patchwork) library(ggplot2) library(ggalluvial) library(svglite) library(Seurat) ...

  3. python和R数据类型查看、赋值、列表、for循环、函数用法对比示例

    python和R数据类型查看.赋值.列表.for循环.函数用法对比示例 很多工程师可能刚开始的时候只熟悉python或者R其中的一个进行数据科学相关的任务. 那么如果我们对比这学习可以快速了解语言设计 ...

  4. 数组常见的遍历循环方法、数组的循环遍历的效率对比

    1 遍历数组的方法 1-1.for / while 最普通的循环 效率最高 兼容ie6 tips:for循环更适用于循环的开始和结束已知,循环次数固定的场合:while循环更适合于条件不确定的场合 1 ...

  5. oracle循环的方式,Oracle 的几种循环方式介绍

    1 Oracle 中的Goto 用法: declare x number; begin x:=10; --定义的初始值 <> --循环点 x:= x-2; -- 循环的处理条件 dbms_ ...

  6. CV之Hog+HamMingDistance:基于Hog提取和汉明距离对比的应用—图像相似度对比之for循环将多个成对图片依次对比并输出相似度

    CV之Hog+HamMingDistance:基于Hog提取和汉明距离对比的应用-图像相似度对比之for循环将多个成对图片依次对比并输出相似度 目录 测试数据集 核心代码 相关文章 ML之相似度计算: ...

  7. 指针数组的初始化和遍历,并且通过for循环方式、函数传参方式进行指针数组的遍历...

    1 /************************************************************************* 2 > File Name: messa ...

  8. java用循环方式实现和计算机玩猜拳的程序

    用循环方式实现和计算机玩猜拳的程序 (设定胜出条件--输3次或赢3次即退出) /*int a=0;//统计赢得次数 int b=0;//统计输的次数 for(;;){ System.out.print ...

  9. 【温故知新】——原生js中常用的四种循环方式

    一.引言 本文主要是利用一个例子,讲一下原生js中常用的四种循环方式的使用与区别: 实现效果: 在网页中弹出框输入0   网页输出"欢迎下次光临" 在网页中弹出框输入1   网页输 ...

最新文章

  1. STM8单片机定时器驱动的深度解析
  2. 12-Generic Timer
  3. 审计 Linux 系统的操作行为的 5 种方案对比
  4. step2 . day7 C语言阶段小的项目总结
  5. git指令如何葱master转到dev_小姐姐用动画图解Git命令,一看就懂!
  6. Idea Marketplace 加载很慢 加载不出来
  7. mysql数据库事务的概念_如何理解数据库事务中的一致性的概念?
  8. shell 脚本执行报错/bin/bash^M: bad interpreter: No such file or directory
  9. Django的model form组件
  10. 数据校验简介与C/C++代码实现
  11. Adobe AI软件解决界面字体过小的方法
  12. C语言程序设计基础篇
  13. Ubuntu20.04 安装matlab2017b
  14. IOTOS物联中台对接海康安防平台(iSecure Center)门禁系统
  15. Unity中,图片 替换 光标 —— 疑问解答
  16. IIS无法启动解决方案
  17. lighttp 配置php扩展包,Lighttpd配置
  18. AIX虚拟内存管理机制(转)
  19. React/Vue/Nerv 任你选,多端框架 Taro 发布 3.0 RC 版本
  20. linux卸载phpstudy_centos

热门文章

  1. 基于flowable的开源OA
  2. 微信域名检测php,微信域名检测API接口PHP代码
  3. 林业大学计算机专业排名2019,2019年东北林业大学优势专业排名及分数线
  4. python防反编译_用Python和Smali模拟器搞定一个加混淆、防篡改的APK逆向
  5. 【安卓APP源码和设计报告(含PPT)——订餐系统
  6. 前端和后端是如何交互的
  7. 盗墓笔记android,盗墓笔记安卓版下载
  8. 一天一个小知识:KT高阶函数
  9. php地图封装,百度地图应用封装——静态图api
  10. 自适应网页设计的方法(手机端良好的访问体验)