文章较长,坚持看完,相信对你一定会有所收获!

Lambda我们可以将其理解为一个未命名的内联函数。
与任何函数类似,一个lambda具有一个返回类型,一个参数列表和一个函数体。
但与函数不同,lambda可能定义在函数内部。
一个lambda表达式具有如下形式:
[capture list] (parameter list) ->return type {function body}
capture list: 捕获列表,是一个lambda所在函数中定义的局部变量列表(通常为空)
parameter list:参数列表
return type:返回类型
function body:函数体
但是与普通函数不同,lambda必须使用尾置返回来指定返回类型
我们可以忽略参数列表和返回类型,但必须永远包含捕获列表和函数体

auto f=[]{return 42;};//分号不能丢

此例中,我们定义了一个可调用对象f,它不接受参数,返回42.
lambda的调用方式与普通函数调用方式相同,都是使用调用运算符:

cout<<f()<<endl;//打印42

在lambda中忽略括号和参数列表等价于指定一个空参数列表。在此例中,当调用f时,参数列表是空的。如果忽略返回类型,lambda根据函数体中的代码推断出返回类型。
如果函数体只是一个return语句,则返回类型从返回的表达式的类型推断而来,否则,返会类型为void.
如果lambda的函数体包含任何一个单一的return语句之外的内容,且未指定返回类型,则返回void

向lambda传递参数

与一个普通函数调用类似,调用一个lambda时给定的实参被用来初始化lambda的形参。通常,实参和形参的类型必须匹配。但与普通函数不同,lambda不能有默认参数
因此,一个lambda调用的实参数目永远与形参数目相等。
下面举一个带参数的lambda的例子:

[](const string &a,const string& b)
{return a.size()<b.size();};//这里分号不能丢

空捕获列表表面此lambda不使用它所在函数中的任何局部变量。

使用捕获列表

虽然一个lambda可以出现在一个函数中,使用其局部变量,但它只能使用那些明确指明的变量。一个lambda通过将局部变量包含在其捕获列表中来指明将会使用这些变量。捕获列表指引lambda在其内部包含访问局部变量所需的信息。
下面举一个例子:

#include<iostream>
using namespace std;
void test()
{string sz("abc");string words("abds");auto ret=[sz](const string& a){return a.size() >= sz.size();};cout << ret("efgdasd") << endl;
}
int main()
{test();system("pause");return 0;
}


lambda以一对[]开始,我们可以在其中提供一个以逗号分隔的名字列表(当前函数中定义的局部变量),这些名字都是它所在函数中定义的。
上面例子中由于lambda只捕获了test函数中局部变量sz,因此可以在lambda的函数体中使用sz.lambda不捕获words,因此不能在lambda的函数体中访问此变量。如果我们给lambda提供一个空捕获列表,则代码会编译出错:

一个lambda只有在其捕获列表中捕获了一个它所在函数中的局部变量,才能在函数体中使用该变量

lambda表达式在泛型算法的应用

与find_if结合使用
举例:调用find_if算法在字符串s中查找第一个长度大于等于字符串sz的元素

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
//用lambda作为参数
void bigger(vector<string>& words,vector<string>::size_type sz)
{vector<string>::iterator pos = find_if(words.begin(), words.end(), [sz](const string& a) {return a.size() > sz; });cout << *pos << endl;
}
int main()
{                                                     vector<string> svec{ "the", "quick", "red", "fox", "jumps","over", "the", "slow", "red", "turtle" };bigger(svec, 4);
}


这里对find_if的调用返回一个迭代器,指向第一个长度不小于sz的元素。如果这样的元素不存在,则返回words.end()的一个拷贝
我们可以使用find_if返回的迭代器来计算从它开始到words的末尾一共有多少个元素。

//用lambda作为参数
void bigger(vector<string>& words,vector<string>::size_type sz)
{vector<string>::iterator pos = find_if(words.begin(), words.end(), [sz](const string& a) {return a.size() > sz; });//这里两个迭代器做减法,类似指针做减法,得到两个迭代器之间的距离//与指针不同,我们无法直接打印迭代器,例如cout<<pos<<endl;auto count = words.end() - pos;cout << count<< endl;
}



与for_each结合使用
举例:打印words中长度大于等于sz的元素。

