目录

lambda 表达式

定义 lambda 表达式

捕获子句

按值捕获

按引用捕获

捕获特定的变量

捕获this指针

结合 lambda 使用 STL 算法


lambda 表达式

lambda 表达式提供了一种便捷、简洁的语法来快速定义回调函数或函数对象。而且,不只语法简洁,lambda 表达式还允许在使用回调的地方定义回调的逻辑。这通常比在某个类定义的函数调用运算符的某个地方定义这种逻辑要好得多。因此,使用 lambda 表达式一般能够得到极具表达力且可读性仍然很好的代码。看下面这个例子:

bool compare(int a,int b){return a>b;
}
使用函数
sort(asd.begin(), asd.end(), compare);使用lambda表达式
sort(asd.begin(), asd.end(), [](int &a, int &b)->bool {return a > b;});

lambda 表达式与函数定义有许多相似的地方。最基本的 lambda 表达式提供了一种定义没有名称的函数(即匿名函数)的方法。在应用中会发现 lambda 表达式与普通函数不同,因为 lambda 表达式可以访问自己定义的作用域内存储的变量。lambda 表达式的计算结果是一个函数对象。该函数对象的正式名称是 lambda 闭包,但是有很多人也称之为 lambda 函数。( 闭包就是能够读取其他函数内部变量的函数 )

定义 lambda 表达式

一个基本的 lambda 表达式:

[] (int x, int y) { return x < y; }

可以看出,lambda 表达式的定义看上去很像函数的定义。主要区别在于,lambda 表达式不指定返回类型和函数名称,并且始终以方括号开头。表达式开始的 " [ ] " 称为 lambda 引导,它们标记了 lambda 表达式的开头。lambda 引导的内容并不总是为空的。lambda 引导后跟的 " ( ) " 是 lambda 参数列表。对于没有参数的 lambda 表达式,可以省略空的参数列表 ()。即可以将形式为 [](){......},的 lambda 表达式进一步缩减为 []{......}。空的 lambda 引导不能省略,因为它标记 lambda 表达式的开始。

有的人可能会觉得 lambda 表达式没有函数名称还可以理解,但是没有返回类型是不是就有点过分了(玩呢?)。lambda 表达式可以包含任意数量的语句,返回类型默认为返回值的类型。如果没有返回值,返回类型为 void。不过返回值类型也是可以根据需求设定的,例如下面这种情况:

[](double x) -> int { int y = x; return x-y;}

捕获子句

lambda 引导 [] 不一定是空的,它可以包含捕获子句,以指定封闭作用域中的变量如何在 lambda 表达式中访问。如果方括号为空,则 lambda 表达式体只能使用 lambda 表达式中局部定义的实参和变量。没有捕获子句的 lambda 表达式称为无状态的 lambda 表达式,因为它不能访问其封闭作用域中的任何内容。默认捕获子句有两种:= 和 &。捕获子句只能包含一种默认捕获子句,不能同时包含两者。

按值捕获

如果在方括号中包含 =,lambda 表达式体就可以按值访问封闭作用域中的所有自动变量,即这些变量的值可以在 lambda 表达式中使用,但不能修改存储在原始变量中的值。当你按值捕获变量时,又试图改变它,编译器就会报错:

int a = 1,b = 2,c = 3;
[ =,&c ](){ a++; b++; c++; cout << a << " " << b << " " << c <<endl; } ();
cout << a << " " << b << " " << c <<endl;

= 捕获子句允许在 lambda 表达式体中按值访问 lambda 表达式定义所在作用域的所有变量。在上面的例子中,在原则上,lambda 表达式体能够访问 main() 的 3 个局部变量:a、b 和 c。按值捕获局部变量的效果与按值传递实参大不相同。

不同点在于:对于 lambda 表达式体内用到的封闭作用域内的每个局部变量,闭包对象都有一个成员。称为 lambda 捕获了这些变量。至少在概念上,所生成的成员变量的名称与捕获到的变量的名称相同。这样一来,lambda 表达式体看起来访问的是封闭作用域中的变量,但实际上访问的是 lambda 闭包内存储的对应的成员变量。

按引用捕获

