一个lambda表达式用于创建闭包。lambda表达式与任何函数类似,具有返回类型、参数列表和函数体。与函数不同的是,lambda能定义在函数内部。lambda表达式具有如下形式

[ capture list ] ( parameter list) -> return type { function body }

capture list,捕获列表,是一个lambda所在函数中定义的局部变量的列表。lambda函数体中可以使用这些局部变量。捕获可以分为按值捕获和按引用捕获。非局部变量,如静态变量、全局变量等可以不经捕获,直接使用;

parameter list,参数列表。从C++14开始,支持默认参数,并且参数列表中如果使用auto的话,该lambda称为泛化lambda(generic lambda);

return type,返回类型,这里使用了返回值类型尾序语法(trailing return type synax)。可以省略,这种情况下根据lambda函数体中的return语句推断出返回类型,就像普通函数使用decltype(auto)推导返回值类型一样;如果函数体中没有return,则返回类型为void。

function body,与任何普通函数一样,表示函数体。

Lambda表达式可以忽略参数列表和返回类型,但必须包含捕获列表和函数体:

auto f = [] { return ; }
cout << f() << endl;

上面的lambda表达式,定义了一个可调用对象f,它不接受参数,返回42。Lambda的调用方式与普通函数的调用方式相同。

lambda表达式是用于生成闭包的纯右值(prvalue)表达式。每一个lambda表达式都定义了独一无二的闭包类,闭包类内主要的成员有operator()成员函数:

ret operator()(params) const { body } //the keyword mutable was not used
ret operator()(params) { body } //the keyword mutable was used template<template-params> //since C++14, generic lambda
ret operator()(params) const { body } template<template-params> //since C++14, generic lambda, the keyword mutable was used
ret operator()(params) { body }

当调用lambda表达式生成的闭包时,执行operator()函数。除非lambda表达式中使用了mutable关键字,否则lambda生成的闭包类的operator()函数具有const饰词,从而lambda函数体中不能修改其按值捕获的变量;如果lambda表达式的参数列表中使用了auto,则相应的参数称为模板成员函数operator()的模板形参,该lambda表达式也就成了泛化lambda表达式。

如果捕获列表中,有按值捕获的局部变量,则闭包类中就会有相应的未命名成员变量副本,这些成员变量在定义lambda表达式时就由那些相应的局部变量进行初始化。如果按值捕获的变量是个函数引用,则相应的成员变量是引用指向函数的左值引用;如果是个对象引用,则相应的成员变量是该引用指向的对象。如果是按引用捕获,标准中未指明是否会在闭包类中引入相应的成员变量。

该闭包类还有其他成员函数。比如转换为函数指针的转换函数、构造函数(包括复制构造函数)、析构函数等,具体可参考https://en.cppreference.com/w/cpp/language/lambda

一:捕获列表

lambda可以定义在函数内部,使用其局部变量,但它只能使用那些明确指明的变量。lambda通过将外部函数的局部变量包含在其捕获列表中来指出将会使用这些变量。

当定义一个lambda时,编译器生成一个与lambda对应的新的(未命名的)类。当向函数传递一个lambda时,同时定义了一个新类型和该类型的一个对象:传递的参数就是编译器生成的类类型的未命名对象;类似的,当使用auto定义一个用lambda初始化的变量时,定义了一个从lambda生成的类型的对象。

默认情况下,从lambda生成的类都包含一个对应该lambda所捕获的变量的数据成员。类似任何普通类的数据成员,lambda的数据成员在lambda对象创建时被初始化。

1:值捕获

类似参数传递,变量的捕获方式可以是值或引用。与传值参数类似,采用值捕获的前提是变量可以拷贝。被捕获的变量的值是在lambda创建时拷贝,而不是调用时拷贝:

int v1 = ;
auto f=[v1]{return v1;};
v1=;
auto j = f(); //j is 42

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

2:引用捕获

定义lambda时可以采用引用方式捕获变量。例如:

int v1 = ;
auto f=[&v1]{return v1;};
v1=;
auto j = f(); //j is 0