//用lambda作为参数
void bigger(vector<string>& words,vector<string>::size_type sz)
{vector<string>::iterator pos = find_if(words.begin(), words.end(), [sz](const string& a) {return a.size() > sz; });for_each(pos, words.end(), [](const string& a) {cout << a << " "; });
}
int main()
{                                                     vector<string> svec{ "the", "quick", "red", "fox", "jumps","over", "the", "slow", "red", "turtle" };bigger(svec, 4);
}


此lambda中的捕获列表为空,但其函数体中还是使用了两个名字:s和cout,前者是它自己的参数。
捕获列表为空,是因为我们只对lambda所在的函数中定义的(非static)变量使用了捕获列表。一个lambda可以直接使用定义在当前函数之外的名字。在本例中,cout不是定义在bigger中的局部名字,而是定义在头文件iostream中。因此,只要在bigger出现的作用域中包含头文件iostream,我们的lambda就可以使用cout.
注意:捕获列表只用于局部非static变量,lambda可以直接使用局部static变量和它所在函数之外声明的名字
完整的biggerd代码:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
//删除重复元素
void elimDups(vector<string>& words)
{//再删除重复元素之前,需要先进行排序sort(words.begin(), words.end());//把重复元素移到尾部auto new_end = unique(words.begin(), words.end());//将尾部重复元素删除words.erase(new_end, words.end());
}
//用lambda作为参数
void bigger(vector<string>& words,vector<string>::size_type sz)
{//将words按字典序重排,并消除重复单词elimDups(words);//按长度重新排序,长度相同的单词维持字典序stable_sort(words.begin(), words.end(), [](const string& a1, const string& a2) {return a1.size() > a2.size(); });//获取一个迭代器,指向第一个满足size>=sz的元素vector<string>::iterator pos = find_if(words.begin(), words.end(), [sz](const string& a) {return a.size() > sz; });//计算满足size>=sz的元素的数目auto count = count_if(words.begin(), words.end(), [sz](const string& a) {return a.size()>sz; });cout << count << endl;//打印长度大于等于给定值的单词,每个单词后面接一个空格for_each(pos, words.end(), [](const string& a) {cout << a << " "; });
}
int main()
{                                                     vector<string> svec{ "the", "quick", "red", "fox", "jumps","over", "the", "slow", "red", "turtle" };bigger(svec, 4);
}


类似参数传递,变量的捕获方式也可以是值或引用。

值捕获

与传值参数类似,采用值捕获的前提是变量可以拷贝。
与参数不同,被捕获的变量的值实在lambda创建时拷贝,而不是调用时拷贝
举例:

#include <iostream>
using namespace std;
void test()
{size_t v1 = 42;//局部变量//将v1拷贝到名为f的可调用对象auto f = [v1] {return v1; };v1 = 0;auto j = f();cout << j << endl;//j为42,f保存了我们创建它时的拷贝
}
int main()
{            test();return 0;
}


由于被捕获的变量的值是在lambda创建时拷贝,因此随后对其修改不会影响到lambda内对应的值

引用捕获

举例:

#include <iostream>
using namespace std;
void test()
{size_t v1 = 42;//局部变量//f对象包含v1的引用auto f = [&v1] {return v1; };v1 = 0;auto j = f();cout << j << endl;//j为0,f保存了v1的引用,而非拷贝
}
int main()
{            test();return 0;
}


当我们在lambda函数体内使用此变量时,实际上使用的时引用所绑定的对象。
引用捕获和返回引用的注意事项:
如果我们采用引用的方式捕获了一个变量,就必须确保被引用的对象在lambda执行的时候是存在的。
lambda捕获的都是局部变量,这些变量在函数结束后就不复存在了。
如果lambda可能在函数结束后执行,捕获的引用执行的局部变量已经消失。
引用捕获有时候是必要的,例如:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void bigger(vector<string>& words ,ostream &os=cout,char ch=' ')
{for_each(words.begin(), words.end(), [&os, ch](const string& s) {os << s << ch; });
}
int main()
{                                                     vector<string> svec{ "the", "quick", "red", "fox", "jumps","over", "the", "slow", "red", "turtle" };bigger(svec);
}


我们不能拷贝ostream对象,因此捕获os的唯一方法就是捕获其引用(或指向os的指针)。
注意:当以引用方式捕获一个变量的时候,必须保证lambda指向时变量是存在的

对lambda变量捕获部分重点总结:

