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

以前,在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));
}
……
}




郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。