如果在方括号中放置 &,封闭作用域中的所有变量就可以按引用访问,所以它们的值可以由 lambda 表达式体中的代码修改。例如:

int a = 1,b = 2,c = 3;
//按引用捕获
[ & ](){ a++; b++; c++; } ();
cout << a << " " << b << " " << c <<endl;

外层作用域内的所有变量都可按引用访问,所以 lambda 可以使用和修改它们的值。虽然 & 捕获子句是合法的,但是在外层作用域按引用捕获所有变量并不是一种好方法,因为这将使得变量有可能被无意修改。类似地,使用 = 默认捕获子句则可能引入高开销的复制操作。因此,更安全的做法是显式指定如何捕获自己需要的每个变量。

捕获特定的变量

通过在捕获子句中逐个列举,可以指定想要访问的封闭作用域中的特定变量。对于每个变量,可以选择按值还是按引用捕获。在变量名前面加上 &,就可以按引用捕获变量。例如:

auto counter { [ &count] (int x, int y) {++count; return x < y;} };

count 是封闭作用域中可以在 lambda 表达式体中访问的唯一变量,&count 规范使之可以按引用访问。没有 &,外层作用域中的 count 变量就按值访问。当你想要按值捕获特定变量时,不能在变量名前面加 = 作为前缀。例如,捕获子句 [ = numbers ] 是无效的,正确的语法是 [ numbers ]。

在捕获子句中放置多个变量时,就用逗号分开它们。可以自由混合按值捕获的变量和按引用捕获的变量。还可以在捕获子句中,同时包含默认捕获子句和捕获的特定变量名称。例如:捕获子句 [ =, &counter ] 允许按引用访问 counter,按值访问封闭作用域中的其他变量。类似的,捕获子句 [ &, numbers ] 的意思是,按值捕获 numbers,按引用捕获其他变量。如果指定默认捕获子句( = 或 &),则它必须是捕获列表中的第一项。

注意:如果使用 = 默认捕获子句,则不能再按值捕获任何特定变量;类似地,如果使用 &,则不能再按引用捕获特定变量。例如:[ =, = a ] 或 [ &, & b ]。

捕获this指针

当我们在类中的成员函数中使用 lambda 表达式时,这个情况和之前的不一样,问题在于只有局部变量和函数实参才能按值或引用捕获,而类的成员变量不能按值或按引用捕获。以下是在类的成员函数中使用 lambda 的情况:

可以通过对捕获子句添加关键字 this ,让 lambda 表达式访问类的成员。通过捕获 this 指针,实际上就使得 lambda 表达式能够访问包含它的成员函数所能访问的所有成员,也就是说,尽管 lambda 闭包不属于类,但其函数调用运算符仍然能够访问类的 protected 和 private 数据成员。lambda 表达式还能够访问所有成员函数,无论它们被声明为 public、protected 还是 private。

class Asd
{private:int a, b;public:Asd(){a = 1;b = 2; }int geta(){return a;}int getb(){return b;}void print(){auto asd = [this](){ return getb() - geta();} ;cout << "getb() - geta():" << asd() <<endl;}void print1(){auto asd = [=](){ return b - a;} ;cout << "b-a:" << asd() <<endl;}
};

注意:= 默认捕获子句已经暗示着(按值)捕获this指针。因此这条语句也是合法的:

auto asd = [=](){ return b - a;} ;

但不允许将默认捕获子句 = 与 this 结合使用 (至少在 C++17 中不允许,C++20 中可能会有变化)。因此,编译器将把 [=, this] 这种形式的捕获子句标记为错误。不过,允许使用[&, this],因为 & 并不暗示着捕获 this。

捕获子句小总结:

[]        不捕获任何变量
[=]       用值的方式捕获所有变量
[&]       以引用方式捕获所有变量
[asd]     以值方式捕获asd; 不捕获其它变量
[=,&asd]  以引用捕获asd, 但其余变量都靠值捕获
[&, asd]  以值捕获asd, 但其余变量都靠引用捕获
[this]    捕获所在类的this指针

完整代码:

#include <iostream>
using namespace std;class Asd
{private:int a, b;public:Asd(){a = 1;b = 2; }int geta(){return a;}int getb(){return b;}void print(){auto asd = [this](){ return getb() - geta();} ;cout << "getb() - geta():" << asd() <<endl;}void print1(){auto asd = [=](){ return b - a;} ;cout << "b-a:" << asd() <<endl;}
};int main()
{//不捕获任何变量[] { cout << "Study lambda!" <<endl; } ();auto print = [] { cout << "Study lambda!" <<endl; };   print();//根据需求确定返回值auto asd = [](double x) -> int { int y = x; return x-y;};cout << asd(3.6) <<endl;int a = 1,b = 2,c = 3;//按值捕获[ = ](){ cout << a << " " << b << " " << c <<endl; } ();//按引用捕获[ & ](){ a++; b++; c++; } ();cout << a << " " << b << " " << c <<endl;//捕获特定的变量[ =,&c ](){ c++; cout << a << " " << b << " " << c <<endl; } ();cout << a << " " << b << " " << c <<endl;[ &,a ](){ b++; c++; cout << a << " " << b << " " << c <<endl; } ();cout << a << " " << b << " " << c <<endl;//捕获this指针Asd asd1, asd2;asd1.print();asd2.print1();return 0;
}

结合 lambda 使用 STL 算法

接下来将从 距离 和 简洁 两个方面探讨使用 lambda 的优势。

很多人认为,让定义位于使用的地方附近很有用。这样,在阅读源代码时,就无需去找该函数功能的具体代码。例如,调用 count_if() 的第三个参数时,不需要向前寻找具体实现。另外,如果需要修改代码,涉及的内容都在附近。从这一角度出发,lambda 是理想的选择,因为其定义和使用是在同一个地方进行的。而函数可能存在一种比较糟糕的情况,即其函数内部使用了其他函数,而这些函数可能在不同的地方,这在阅读源码这一点上是非常费时、费力的。

从简洁的角度看,函数和 lambda 的简洁程度相当,一个例外是,需要使用同一个 lambda 两次:

count1 = count_if ( a.begin(), a.end(), [] (int x) { return x % 3 == 0; });
count2 = count_if ( b.begin(), b.end(), [] (int x) { return x % 3 == 0; });

但并不一定要编写 lambda 两次,而是给 lambda 指定一个名称,并使用该名称两次:

auto mod3 = [] (int x) { return x % 3 == 0; }
count1 = count_if ( a.begin(), a.end(), mod3 );
count2 = count_if ( b.begin(), b.end(), mod3 );

甚至可以像使用常规函数那样使用有名称的 lambda:

bool result = mod3(z);

然而,不同于常规函数,可在函数内部定义有名称的 lambda 。mod3 的实际类型随实现而异,它取决于编译器使用什么类型来跟踪 lambda 。

代码:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;bool compare(int a, int b)
{return a > b;
}int main()
{   vector<int> asd;for (int i = 0; i < 10; ++i)asd.push_back(i);sort(asd.begin(), asd.end(), [](int &a, int &b)->bool {return a > b;});sort(asd.begin(), asd.end(), compare);for (int i = 0; i < asd.size(); ++i)cout << asd[i] << endl;return 0;
}

代码:


#include <iostream>
#include <numeric>
#include <vector>void print_container(const std::vector<char>& c)
{for (auto x : c) {std::cout << x << ' ';}std::cout << '\n';
}int main()
{std::vector<char> cnt(10);std::iota(cnt.begin(), cnt.end(), '0');std::cout << "Init:\n";print_container(cnt);std::erase(cnt, '3');std::cout << "Erase \'3\':\n";print_container(cnt);auto erased = std::erase_if(cnt, [](char x) { return (x - '0') % 2 == 0; });std::cout << "Erase all even numbers:\n";print_container(cnt);std::cout << "In all " << erased << " even numbers were erased.\n";
}