捕获一个普通变量,如int,string或其他非指针类型,通常采用简单的值捕获方式。
如果我们捕获一个指针或迭代器,或采用引用捕获方式,就必须保证对象具有预期的值。
在lambda从创建到它执行这段时间内,可能有代码改变绑定对象的值。
也就是说,在该指针(或引用)被捕获的时刻,绑定的对象的值是我们所期望的,但在lambda执行时,该对象的值已经完全不同了。
一般来说,我们应该尽量减少捕获的数据量,来避免潜在的捕获导致的问题。而且,如果有可能的话,应该避免捕获指针或引用。

隐式捕获

通过在捕获列表中写一个&或=,指示编译器推断捕获列表。
&告诉编译器采用引用方式,=则表示采用值捕获方式
例如:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void bigger(vector<string>& words,vector<string>::size_type sz)
{//sz为隐式捕获,值捕获方式auto wc = find_if(words.begin(), words.end(), [=](const string& s) {return s.size() >= sz; });cout << *wc << endl;
}
int main()
{                                                     vector<string> svec{ "the", "quick", "red", "fox", "jumps","over", "the", "slow", "red", "turtle" };bigger(svec, 4);
}


如果我们希望对一部分变量采用值捕获,对其他变量采用引用捕获,可以混合使用隐式捕获和显示捕获:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void bigger(vector<string>& words,vector<string>::size_type sz,ostream& os=cout,char c=' ')
{//os为隐式捕获,引用捕获方式//c显示捕获,值捕获方式for_each(words.begin(), words.end(), [&, c](const string& s) {os << s << c; });cout << endl;//os显示捕获,引用捕获方式//c隐式捕获,值捕获方式for_each(words.begin(), words.end(), [=,&os](const string& s) {os << s << c; });
}
int main()
{                                                     vector<string> svec{ "the", "quick", "red", "fox", "jumps","over", "the", "slow", "red", "turtle" };bigger(svec, 4);
}


当我们混合使用隐式捕获和显示捕获时,捕获列表中的第一个元素必须是一个&或=,此符号指定了默认捕获方式为引用或值
当混合使用隐式捕获和显示捕获时,显示捕获的变量必须使用与隐式捕获不同的方式。
即如果隐式不会是引用方式,则显示捕获命名变量必须采用值方式,因此不能在其名字前使用&.
类似的,如果隐式捕获采用的是值方式,则显示捕获命名的变量必须采用引用方式,即在名字前使用&。

总结lambda捕获列表:

1、空。没有使用任何函数对象参数。
2、=。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。
3、&。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量)。
4、this。函数体内可以使用Lambda所在类中的成员变量。
5、a。将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。要修改传递进来的a的拷贝,可以添加mutable修饰符。
6、&a。将a按引用进行传递。
7、a, &b。将a按值进行传递,b按引用进行传递。
8、=,&a, &b。除a和b按引用进行传递外,其他参数都按值进行传递。
9、&, a, b。除a和b按值进行传递外,其他参数都按引用进行传递。

可变lambda

默认情况下,对于一个值被拷贝的变量,lambda不会改变其值,如果我们希望能改变一个被捕获的变量的值,就必须在参数列表首加上关键字mutable。因此,可变lambda能省略参数列表:

#include <iostream>
using namespace std;
void test()
{size_t val = 42;//局部变量//f可以改变它所捕获的变量的值auto f = [val]()mutable {return val++; };val = 0;cout << f() << endl;cout << f() << endl;cout << f() << endl;cout << val << endl;
}
int main()
{test();return 0;
}


多次调用函数f,就可以多次对f保存的val进行累加改变。
但val本身值不变
一个引用捕获的变量是否可以修改依赖与此引用指向的是一个const类型还是一个非const类型:

#include <iostream>
using namespace std;
void test()
{size_t val = 42;//局部变量//val是一个非const变量的引用//可以通过f中的引用来改变它auto f = [&val]() {return val++; };val = 0;cout << f() << endl;cout << f() << endl;cout << f() << endl;cout << val << endl;
}
int main()
{test();return 0;
}


指定lambda的返回类型

默认情况下,如果一个lambda体包含return之外的任何语句,则编译器假定此lambda返回void.
与其他返回void的函数类似,被推断为返回void的lambda不能有返回值。
举例:

#include <iostream>
#include<algorithm>
using namespace std;
void test()
{int arr[5] = { -1,-2,-3,-4,-5 };transform(arr, arr + 5,arr,[](int i) {return i < 0 ? -i : i; });for (int i = 0; i < 5; i++){cout << arr[i];}cout << endl;
}
int main()
{test();return 0;
}


