文档章节

C++11 lambda表达式在for_each和transform算法下的使用

尘中远
 尘中远
发布于 2016/05/12 23:54
字数 2655
阅读 15
收藏 0
以前,在lambda表达式没有进入标准的时候,对容器的遍历等涉及到使用函数指针的情况,一般人会懒得使用std::for_each,或std::transform,也许只是一个短短的几句话,却要单独写个函数,或函数对象,写的代码反而不如自己用for循环来的快。
但是,C++11引入了lambda表达式后,一切都变的简单了!


1.lambda表达式

lambda表达式是一个匿名函数,用它可以非常方便的表示一个函数对象,先简单说一下lambda表达式,下面这张图表示了C++11中lambda表达式的写法


  1. Lambda表达式的引入标志,在‘[]’里面可以填入‘=’或‘&’表示该lambda表达式“捕获”(lambda表达式在一定的scope可以访问的数据)的数据时以什么方式捕获的,‘&’表示一引用的方式;‘=’表明以值传递的方式捕获,除非专门指出。

  2. Lambda表达式的参数列表

  3. Mutable 标识(可以没有)

  4. 异常标识(可以没有)

  5. 返回值,如果没有,可以不写

  6. “函数”体,也就是lambda表达式需要进行的实际操作

下面看看几个lambda表达式的例子

void print(int a){……}
上面函数lambda表达式为:

[](int a){……}
上面这个是无返回值的例子,下面这个是有返回值的例子
int add(int a,int b){……}
上面函数lambda表达式为:
[](int a,int b)->int{……}
当需要引入其他变量的时候,如有个类的成员变量需要引用或者函数局部变量这种情况下可以显示声明需要引入的变量
如成员变量:
double m_result;
函数:

void foo(int a,double b)
{
    ……
    use(m_result);
}
其lambda表达式可以表示为
[m_result](int a,int b){……use(m_result);}
如果lambda表达式需要修改m_result的值,可以以引用方式传递进去
[&m_result](int a,int b){……m_result = 12.1;……}
如果要传入的参数很多,或者干脆在这个作用域里的所有参数都想用到,可以直接在中括号里使用”=“或”&“
如下例子,g_bb为一个全局变量,fun3的lambda表达式把所有内容以引用方式传入:
double g_bb = 11.2;
void foo1()
{
    auto f_add = [&](int a,int b)->int{return a+b;};
    std::cout<<f_add(1,2);//3
    std::cout<<std::endl;
    
    double aa = 5.0;
    auto fun = [aa]()->double{return aa+3;};//此时aa不能进行赋值操作如:aa=7;
    std::cout<<fun();
    std::cout<<"  aa:"<<aa<<std::endl;//8 aa:5
    auto fun2 = [&aa]()->double{aa = 7.0;return aa+3;};//此时aa以引用方式传入,可以进行赋值操作如:aa=7,同时修改aa的值;
    std::cout<<fun2();
    std::cout<<"  aa:"<<aa<<std::endl;//10  aa:7
    auto fun3 = [&]()->double{aa = 8.0;g_bb = 15;return aa+3;};//此时aa可以进行赋值操作如:aa=7;,其他在作用域范围的变量都可以以引用方式调用
    std::cout<<fun3();
    std::cout<<"  aa:"<<aa<<" g_bb:"<<g_bb<<std::endl;//11  aa:8
}
输出:

3
8  aa:5
10  aa:7
11  aa:8 g_bb:15

一般也是以这种方式来写[&],简单明了。
lambda表达式先说到这后面在讲std::for_each和std::transform时会有更多例子。

2.std::foreach

std::foreach是很经典的算法,但是由于需要用到函数对象,有时候还不如直接for循环方便(暂且不讨论for循环的新表达式写法,目前没多少个编译器支持),例如我有个vector,我要打印出来看看里面有什么内容,经常下意识的就直接成:

std ::vector <double>v ;
v . push_back ( 3);
v . push_back ( 1. 666);
v . push_back ( 4. 5);
v . push_back ( 6. 7);
for( std ::vector <double>::iterator i =v . begin (); i !=v . end (); ++i )
{
    std ::cout <<*i <<",";
}

std :: vector < double >:: iterator是多么的碍眼的。
还好,C++11把auto升了级,上面那个代码会变成

for( autoi =v . begin (); i !=v . end (); ++i )

用std::foreach(),上面这个就变成

std ::for_each ( v . begin (), v . end (),[ &]( double d ){ std ::cout <<d <<",";});

于是,以后凡是要遍历容器,且代码不太长,都可以使用std::foreach加lambda表达式方便实现

为了方便下面的演示,编写一个打印容器内容的函数printElement
template<typename Container>
void printElement(Container& v)
{
    std::cout<<"(";
    for(auto i = v.begin();i != v.end();++i)
    {
        std::cout<<*i;
        if(i != v.end() - 1)
            std::cout<<",";
    }
    std::cout<<")";
    std::cout<<std::endl;
}

可以输出序列的内容,如vector<double>内容为(1,2,3,4,5),输出:(1,2,3,4,5)

3.std::transform


当涉及到两个或三个容器的操作,就需要使用std::transform操作
transform有两个重载版本
template <class InputIterator, class OutputIterator, class UnaryOperation>
  OutputIterator transform (InputIterator first1, InputIterator last1,
                            OutputIterator result, UnaryOperation op);

原理如下图所示:



template <class InputIterator1, class InputIterator2,
          class OutputIterator, class BinaryOperation>;
OutputIterator transform (InputIterator1 first1, InputIterator1 last1,
                            InputIterator2 first2, OutputIterator result,
                            BinaryOperation binary_op);
原理如下图所示:



如需要求序列a的log,结果存入c

//求a的log存入c
    a.clear();b.clear();c.clear();
    for(auto i(1);i<10;++i){
        a.push_back(i);
    }

    std::transform(a.begin(),a.end(),std::back_inserter(c),[](double d)->double{return log(d);});
    std::cout<<"a:";
    printElement(a);//
    std::cout<<"c:";
    printElement(c);//(0,0.693147,1.09861,1.38629,1.60944,1.79176,1.94591,2.07944,2.19722)


如果对a序列求log并直接把结果存入a中的话有两种方法
方法1:使用(transform)
std::cout<<"a=log(a):"<<std::endl<<"a:";
    printElement(a);
    std::transform(a.begin(),a.end(),a.begin(),[](double d)->double{return log(d);});
    std::cout<<"after a:";
    printElement(a);//(0,0.693147,1.09861,1.38629,1.60944,1.79176,1.94591,2.07944,2.19722)
输出:

a=log(a):
a:(1,2,3,4,5,6,7,8,9)
after a:(0,0.693147,1.09861,1.38629,1.60944,1.79176,1.94591,2.07944,2.19722)

方法2:使用(for_each)

std::cout<<"a=log(a):"<<std::endl<<"a:";
    printElement(a);
    std::for_each(a.begin(),a.end(),[](double& d){d = log(d);});
    std::cout<<"after a:";
    printElement(a);//(0,0.693147,1.09861,1.38629,1.60944,1.79176,1.94591,2.07944,2.19722)

输出:

a=log(a):
a:(1,2,3,4,5,6,7,8,9)
after a:(0,0.693147,1.09861,1.38629,1.60944,1.79176,1.94591,2.07944,2.19722)
这里,for_each的操作函数以引用方式传入,因此可以实现对自身元素的修改。
从这里可以看出,std::transform是可以实现std::for_each的功能的,也就是说std::transform是std::for_each的扩展

如两个vector<double>V,W要进行某个运算,如计算U = sin(V) + W,结果存入U;
 
