文档章节

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

尘中远
 尘中远
发布于 2016/05/12 23:54
字数 2655
阅读 9
收藏 0
点赞 2
评论 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++ 来学习 Python. 不信?来跟着我学? 字面量 Python 早在 2.6 版本中就支持将二进制作为字面量了[1], 最近 C++14 逐步成熟,刚刚支持这么干[2]: static const int primes = 0b1010...

铁扇公主1
2017/04/28
91
0
C++11 lambda 表达式

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

刘大神
2015/05/25
664
2
有效使用 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.1K
0
【C++11】lambda 表达式解析

C++11 新增了很多特性,lambda 表达式是其中之一,如果你想了解的 C++11 完整特性,建议去这里,这里,这里,还有这里看看。本文作为 5 月的最后一篇博客,将介绍 C++11 的 lambda 表达式。 ...

筱骏
2017/03/17
0
0
【C++11】30分钟了解C++11新特性

---------- 点击进入我的新博客 什么是C++11 C++11是曾经被叫做C++0x,是对目前C++语言的扩展和修正,C++11不仅包含核心语言的新机能,而且扩展了C++的标准程序库(STL),并入了大部分的C++...

王选易
2013/12/10
0
42
C++ 11和C++98相比有哪些新特性

此文是如下博文的翻译: https://herbsutter.com/elements-of-modern-c-style/ C++11标准提供了许多有用的新特性。这篇文章特别针对使C++11和C++98相比看上去像一门新语言的特性,因为: C+...

harlanc
2017/03/05
0
0
Qt5 中对 C++11 一些新特性的封装

C++11 是现在的 C++ 标准的名称,C++11 为 C++ 语言带来很多新特性。 而 Qt 4.8 是 Qt 首个在其 API 中开始使用一些新的 C++11 特性的版本,我之前写过一篇博文:C++11 in Qt 4.8 描述了这个...

红薯
2012/06/12
15K
8
C++11 新特性:Lambda 表达式

参考文章:https://blogs.oracle.com/pcarlini/entry/c1xtidbitslambdaexpressions 或许,Lambda 表达式算得上是 C++ 11 新增特性中最激动人心的一个。这个全新的特性听起来很深奥,但却是很...

雅各宾
2014/07/17
0
0
三驾马车并驾齐驱 C++能否重焕青春

【IT168 技术】Java十几年来的迅猛发展,似乎在印证一个亘古不变的真理——与时俱进。C语言几年来也在不断发力,作为变种,Object C在Apple应用领域如火如荼,而作为编程语言前三甲的C++,似...

it168网站
2011/12/02
0
0
C++primer标准库(2)

作为大四应届生o( ̄︶ ̄)o 最近为了后续的面试工作地点看C++primer再次深入学习C++写里一点笔记: 如下: 1. io库 • **istream(输入流)类提供输入操作。 • ostream(输出流)类提供输出操作。 ...

微小的鱼233
03/02
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Hbase增删查改工具类

package cn.hljmobile.tagcloud.service.data.repository;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util......

gulf
4分钟前
0
0
详解机器学习中的梯度消失、爆炸原因及其解决方法

前言 本文主要深入介绍深度学习中的梯度消失和梯度爆炸的问题以及解决方案。本文分为三部分,第一部分主要直观的介绍深度学习中为什么使用梯度更新,第二部分主要介绍深度学习中梯度消失及爆...

tantexian
5分钟前
0
0
JavaMail 发送邮件

参考 https://www.cnblogs.com/xdp-gacl/p/4216311.html 发送html格式邮件 package com.example.stumgr;import java.util.Properties;import javax.mail.Message;import javax.mail......

阿豪boy
7分钟前
0
0
Mongodb安装教程

MongoDB是一个基于分布式文件存储的数据库,是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似json的bso...

木筏笔歆
8分钟前
0
0
Hadoop之YARN命令

概述 YARN命令是调用bin/yarn脚本文件,如果运行yarn脚本没有带任何参数,则会打印yarn所有命令的描述。 使用: yarn [--config confdir] COMMAND [--loglevel loglevel] [GENERIC_OPTIONS] [...

舒运
9分钟前
0
0
个推数据统计产品(个数)iOS集成实践

最近业务方给我们部门提了新的需求,希望能一站式统计APP的几项重要数据。这次我们尝试使用的是个推(之前专门做消息推送的)旗下新推出的产品“个数·应用统计”,根据官方的说法,个推的数...

个推
10分钟前
0
0
Git 修改提交的用户名和邮箱名字

在通过git提交代码时,发现提交的用户名是自己mac的账户名,想要修改为其他名字和邮箱。 首先可以通过以下命令查看当前配置下的信息,包括用户名和邮箱: > git config --list 针对单项目的相...

edwardGe
13分钟前
0
0
Object.defineProperty()

Object.defineProperty(obj, props)方法直接在一个对象上定义新的属性或修改现有属性,并返回该对象。 obj 在其上定义或修改属性的对象 props 要定义其可枚举属性或修改的属性描述符的对象 ...

litCabbage
14分钟前
0
0
JEESZ分布式框架--单点登录集成方案(三)

多项目集成单点登录配置 当sso验证完成之后,客户端系统需要接收sso系统返回的结果时,需要定义一个过滤器获取返回结果,然后针对返回结果做相关处理.如果不需要做处理时,此处Filter也可以不...

明理萝
15分钟前
0
1
plisteditor 查看ipa包名及其它信息

1.下载ipa安装包 2.用rar等工具打开 3.将iTunesMetadata.plist文件解压出来 4.用plist Editor 工具打开 或http://www.atool.org/plist_reader.php在线反编译工具 5.在其中中找到softwareVer...

xiaogg
15分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部