本例中lambda体中只有单一的return语句。我们无需指定返回类型,因为可以根据条件运算符的类型推断出来。
但是如果我们将程序改写成看起来是等价的if语句,就会产生编译错误:

虽然这里没有发生错误,是因为版本问题,有些低版本编译器会出现问题,原因在于:
编译器推断这个版本的lambda返回类型为void,但它返回一个int值。
当我们需要为一个lambda定义一个返回类型时,必须使用尾置返回类型:

#include <iostream>
#include<algorithm>
using namespace std;
void test()
{int arr[5] = { -1,-2,-3,-4,-5 };transform(arr, arr + 5, arr, [](int i)->int{if (i < 0) return -i;else return i;});for (int i = 0; i < 5; i++){cout << arr[i];}cout << endl;
}
int main()
{test();return 0;
}

lambda是函数对象----->函数对象—>重载()运算符—>operator()

当我们编写了一个lambda后,编译器将该表达式翻译成一个未命名类的未命名对象。
在lambda表达式产生的类中含有一个重载的函数调用运算符。
举例:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void bigger(vector<string>& words,vector<string>::size_type sz)
{//根据单词的长度对其进行排序stable_sort(words.begin(), words.end(), [](const string& a1, const string& a2) {return a1.size()<a2.size(); });for_each(words.begin(), words.end(), [](const string& a) {cout << a << " "; });
}
int main()
{                                                     vector<string> svec{ "the", "quick", "red", "fox", "jumps","over", "the", "slow", "red", "turtle" };bigger(svec, 4);
}


该lambda的行为类似于下面这个类的一个未命名对象
函数对象的概念
仿函数做泛型算法的参数又细分为一元谓词和二元谓词,不了解的建议去看看:
谓词的概念

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class ShortString
{public:bool operator()(const string& a1, const string& a2){return a1.size() < a2.size();}
};
void bigger(vector<string>& words,vector<string>::size_type sz)
{//根据单词的长度对其进行排序stable_sort(words.begin(), words.end(), ShortString());for_each(words.begin(), words.end(), [](const string& a) {cout << a << " "; });
}
int main()
{                                                     vector<string> svec{ "the", "quick", "red", "fox", "jumps","over", "the", "slow", "red", "turtle" };bigger(svec, 4);
}


默认情况下lambda不能改变他捕获的变量。是因为由lambda产生的类中的函数调用的运算符是一个const成员函数。如果lambda被声明为可变的,则调用运算符就不是const的了。

表示lambda及相应捕获行为的类

当一个lambda表达式通过引用捕获变量时,将由程序负责确保lambda执行时引用所引的对象确实存在。因此,编译器可以直接使用该引用而无需在lambda产生的类中将其存储为数据成员。
相反,通过值捕获的变量被拷贝到lambda中。因此,这种lambda产生的类必须为每个值捕获的变量建立对应的数据成员,同时创建构造函数,令其使用捕获捕获的变量的值来初始化数据成员。
举个例子:

void bigger(vector<string>& words,vector<string>::size_type sz)
{auto wc = find_if(words.begin(), words.end(), [sz](const string& a) {return a.size() >= sz; });
}

该lambda表达式的产生的类的将形如:

class SizeComp
{private:size_t sz;//该数据成员对应通过值捕获的变量
public:SizeComp(size_t n):sz(n){}//该形参对应的捕获变量//该调用运算符的返回类型,形参和函数体都与lambda一致bool operator()(const string& s)const{return s.size() >= sz;}
};

上面这个类含有一个数据成员以及一个用于初始化该成员的构造函数。
这个合成的类不含有默认构造函数,因此想使用这个类必须提供一个实参:

void bigger(vector<string>& words,vector<string>::size_type sz)
{auto wc = find_if(words.begin(), words.end(), SizeComp(sz));
}

lambda表达式产生的类不含默认构造函数,赋值运算符及默认析构函数;
它是否含有默认的拷贝/移动构造函数则通常要视捕获的数据成员类型而定。

完结撒花!!!