std ::vector <double>v , w , u ;
v . push_back ( 3);
v . push_back ( 1.666);
v . push_back ( 4.5);
v . push_back ( 6.7);
w . push_back ( 3);
w . push_back ( 1.666);
w . push_back ( 4.5);
w . push_back ( 6.7);
for( autoi =v . begin (), j =w . begin (), k =u . begin (); i !=v . end (), j !=w . end (), k !=u . end ();++i ,++j ,++k )
{
    *k =sin (*i )+*j ;
}

幸好还有auto,要不然,呵呵……
transform的表达形式如下
std ::transform ( v . begin (), v . end (), w . begin (), u . begin () ,[&]( double a , double b )->double{
return sin ( a )+b ;
} );

使用std::transform可以比较方便的实现序列容器的四则运算
stl提供了plus,multiplies,divides,modulus,negate等函数对象,方便实现加减乘除等运算,具体见: http://www.cplusplus.com/reference/functional/plus/
如实现两序列的加法运算,不必写lambda表达式,直接使用stl的std::plus即可,如:
实现vector<double> A,B,计算C = A+B
a.clear();
    b.clear();
    c.clear();
    for(int i(1);i<10;++i)
    {
       a.push_back(i);
       b.push_back(i*10);
    }
    std::cout<<"calc c=a+b:"<<std::endl<<"a:";
    printElement(a);
    std::cout<<"b:";
    printElement(b);
    std::transform(a.begin(),a.end(),b.begin(),std::back_inserter(c),std::plus<double>());
    std::cout<<"calc c=a+b -> c:";
    printElement(c);

输出:

calc c=a+b:
a:(1,2,3,4,5,6,7,8,9)
b:(10,20,30,40,50,60,70,80,90)
calc c=a+b -> c:(11,22,33,44,55,66,77,88,99)

代码:

#include <iostream>
#include <functional>
#include <algorithm>
#include <vector>
#include <iterator>
#include "math.h"
double g_bb = 11.2;
void foo1()
{
    auto f_add = [&](int a,int b)->int{return a+b;};
    std::cout<<f_add(1,2);//3
    std::cout<<std::endl;

    double aa = 5.0;
    auto fun = [aa]()->double{return aa+3;};//此时aa不能进行赋值操作如:aa=7;
    std::cout<<fun();
    std::cout<<"  aa:"<<aa<<std::endl;//8 aa:5

    auto fun2 = [&aa]()->double{aa = 7.0;return aa+3;};//此时aa以引用方式传入,可以进行赋值操作如:aa=7,同时修改aa的值;
    std::cout<<fun2();
    std::cout<<"  aa:"<<aa<<std::endl;//10  aa:7

    auto fun3 = [&]()->double{aa = 8.0;g_bb = 15;return aa+3;};//此时aa可以进行赋值操作如:aa=7;,其他在作用域范围的变量都可以以引用方式调用
    std::cout<<fun3();
    std::cout<<"  aa:"<<aa<<" g_bb:"<<g_bb<<std::endl;//11  aa:8
}

template<typename Container>
void printElement(Container& v)
{
    std::cout<<"(";
    for(auto i = v.begin();i != v.end();++i)
    {
        std::cout<<*i;
        if(i != v.end() - 1)
            std::cout<<",";
    }
    std::cout<<")";
    std::cout<<std::endl;
}

