1、  定义

  lambda表达式是C++11非常重要也是很常用的特性之一,来源于函数式编程的概念,也是现代编程语言的一个特点。它有如下特点:

  • 声明式编程风格:就地匿名定义目标函数或者函数,不需要额外写一个命名函数或者函数对象,以更直接的方式写程序。
  • 简洁:不需要额外再写一个函数或者函数对象,避免了代码膨胀和功能分散。
  • 在需要的时间和地点实现功能闭包,使程序更加灵活。

  lambda表达式定义一个匿名函数,并且可以捕获一定范围内的变量,其定义如下:

  [captrue] (params) opt -> ret {body};

  其中,capture是捕获列表;params是参数列表;opt是函数选项;ret是返回值类型;body是函数体。

  我们来做一个简单的例子,定义一个简单的封包,用来将输入的结果+1并返回。

auto f = [](int a) -> int {return a + ; };
std::cout << f() << std::endl;

  lambda表达式的返回值通过返回值后置语法来定义,所以很多时候可以省略返回值类型,编译器根据return语句自动推导返回值类型。

auto f = [] (int a) {return a + ;};

  但是初始化列表不能作为返回值的自动推导,需要显示给出具体的返回值类型。

auto f1 = [] (int a) {return a + ;};  //ok,return type is int
auto f2 = [] () {return {, }};    //error:无法推导出返回值类型

  lambda表达式在没有参数列表的时候,参数列表可以省略。

auto f1 = [] () {return ;};
auto f2 = [] {return ;} //省略空参数表

2、  捕捉

  lambda表达式可以通过捕获列表捕获一定范围内的变量:

  • []不捕获任何变量;
  • [&]捕获外部作用域所有变量,并作为引用在函数体使用(按引用捕获);
  • [=]捕获外部作用域作用变量,并作为副本在函数体使用(按值捕获);
  • [=,&foo]按值捕获外部作用域所有变量,并按引用捕获foo变量;
  • [bar]按值捕获bar变量,同时不捕获其他变量;
  • [this]捕获当前类中的this指针,让lambda拥有和当前类成员函数同样的访问权限,如果已经使用了&或者=,就默认添加此选项。捕获this的目的是可以在lambda中使用当前类的成员函数和成员变量。
class A
{
public:
int mi = ; void func(int x, int y)
{
auto x1 = []{return mi;}; //error,没有捕获外部变量
auto x2 = [=] {return mi + x + y;}; //ok,按值捕获所有外部变量
auto x3 = [&] {return mi + x + y;}; //ok,按引用捕获所有外部变量
auto x4 = [this] {return mi;}; //ok,捕获this指针
auto x5 = [this] {return mi + x + y;}; //error,没有捕获x,y
auto x6 = [this,x,y] {return mi + x + y;}; //ok,捕获this,x,y
auto x7 = [this] {return mi++;}; //ok,捕获this指针,并修改成员的值
}
}; int a = , b = ;
auto f1 = [] {return a;} ; //error,没有捕获外部变量
auto f2 = [&] {return a++;}; //ok,按引用捕获所有外部变量,并对a执行自加运算
auto f3 = [=] {return a;}; //ok,按值捕获所有外部变量,并返回a
auto f4 = [=] {return a++;}; //error,按值引用不能改变值
auto f5 = [a] {return a + b;}; //error,没有捕获b
auto f6 = [a, &b] {return a + (b++);}; //ok,捕获a和b的值,并对b做自加运算
auto f7 = [=, &b] {return a + (b++);}; //ok,捕获所有外部变量和b的引用,并对b做自加运算

  从例子中可以看到,lambda表达式的捕获列表精细的控制表达式能够访问的外部变量,以及如何访问这些变量。需要注意的是,默认状态下的lambda表达式无法修改通过复制方式捕获的外部变量,如果希望修改这些变量,需要用引用的方式进行修改。但是按值引用的话,如果延迟调用,那么在调用该lambda表达式的时候,其捕获的变量值还是在lambda定义的时候的值。