v1之前的&指出v1应该以引用方式捕获。一个以引用方式捕获的变量与其他任何类型的引用的行为类似。当我们在lambda函数体内使用此变量时,实际上使用的是引用所绑定的对象。在本例中,当lambda返回v1时,它返回的是v1指向的对象的值。

引用捕获与返回引用有着相同的问题和限制。如果我们采用引用方式捕获一个变量,就必须确保被引用的对象在lambda执行的时候是存在的。lambda捕获的都是局部变量,这些变量在函数结束后就不复存在了。如果lambda可能在函数结束后执行,捕获的引用指向的局部变量已经消失,这就是未定义行为。

引用捕获有时是必要的:

void biggies(vector<string> &words,
vector<string>::size_ type sz,
ostream &os=cout, char c=' ')
{
for_each(words.begin(), words.end(),
[&os, c](const strinq &s) { os << s << c; });
}

不能拷贝ostream对象,因此捕获os的唯一方法就是捕获其引用。当我们向一个函数传递lambda时,就像本例子调用for_each那样,lambda会在函数内部执行。在此情况下,以引用方式捕获os没有问题,因为当for_each执行时,biggies中的变量是存在的。

我们也可以从一个函数返回lambda。函数可以直接返问一个可调用对象,或者返回一个类对象,该类含有可调用对象的数据成员。如果函数返回一个lambda,则与函数不能返回一个局部变量的引用类似,此lambda也不能包含引用捕获。

3:隐式捕获

除了显式列出我们希望使用的来自所在函数的变量之外,还可以让编译器根据lambda体中的代码来推断我们要使用哪些变量。为了指示编译器推断捕获列表,应在捕获列表中写一个” &” 或”=”。 ” &”告诉编译器采用引用捕获方式,”=”则表示采用值捕获方式。例如:

we = find_if(words.begin(), words.end(),
[=](const string &s)
{ return s.size() >= sz; });

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

void biggies(vector<string> &words, vector<string>::size_ type sz,
ostream &os=cout, char c=' ')
{
//os隐式捕获,引用捕获方式;c显式捕获,值捕获方式
for_each(words.begin(), words.end(),
[&, c](const strinq &s) { os << s << c; }); //os显式捕获,引用捕获方式;c隐式捕获,值捕获方式
for_each(words.begin(), words.end(),
[=, &os](const strinq &s) { os << s << c; });
}

当混合使用隐式捕获和显式捕获时,捕获列表中的第一个元素必须是一个”&”或”=“。此符号指定了默认捕获方式为引用或值;并且显式捕获的变量必须使用与隐式捕获不同的方式。即,如果隐式捕获是引用方式,则显式捕获命名变量必须采用值方式;类似的,如果隐式捕获采用的是值方式,则显式捕获命名变量必须采用引用方式。

二:可变lambda

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

int v1 = ;
auto f=[v1] () mutable {return ++v1;};
v1=;
auto j = f(); //j is 43

一个引用捕获的变量是否可以修改依赖于此引用指向的是一个const类型还是一个非const类型:

int v1 = ;
auto f=[&v1] () {return ++v1;};
v1=;
auto j = f(); //j is 1