int main()
{
    foo1();

    std::vector<double> a,b,c;
    a.push_back(3);
    a.push_back(1.666);
    a.push_back(4.5);
    a.push_back(6.7);
    b.push_back(3);
    b.push_back(1.666);
    b.push_back(4.5);
    b.push_back(6.7);
    for ( std :: vector < double >:: iterator i = a . begin (); i != a . end ();++ i )
    {
        std::cout<<*i<<",";//3,1.666,4.5,6.7,
    }
    std::cout<<std::endl;
    std::for_each(a.begin(),a.end(),[&](double d){std::cout<<d<<",";});//3,1.666,4.5,6.7,
    std::cout<<std::endl;
    c.resize(a.size());
    for (auto i=a.begin(),j =b.begin(),k = c.begin(); i!=a.end (),j!=b.end(),k!=c.end();++i,++j,++k)
    {
        *k = sin(*i)+*j;
    }
    std::transform(a.begin(),a.end(),b.begin(),c.begin()
                   ,[&](double i,double j)->double{return sin(i)+j;});
    printElement(c);//(3.14112,2.66147,3.52247,7.10485)

    a.clear();b.clear();c.clear();
    for(auto i(1);i<10;++i){
        a.push_back(i);
    }
    //求a的log存入c
    std::cout<<"c=log(a):"<<std::endl<<"a:";
    printElement(a);
    std::transform(a.begin(),a.end(),std::back_inserter(c),[](double d)->double{return log(d);});
    std::cout<<"c:";
    printElement(c);//(0,0.693147,1.09861,1.38629,1.60944,1.79176,1.94591,2.07944,2.19722)

    b = a;
    std::cout<<"a=log(a):"<<std::endl<<"a:";
    printElement(a);
    std::transform(a.begin(),a.end(),a.begin(),[](double d)->double{return log(d);});
    std::cout<<"after a:";
    printElement(a);//(0,0.693147,1.09861,1.38629,1.60944,1.79176,1.94591,2.07944,2.19722)

    a = b;
    std::cout<<"a=log(a):"<<std::endl<<"a:";
    printElement(a);
    std::for_each(a.begin(),a.end(),[](double& d){d = log(d);});
    std::cout<<"after a:";
    printElement(a);//(0,0.693147,1.09861,1.38629,1.60944,1.79176,1.94591,2.07944,2.19722)

    a.clear();
    b.clear();
    c.clear();
    for(int i(1);i<10;++i)
    {
       a.push_back(i);
       b.push_back(i*10);
    }
    std::cout<<"calc c=a+b:"<<std::endl<<"a:";
    printElement(a);
    std::cout<<"b:";
    printElement(b);
    std::transform(a.begin(),a.end(),b.begin(),std::back_inserter(c),std::plus<double>());
    std::cout<<"calc c=a+b -> c:";
    printElement(c);

    return 0;
}

写上面这篇文章,主要是因为最近经常使用序列的四则运算,如vector a + vector b,或者进行一些稍微复杂的数学运算,自己封装了一些简化的用法,截取如下:

#define INPUT
#define OUTPUT
namespace Array {
///
/// \brief 加一个常数的函数对象,类似于std::plus<T>(),不过此函数对象是用于对一个常数进行加法运算
///
template <class T> struct plus_const : std::binary_function <T,T,T> {
    plus_const(const T& data){m_data = data;}
  T operator() (const T& x) const {return x+m_data;}
  T m_data;
};

///
/// \brief 序列加法运算,用于序列加上单一一个值
/// \param begin 序列迭代器的起始
/// \param end 序列迭代器的结尾
/// \param beAddData 需要进行加法运算的值
/// \note 此操作会直接修改原有序列值
///
template<typename T,typename IT>
void add(INPUT IT begin,INPUT IT end,T beAddData)
{
    std::transform(begin,end,begin,plus_const<T>(beAddData));
}
///
/// \brief 序列加法运算,两个等长序列相加
/// \param begin_addfont “加数”序列迭代器的起始
/// \param end_addfont “加数”序列迭代器的结尾
/// \param begin_addend “被加数”序列迭代器的起始
/// \param begin_res 用于存放结果的序列的起始地址
///
template<typename T,typename IT>
void add(INPUT IT begin_addfont,INPUT IT end_addfont,IT begin_addend,IT begin_res)
{
    std::transform(begin_addfont,end_addfont,begin_addend,begin_res
                   ,std::plus<T>());
}
///
/// \brief 序列减法运算,两个等长序列相减
/// \param begin_addfont “加数”序列迭代器的起始
/// \param end_addfont “加数”序列迭代器的结尾
/// \param begin_addend “被加数”序列迭代器的起始
/// \param begin_res 用于存放结果的序列的起始地址
///
template<typename T,typename IT>
void minus(INPUT IT begin_minusfont,INPUT IT end_minusfont,IT begin_minusend,IT begin_res)
{
    std::transform(begin_minusfont,end_minusfont,begin_minusend,begin_res
                   ,std::minus<T>());
}
///
/// \brief 序列减法运算,用于序列减去单一一个值
/// \param begin 序列迭代器的起始
/// \param end 序列迭代器的结尾
/// \param beAddData 需要进行减法运算的值
/// \note 此操作会直接修改原有序列值
///
template<typename T,typename IT>
void minus(INPUT IT begin,INPUT IT end,T beMinusData)
{
    std::transform(begin,end,begin,plus_const<T>(-beMinusData));
}
……
}