int a = ;
auto f = [=] {return a;}; //按值捕获外部变量
a += ; //修改值
std::cout << f() << std::endl; //输出0

  这个例子中,lambda表达式按值捕获了所有外部变量,在捕获的一瞬间a的值就已经被赋值在其中,之后a值修改并不会改变lambda表达中存的a的值,因此最终结果输出还是0。如果希望lambda表达式在调用的时候能即时访问外部变量,应当使用引用的方式捕获。

  如果希望去修改按值捕获的外部变量,需要显示指明lambda表达式为mutable,但是被mutable修饰的lambda表达式有一个特点,就是无论有没有参数都要写明参数列表。

int a = ;
auto f1 = [=] {return a++;}; //error,不能修改按值捕获的变量
auto f2 = [=] mutable {return a++; }; //ok,mutable

  lambda表达式其实是一个带有operator()的类,即仿函数,因此我们可以使用std::bind和std::function来存储和操作lambda表达式:

std::function<int(int)> f1 = [] (int a){ return a;};
std::function<int(void)> f2 = std::bind([](int a) {return a}, );

  对于没有捕获任何变量的lambda表达式,还可以被转换成一个普通的函数指针:

using func_t = int(*)(int);
func_t f = [](int a){return a;};
f();

  lambda表达式可以说是定义仿函数闭包的语法糖,它捕获的任何外部变量都会转换为闭包类型的成员变量。而使用成员变量的类的operator(),如果能直接转换为普通的函数指针,那lambda表达式本身的this指针会丢失,没有捕获任何外部变量的lambda表达式则不存在这个问题,所以按值捕获的外部变量无法修改。因为lambda表达式中的operator()默认是const的,一个const成员函数无法修改成员变量的值,而mutable则是取消operator()的const。

  所以,没有捕获变量的lambda表达式可以直接转换为函数指针,而捕获变量的lambda表达式则不能转换为函数指针。

typedef void(*Ptr)(int *);
Ptr p1 = [](int *p) {delete p;}; //ok,没有捕获的lambda表达式可以转换为函数指针
Ptr p2 = [&](int *p){delete p;}; //error,有捕获的lambda表达式不能直接转换为函数指针,不能通过编译

3、  简洁代码

  就地定义匿名函数,不再需要定义函数对象,大大简化了标准库的调用。比如我们使用for_each将vector中的偶数打印出来。

class CountEven
{
private:
int &micount; public:
CountEven(int &count) : micount(count) {} void operator()(int val)
{
if(!(val & ))
{
++micount;
}
}
}; std::vector<int> v = {, , , , , };
int count = ;
for_each(v.begin(), v.end(), CountEven(count));
std::cout << "The number:" << count << std::endl;

  这样写比较繁琐,如果用lambda表达式,使用闭包概念来替换这里的仿函数。

std::vector<int> v = {, , , , , };
int count = ; for_each(v.begin(), v.end(), [&count] (int val)
{
if(!(val & ))
{
++count;
}
});

  在之前的例子中,使用std::bind组合多个函数,实现了计算集合中大于5小于10的元素个数。

using std::placeholders::_1;
auto f = std::bind(std::logical_and<bool>(),
std::bind(std::greater<int>(), std::placeholders::_1, ),
std::bind(std::less_equal<int>(), std::placeholders::_1, ));
int count = std::count_if(coll.begin(), coll.end(), f);

  通过lambda表达式可以轻松的实现类似的功能:

  //查找大于5小于10的元素个数

int count  = std::count_if(coll.begin(), coll.end(), [](int x) {return x >  && x < ;})

  lambda表达式比std::bind更加灵活和简洁,如果简单的逻辑处理,用lambda表达式来代替function,提升开发效率,效果会更好。