快用一用 lambda 表达式吧,让你的代码更简洁、更漂亮!相关推荐

  1. 【Lambda表达式】让你的代码逼范十足~~

    [关于Lambda表达式] Lambda表达式是 JDK8的新特性,可以取代大部分的匿名内部类,写出更优雅简洁的Java代码,我在日常撸代码的时候常用的地方是集合的遍历和其他集合操作中,极大的优化了代 ...

  2. Lambda表达式的运用(详尽版)

    Lambda表达式是Java JDK8发布的最新特性,它极大地简化了定义匿名内部类时要写的代码,使代码变得更加简洁紧凑. 我们知道定义匿名内部类时要紧跟创建对象,接着要实现接口或重写父类中的抽象方法, ...

  3. 代码重构之没有理由拒绝Lambda表达式

    代码重构之没有理由拒绝Lambda表达式 Lambda表示是在C# 3.0推出的新特性,而匿名方法是C# 2.0推出的新特性,这一切都是为了让代码变得更简洁,更容易理解. 最近开始做一些Silverl ...

  4. 三、jdk1.8新特新Lambda表达式方法引用

    前言 在之前我们接触了JDK1.8引入的新特新lambda表达式没在某种程度上,它可以简化我们的代码,帮助我们快速的编写代码,但在这其中我们之前的编写方式并不是lambda表达式最简洁的方式,而在头屑 ...

  5. 深入理解Java Lambda表达式,匿名函数,闭包

    前言 对于Lambda表达式一直是知其然不知其所以然,为了搞清楚什么是Lambda表达式,以及Lambda表达式的用法和作用,本文应运而生当做学习笔记分享出来,欢迎指正交流. 什么是Lambda 让我 ...

  6. Java Lambda表达式入门

    本文转自:http://blog.csdn.net/renfufei... 转载请注明出处 原文链接: Start Using Java Lambda Expressions 下载示例程序 Examp ...

  7. java拉姆达表达式事例,Java Lambda表达式详解和实例

    简介 Lambda表达式是Java SE 8中一个重要的新特性.lambda表达式允许你通过表达式来代替功能接口. lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体( ...

  8. java 8 Lambda 表达式(副作用)

    [1]转自: https://www.cnblogs.com/linlinismine/p/9283532.html 早在2014年oracle发布了jdk 8,在里面增加了lambda模块.于是ja ...

  9. Lambda表达式的前世今生

    Lambda 表达式 早在 C# 1.0 时,C#中就引入了委托(delegate)类型的概念.通过使用这个类型,我们可以将函数作为参数进行传递.在某种意义上,委托可理解为一种托管的强类型的函数指针. ...

最新文章

  1. EXCEL公式、函数、图表应用技巧800问
  2. X.509证书的介绍
  3. 魏代汉,晋代魏,背后是士族与寒族的博弈,也是士族门阀的形成时期
  4. MySQL中索引的分类和基本操作
  5. 虚拟化概念和KVM简述
  6. CVE-2015-1635(MS15-034)-HTTP.sys远程代码执行复现
  7. 4个优化MongoDB的技巧
  8. java httpclient发送json 请求 ,go服务端接收
  9. r语言实现sem_统计基础:【18】使用Excel和R语言来实现抽样
  10. 安卓rtmp推流app_同城直播电商APP小程序平台开发
  11. 一种非常简单的静态网页生成方法介绍
  12. java安卓6.0闪退_Android开发activity跳转闪退
  13. Python学习笔记(语句)
  14. [源码]vb6鼠标连点器
  15. 检测数据库连接泄漏的最佳方法
  16. 计算机电脑用英语单词怎么读,计算器 英语
  17. 《Multiobjective Evolutionary Algorithms:A Comparative Case Study and the Strength Pareto Approach》
  18. 鸿蒙系统多屏协同,华为EMUI 11支持畅连、多屏协同等功能,与鸿蒙设备实现交互...
  19. HTML+CSS+JS网页设计期末课程大作业 web前端开发技术 web课程设计 html网页规划与设计
  20. 振弦式渗压计渗流孔隙水压立计应用地基内部渗透水压力

热门文章

  1. Win32多线程编程(5) — 线程局部存储
  2. IOCP 浅析与实例
  3. WebRTC 的版本号与代码分支
  4. Spring Boot 静态资源处理,原来如此!
  5. 动态规划之KMP字符匹配算法
  6. 音视频技术开发周刊 | 198
  7. 进入全真互联网——音视频通信的技术变革
  8. 【免费活动】解析腾讯云音视频通信三大核心网络技术实战与创新
  9. 音视频技术开发周刊 | 162
  10. 【多媒体开源技术栈】