© 著作权归作者所有

共有 人打赏支持
尘中远
粉丝 1
博文 26
码字总数 47436
作品 0
朝阳
程序员
私信 提问
C++ 逐渐 Python 化

近几年C++有了很多变化。最新的两个版本C++11和C++14,引入了如此多的新特性,用 Bjarne Stroustrup的话说就是“感觉就像一个新语言一样。” 真的。现代c++形成了一个全新的编程风格,我不能...

fsxchen
2014/12/09
27.4K
57
你们以为我在学C++?其实我在学 Python

我在用 C++ 来学习 Python. 不信?来跟着我学? 字面量 Python 早在 2.6 版本中就支持将二进制作为字面量了[1], 最近 C++14 逐步成熟,刚刚支持这么干[2]: static const int primes = 0b1010...

铁扇公主1
2017/04/28
91
0
有效使用 Lambda 表达式和 std::function [翻译]

翻译自 Dr. Dobb's 的一篇关于 C++11 的 lambda 表达式, 闭包 和 std::function 的文章 原文: Efficient Use of Lambda Expressions and std::function 作者: Cassio Neri 译文: 有效使用 La......

晨曦之光
2012/05/23
3.5K
0
C++11 lambda 表达式

C++11 lambda表达式 在C++ 03标准中,并没有lambda这个概念,对于C++来说,boost库提供了lambda的接口。在C++11中,引进了lambda表达式,这也可能是面向对象语言中,比较晚引进这个概念的语...

刘大神
2015/05/25
664
2
C++11 & C++14 & C++17新特性

原文:https://www.cnblogs.com/guxuanqing/p/6707824.html C++11:C++11包括大量的新特性:包括lambda表达式,类型推导关键字auto、decltype,和模板的大量改进。 新的关键字 auto C++11中引...

炎林2018
2018/12/06
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Git代码防丢指南

我们在日常使用Git的过程中经常会发生一些意外情况,如果处理不当,则可能会出现代码丢失的假象。本文将针对IDEA&Git日常开发中的一些场景,为你层层拨开迷雾,解析常见的错误及其发生原因,...

joymufeng
7分钟前
0
0
传统IDC部署网站(三)

16. chown命令 chown 用来更改一个文件或者目录的所有者护着所属组 -R 级联更改一个目录下所有的目录和文件 chown user1:users 1.txtchown user1.users 1.tx useradd 添加用户的命令 user...

miko0089
17分钟前
0
0
来玩一下Java设计模式之命令模式

wiki上的描述 Encapsulate a request as an object, thereby allowing for the parameterization of clients with different requests, and the queuing or logging of requests. It also al......

小刀爱编程
18分钟前
0
0
Optional类的简单了解

import java.util.Optional;/** * @author hanliwei */public class OptionalTest { /** * Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返...

wind2012
27分钟前
0
0
如何写出好的单元测试?

大家都知道,开发软件的时候为代码编写单元测试是很好的。但实际上,光有测试还不够,还要编写好的测试,这同样重要。 要做到这一点,考虑遵循一些固执的原则,对测试代码给予一些关爱: 1....

程序猿拿Q
35分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部