C11简洁之道:lambda表达式的更多相关文章

  1. Java8之——简洁优雅的Lambda表达式

    Java8发布之后,Lambda表达式,Stream等等之类的字眼边慢慢出现在我们字眼.就像是Java7出现了之后,大家看到了“钻石语法”,看到了try-with-resource等等.面对这些新东西 ...

  2. C11简洁之道:类型推导

    1.  概述 C++11里面引入了auto和decltype关键字来实现类型推导,通过这两个关键字不仅能方便的获取复杂的类型,还能简化书写,提高编码效率. 2.  auto 2.1 auto关键字的新 ...

  3. C11简洁之道:循环的改善

    1.  for循环的新用法 在C++98/03中,通过for循环对一个容器进行遍历,一般有两种方法,常规的for循环,或者使用<algorithm>中的for_each方法. for循环遍 ...

  4. C11简洁之道:tupe元祖

    tuple元组是一个固定大小不同类型的值的集合,是泛化的std::pair.我们也可以把它当作一个通用的结构体来使用,不需要创建结构体有获取结构体特征,在某些情况可以取代结构体,使程序更简洁.直观. ...

  5. C11简洁之道:初始化改进

    1.  C++98/03初始化 我们先来总结一下C++98/03的各种不同的初始化情况: //普通数组 ] = {, , }; //POD(plain old data) struct A { int ...

  6. C11简洁之道:模板改进

    1.  右尖括号 我们在C++98/03中使用泛型编程的时候,经常遇到“>>”被当作右移操作符,而不是模板参数的结尾.假如我们有如下代码: template <typename T& ...

  7. C11简洁之道:函数绑定

    1.  可调用对象 在C++中,有“可调用对象”这么个概念,那么什么是调用对象呢?有哪些情况?我们来看看: 函数指针: 具有operator()成员函数的类对象(仿函数): 可以被转换为函数指针的类对 ...

  8. Java 8 Lambda表达式,让你的代码更简洁

    Lambda表达式是Java 8一个非常重要的新特性.它像方法一样,利用很简单的语法来定义参数列表和方法体.目前Lambda表达式已经成为高级编程语言的标配,像Python,Swift等都已经支持La ...

  9. JDK新特性-Lambda表达式的神操作

    一.Lambda表达式的介绍 Lambda表达式是 Java8 中最重要的新功能之一.使用 Lambda 表达 式可以替代只有一个抽象函数的接口实现,告别匿名内部类,代码看 起来更简洁易懂.Lambd ...

随机推荐

  1. JS判断备忘

    快速引入jquery并显示重点内容 (function(d,j,s,t){t=d.body.appendChild(d.createElement("script"));t.onl ...

  2. Word Ladder Problem (DFS + BFS)

    Given two words (beginWord and endWord), and a dictionary's word list, find the length of shortest t ...

  3. iOS开发UIColor,CGColor,CIColor三者的区别和联系

    最近看了看CoreGraphics的东西,看到关于CGColor的东西,于是就想着顺便看看UIColor,CIColor,弄清楚它们之间的区别和联系.下面我们分别看看它们三个的概念: 一.UIColo ...

  4. 在线api查询网站

    1.包含各种常用的语言 http://tool.oschina.net/apidocs

  5. MySQL必备命令

    来源:http://www.cnblogs.com/liushuijinger/p/3381775.html 今天跟大家分享一下MySQL从连接到具体操作的一系列常用命令.可能有的人觉得现在有很多可视 ...

  6. Kafka Strem

    Overview Concepts Topology Time States Window Hopping time windows Tumbling time windows Sliding win ...

  7. 在上传文件时候 request.setCharset对文件名有效 对普通文本域无效 需要通过手动转换编码方式编码

    在上传文件时候 request.setCharset对文件名有效 对普通文本域无效 需要通过手动转换编码方式编码

  8. hash 默认使用equal进行元素比较 防止元素重复

    hash 默认使用equal进行元素比较 防止元素重复

  9. BZOJ 2337 XOR和路径(概率DP)

    求点1到点n经过的路径权值异或和的期望. 考虑按位计算,对于每一位来说,令dp[i]表示从i到n的异或和期望值. 那么dp[i]=sum(dp[j]+1-dp[k]).如果w(i,j)这一位为0,如果 ...

  10. bzoj2257[POI2011]Programming Contest

    首先可以费用流建图,左边一堆点表示人,右边一堆点表示题,源点向每个人连floor(t/r)条边,费用依次为r,2r,3r….然后写了一个卡不过去,动态加边也卡不过去,然后我想:这里一定有一些不为人知的 ...