C++ lambda表达式总结的更多相关文章

  1. 你知道C#中的Lambda表达式的演化过程吗?

    那得从很久很久以前说起了,记得那个时候... 懵懂的记得从前有个叫委托的东西是那么的高深难懂. 委托的使用 例一: 什么是委托? 个人理解:用来传递方法的类型.(用来传递数字的类型有int.float ...

  2. Linq表达式、Lambda表达式你更喜欢哪个?

    什么是Linq表达式?什么是Lambda表达式? 如图: 由此可见Linq表达式和Lambda表达式并没有什么可比性. 那与Lambda表达式相关的整条语句称作什么呢?在微软并没有给出官方的命名,在& ...

  3. 背后的故事之 - 快乐的Lambda表达式(一)

    快乐的Lambda表达式(二) 自从Lambda随.NET Framework3.5出现在.NET开发者眼前以来,它已经给我们带来了太多的欣喜.它优雅,对开发者更友好,能提高开发效率,天啊!它还有可能 ...

  4. Kotlin的Lambda表达式以及它们怎样简化Android开发(KAD 07)

    作者:Antonio Leiva 时间:Jan 5, 2017 原文链接:https://antonioleiva.com/lambdas-kotlin/ 由于Lambda表达式允许更简单的方式建模式 ...

  5. java8中lambda表达式的应用,以及一些泛型相关

    语法部分就不写了,我们直接抛出一个实际问题,看看java8的这些新特性究竟能给我们带来哪些便利 顺带用到一些泛型编程,一切都是为了简化代码 场景: 一个数据类,用于记录职工信息 public clas ...

  6. 背后的故事之 - 快乐的Lambda表达式(二)

    快乐的Lambda表达式 上一篇 背后的故事之 - 快乐的Lambda表达式(一)我们由浅入深的分析了一下Lambda表达式.知道了它和委托以及普通方法的区别,并且通过测试对比他们之间的性能,然后我们 ...

  7. CRL快速开发框架系列教程二(基于Lambda表达式查询)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

  8. Lambda 表达式递归用法实例

    注意: 使用Lambda表达式会增加额外开销,但却有时候又蛮方便的. Windows下查找子孙窗口实例: HWND FindDescendantWindows(HWND hWndParent, LPC ...

  9. Spark中Lambda表达式的变量作用域

    通常,我们希望能够在lambda表达式的闭合方法或类中访问其他的变量,例如: package java8test; public class T1 { public static void main( ...

  10. 释放Android的函数式能量(I):Kotlin语言的Lambda表达式

    原文标题:Unleash functional power on Android (I): Kotlin lambdas 原文链接:http://antonioleiva.com/operator-o ...

随机推荐

  1. SQLServer-SQLServer2017:安装 SQL Server 的硬件和软件要求

    ylbtech-SQLServer-SQLServer2017:安装 SQL Server 的硬件和软件要求 1.返回顶部 1. 安装 SQL Server 的硬件和软件要求 2018/11/06 适 ...

  2. js 给链接 url或href或js、css、图片等解决浏览器缓存

    一. 添加时间戳 情况一.链接是常量 var rand = new Date().getTime(); var aLen=document.getElementsByTagName("a&q ...

  3. 创业型 APP 如何筛选合适的推送平台

    对于中小型 App 开发团队来说,采用何种方式实现适时而精准的消息推送是一件矛盾的事.将相同内容推送给所有终端用户,担心打扰用户.引起用户反感:而个性化的分群推送,又因为团队人少.运营精力不足无法实现 ...

  4. ArrayList基础知识

    ArrayList简介 ArrayList 的底层是数组队列,相当于动态数组.与 Java 中的数组相比,它的容量能动态增长.在添加大量元素前,应用程序可以使用ensureCapacity操作来增加 ...

  5. 在scrapy中利用Selector来提取数据

    1.创建对象 Selector类的实现位于scrapy.selector模块,创建Selector对象的时候,可以将页面的Html文档字符串传递给Selector构造器方法 2.选中数据 调用Sele ...

  6. 洛谷P2827 蚯蚓

    传送门 pts85/90(90应该是个意外,第一次交是90之后都是85了): 优先队列模拟题意 #include<iostream> #include<cstdio> #inc ...

  7. @EnableAsync使用

    EnableAsync注解的意思是可以异步执行,就是开启多线程的意思.可以标注在方法.类上. 1 @Component 2 public class Task { 3 4 @Async 5 publi ...

  8. SSM3-SVN的安装和搭建环境

    1.安装svn 2.创建仓库 3.设置用户 . 4.eclipse和svn的集成 eclipse里安装SVN插件,一般来说,有两种方式: 直接下载SVN插件,将其解压到eclipse的对应目录里 使用 ...

  9. vue-cli+webpack搭建简单的vue项目框架

    0.先去官网下载安装nodeJS 1.在cmd中输入命令 node -version    若出现node版本号 则安装成功 2.通过命令:cd 文件夹名     进入某具体文件夹后进行如下命令操作: ...

  10. java 7,8 排序异常

    排序报 java.lang.IllegalArgumentException: Comparison method violates its general contract! 要明确返回-1, 0, ...