Lambda表达式用法超详细整理!!!相关推荐

  1. java lambda 局部变量_java Lambda表达式访问局部变量详细介绍

    此前给大家带来了java Lambda表达式访问成员变量详细介绍,下面就要继续给大家介绍java Lambda表达式访问局部变量的内容,一起通过简单的文章来进行了解吧. 对于成员变量的访问Lambda ...

  2. lambda表达式学习(详细理解 ) 重点在最后

    多线程的runable接口,如果你的代码简单就可以用lambda简化 package cn.com.zzn.lambda;public class TestLambda2 {// 静态内部类/* st ...

  3. C ++匿名函数:揭开C++ Lambda表达式的神秘面纱

    潜意识编程:揭秘C++ Lambda表达式的神秘面纱 Subconscious Programming: Unveiling the Mystery of C++ Lambda Expressions ...

  4. Java笔记整理五(Iterator接口,泛型,常见数据结构(栈,队列,数组,链表,红黑树,集合),jdk新特性,异常,多线程,Lambda表达式)

    Java笔记整理五 1.1Iterator接口 Collection接口与Map接口主要用于存储元素,而Iterator主要用于迭代访问(即遍历)Collection中的元素,因此Iterator对象 ...

  5. java8新特性之lambda表达式--超级详细版本

    java8新特性之lambda表达式 1.什么是lambda表达式?为什么用它? 2. 为什么Java需要lambda表达式? 3. lambda表达式的语法 4.函数式接口 4.1 什么是函数式接口 ...

  6. Java8新特性----Lambda表达式详细探讨

    Java8新特性 Lambda表达式 入门演示 案例1 如何解决 cannot be cast to java.lang.Comparable问题? 案例2 优化方式一 : 策略设计模式 优化方式二: ...

  7. java lam表达式_详细分析Java Lambda表达式

    在了解Lambda表达式之前我们先来区分一下面向对象的思想和函数式编程思想的区别 面向对象的思想: 做一件事情,找一个能解决这个事情的对象,调用他的方法来解决 函数时编程思想: 只要能获取到结果,谁去 ...

  8. Linq、Lambda表达式详细总结(转)

    (一)输入参数 在Lambda表达式中,输入参数是Lambda运算符的 左边部分.它包含参数的数量可以为0.1或者多个.只有当输入参数为1时,Lambda表达式左边的一对小括弧才可以省略.输入参数的数 ...

  9. 让你秒懂的Lambda表达式超级详细讲解

    一.Lambda表达式 (1)函数式编程思想概述 在数学中,函数就是有输入量.输出量的一套计算方案,也就是"拿数据做操作" 面向对象思想强调"必须通过对象的形式来做事情& ...

最新文章

  1. 给用户培训的几点感受
  2. iphone开发如何测试?
  3. 旷视 AI 飞跃 | 研究生联合培养计划
  4. 火狐ok谷歌适配_“ OK Google”在锁定手机上的安全性越来越高
  5. [JAVA实现]微信公众号网页授权登录,java开发面试笔试题
  6. 基于JAVA+SpringMVC+Mybatis+MYSQL的旅游管理系统
  7. lievent源码分析:evbuffer
  8. 剪映专业版mac版本要求 剪映mac版最新版更新
  9. 滚轮事件的防冒泡、阻止默认行为
  10. 工作日志模板_生产运行部操作日志“电子化”
  11. mcs-51单片机CPU的内部结构及工作原理
  12. 微星GT60笔记本升级1060显卡案例
  13. python四分位数_python 计算箱线图、中位数、上下四分位数等
  14. APtos 简介及机制
  15. bitly短网址v4版本的操作及sample code
  16. html5带倍速功能的视频播放器(加速2倍,1.5倍播放)
  17. Uart串口实验--TQ2440
  18. XXL-JOB原理--任务调度中心执行器注册(三)
  19. 南土所褚海燕组综述微生物组学的技术和方法及其应用
  20. 如何批量将 bmp 格式图片转换转换为 jpg

热门文章

  1. 英国工党的歌曲‘耶路撒冷’
  2. android显示屏线接口定义,常见TFT液晶显示器的接口定义
  3. Android onTouch事件传递机制
  4. 全球及中国柔性AMOLED面板行业消费量调研及投资前景预测报告2022-2028年
  5. pkusc2017 BingBingBangBang流水账
  6. 《寻寻觅觅错误无处找?教你如何快速寻找程序错误(必看)》
  7. Django Vue渲染动态数据(七)
  8. 图书馆管理系统(c++语言实现)
  9. 【Unity3D 问题总结】Unity报错提示:Asset database transaction committed twice
  10. 基于Javaweb实现餐饮管理系统