一文读懂c++11 Lambda表达式

1.简介

1.1定义

C++11新增了很多特性,Lambda表达式(Lambda expression)就是其中之一,很多语言都提供了 Lambda 表达式,如 Python,Java ,C#等。本质上, Lambda 表达式是一个可调用的代码单元[1]^{[1]}[1]。实际上是一个闭包(closure),类似于一个匿名函数,拥有捕获所在作用域中变量的能力,能够将函数做为对象一样使用,通常用来实现回调函数、代理等功能。Lambda表达式是函数式编程的基础,C++11引入了Lambda则弥补了C++在函数式编程方面的空缺。

1.2作用

以往C++需要传入一个函数的时候,必须事先进行声明,视情况可以声明为一个普通函数然后传入函数指针,或者声明一个仿函数(functor,函数对象),然后传入一个对象。比如C++的STL中很多算法函数模板需要传入谓词(predicate)来作为判断条件,如排序算法sort。谓词就是一个可调用的表达式,其返回结果是一个能用作条件的值。标准库算法所使用的谓词分为两类:一元谓词(unary predicate,只接受单一参数)和二元谓词(binary predicate,接受两个参数)。接受谓词的算法对输入序列中的元素调用谓词,因此元素类型必须能转换为谓词的参数类型。如下面使用sort()传入比较函数shorter()(这里的比较函数shorter()就是谓词)将字符串按长度由短至长排列。

//谓词:比较函数,用来按长度排列字符串
bool shorter(const string& s1,const string& s2)
{
 return s1.size()<s2.size();
}

//按长度由短至长排列words
std::sort(words.begin(),words.end(),shorter);

Lambda表达式可以像函数指针、仿函数一样,作为一个可调用对象(callable object)被使用,比如作为谓词传入标准库算法。

也许有人会问,有了函数指针、函数对象为何还要引入Lambda呢?函数对象能维护状态,但语法开销大,而函数指针语法开销小,却没法保存函数体内的状态。如果你觉得鱼和熊掌不可兼得,那你可错了。Lambda函数结合了两者的优点,让你写出优雅简洁的代码。

1.3语法格式

Lambda 表达式就是一个可调用的代码单元,我们可以将其理解为一个未命名的内联函数。与任何函数类似,一个Lambda具有一个返回类型、一个参数列表和一个函数体。但与函数不同,Lambda可以定义在函数内部,其语法格式如下:

[capture list](parameter list) mutable(可选) 异常属性->return type{function body}

capture list(捕获列表)是一个Lambda所在函数中定义的局部变量的列表,通常为空,表示Lambda不使用它所在函数中的任何局部变量。parameter list(参数列表)、return type(返回类型)、function body(函数体)与任何普通函数基本一致,但是Lambda的参数列表不能有默认参数,且必须使用尾置返回类型。 mutable表示Lambda能够修改捕获的变量,省略了mutable,则不能修改。异常属性则指定Lambda可能会抛出的异常类型。

其中Lambda表达式必须的部分只有capture list和function body。在Lambda忽略参数列表时表示指定一个空参数列表,忽略返回类型时,Lambda可根据函数体中的代码推断出返回类型。例如:

auto f=[]{return 42;}

我们定义了一个可调用对象f,它不接受任何参数,返回42。auto关键字实际会将 Lambda 表达式转换成一种类似于std::function的内部类型(但并不是std::function类型,虽然与std::function“兼容”)。所以,我们也可以这么写:

std::function<int()> Lambda = [] () -> int { return val * 100;};

如果你对std::function<int()>这种写法感到很神奇,可以查看 C++ 11 的有关std::function的用法。简单来说,std::function<int()>是一个实例化后的模板类,代表一个可调用的对象,接受 0 个参数,返回值是int。所以,当我们需要一个接受一个double作为参数,返回int的对象时,就可以写作:std::function<int(double)>[3]^{[3]}[3]。

1.4调用方式

Lambda表达式的调用方式与普通函数的调用方式相同,上面Lambda表达式的调用方式如下:

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

//或者直接调用
cout<<[]{return 42;}()<<endl;

我们还可以定义一个单参数的Lambda,实现上面字符串排序的shorter()比较函数的功能:

auto f=[](cosnt string& a,const string& b)
{
 return a.size()<b.size();
}

//将Lambda传入排序算法sort中
sort(words.begin(),word2.end(),[](cosnt string& a,const string& b){
 return a.size()<b.size();
});

//或者
sort(words.begin(),word2.end(),f);

2.Lambda的捕获列表

Lambda可以获取(捕获)它所在作用域中的变量值,由捕获列表(capture list)指定在Lambda 表达式的代码内可使用的外部变量。比如虽然一个Lambda可以出现在一个函数中,使用其局部变量,但它只能使用那些在捕获列表中明确指明的变量。Lambda在捕获所需的外部变量有两种方式:引用和值。我们可以在捕获列表中设置各变量的捕获方式。如果没有设置捕获列表,Lambda默认不能捕获任何的变量。捕获方式具体有如下几种:

  • [] 不截取任何变量
  • [&} 截取外部作用域中所有变量,并作为引用在函数体中使用
  • [=] 截取外部作用域中所有变量,并拷贝一份在函数体中使用
  • [=,&valist]   截取外部作用域中所有变量,并拷贝一份在函数体中使用,但是对以逗号分隔valist使用引用
  • [&,valist] 以引用的方式捕获外部作用域中所有变量,对以逗号分隔的变量列表valist使用值的方式捕获
  • [valist] 对以逗号分隔的变量列表valist使用值的方式捕获
  • [&valist] 对以逗号分隔的变量列表valist使用引用的方式捕获
  • [this] 截取当前类中的this指针。如果已经使用了&或者=就默认添加此选项。

在[]中设置捕获列表,就可以在Lambda中使用变量a了,这里使用按值(=, by value)捕获。

#include <iostream>

int main()
{
 int a = 123;
 auto lambda = [=]()->void
 {
 std::cout << "In Lambda: " << a << std::endl;
 };
 lambda();
 return 0;
}

编译运行结果如下:

In Lambda: 123

按值传递到Lambda中的变量,默认是不可变的(immutable),如果需要在Lambda中进行修改的话,需要在形参列表后添加mutable关键字(按值传递无法改变Lambda外变量的值)。

#include <iostream>
int main()
{
 int a = 123;
 std::cout << a << std::endl;
 auto lambda = [=]() mutable ->void{
 a = 234;
 std::cout << "In Lambda: " << a << std::endl;
 };
 lambda();
 std::cout << a << std::endl;
 return 0;
}

编译运行结果为:

123
In Lambda: 234  //可以修改
123             //注意这里的值,并没有改变

如果没有添加mutable,则编译出错:

$ g++ main.cpp -std=c++11
main.cpp:9:5: error: cannot assign to a variable captured by copy in a non-mutable Lambda
    a = 234;
                ~ ^
1 error generated.

看到这,不禁要问,这魔法般的变量捕获是怎么实现的呢?原来,Lambda是通过创建个类来实现的。这个类重载了操作符(),一个Lambda函数是该类的一个实例。当该类被构造时,周围的变量就传递给构造函数并以成员变量保存起来,看起来跟函数对象(仿函数)很相似,但是C++11标准建议使用Lambda表达式,而不是函数对象,Lambda表达式更加轻量高效,易于使用和理解[4]^{[4]}[4]。

3.Lambda的类型

lambda函数的类型看起来和函数指针很像,都是把函数赋值给了一个变量。实际上,lambda函数是用仿函数实现的,它看起来又像是一种自定义的类。而事实上,lambda类型并不是简单的函数指针类型或者自定义类型,lambda函数是一个闭包(closure)的类,C++11标准规定,closure类型是特有的、匿名且非联合体的class类型。每个lambda表达式都会产生一个闭包类型的临时对象(右值)。因此,严格来说,lambda函数并非函数指针,但是C++11允许lambda表达式向函数指针转换,前提是没有捕捉任何变量且函数指针所指向的函数必须跟lambda函数有相同的调用方式。

typedef int(*pfunc)(int x, int y);

int main()
{
 auto func = [](int x, int y)->int {
 return x + y;
 };
 pfunc p1 = nullptr;
 p1 = func;  //lambda表达式向函数指针转换

 std::cout << p1(1, 2) << std::endl;

 return 0;
}

4.lambda的常量性和mutable关键字

C++11中,默认情况下lambda函数是一个const函数,按照规则,一个const成员函数是不能在函数体内改变非静态成员变量的值。

int main()
{
 int val = 0;
 auto const_val_lambda = [=] { val = 3; }; // 编译失败,不能在const的lambda函数中修改按值捕获的变量val

 auto mutable_val_lambda = [=]() mutable { val = 3; };

 auto const_ref_lambda = [&] { val = 3; };

 auto const_param_lambda = [](int v) { v = 3; };
 const_param_lambda(val);

 return 0;
}

阅读代码,注意以下几点:
 (1)可以看到在const的lambda函数中无法修改按值捕捉到的变量。lambda函数是通过仿函数来实现的,捕捉到的变量相当于是仿函数类中的成员变量,而lambda函数相当于是成员函数,const成员函数自然不能修改普通成员变量;
 (2)使用引用的方式捕获的变量在常量成员函数中值被更改则不会导致错误,其原因简单地说,由于const_ref_lambda 不会改变引用本身,而只会改变引用的值,所以编译通过;
 (3)使用mutable修饰的mutable_val_lambda,去除了const属性,所以可以修改按值方式捕获到的变量;
 (4)按值传递参数的const_param_lambda修改的是传入lambda函数的实参,当然不会有问题。

5.Lambda的常见用法

(1)Lambda函数和STL
 Lambda函数的引入为STL的使用提供了极大的方便。比如下面这个例子,当你想遍历一个vector的时候,原来你得这么写:

vector<int> v={1,2,3,4,5,6,7,8,9};

//传统的for循环
for ( auto itr = v.begin(), end = v.end(); itr != end; itr++ )
{
 cout << *itr;
}

//函数指针
void printFunc(int v)
{
 cout<<v;
}
for_each(v.begin(),v.end(),printFunc);

//仿函数
struct CPrintFunc
{
 void operator() (int val)const { cout << val; }
};
for_each(v.begin(),v.end(),CPrintFunc());

现在有了Lambda函数你就可以这么写:

for_each(v.begin(),v.end(),[](int val)
{
 cout << val;
});

很明显,相比于传统的for循环、函数指针和仿函数,使用lambda函数更加简洁。如果处理vector成员的业务代码更加复杂,那么更能凸显Lambda函数的便捷。而且这么写之后执行效率反而会提高,因为编译器有可能使用循环展开来加速执行过程。

以上就是一文读懂c++11 Lambda表达式的详细内容,更多关于c++11 Lambda表达式的资料请关注我们其它相关文章!

(0)

相关推荐

  • 浅析C++11新特性的Lambda表达式

    lambda简介 熟悉Python的程序员应该对lambda不陌生.简单来说,lambda就是一个匿名的可调用代码块.在C++11新标准中,lambda具有如下格式: [capture list] (parameter list) -> return type { function body } 可以看到,他有四个组成部分: 1.capture list: 捕获列表 2.parameter list: 参数列表 3.return type: 返回类型 4.function body: 执行代码

  • 浅谈C++11新引入的lambda表达式

    ISO C++ 11 标准的一大亮点是引入Lambda表达式.基本语法如下: [capture list] (parameter list) ->return type { function body } 简单的讲一下各个部分的作用 1.[capture list]捕获列表,捕获到函数体中,使得函数体可以访问 2.(parameter list)参数列表,用来表示lambda表达式的参数列表 3.->return type函数返回值 {function body}就是函数体 lambda表达式

  • 结合C++11新特性来学习C++中lambda表达式的用法

    在 C++ 11 中,lambda 表达式(通常称为 "lambda")是一种在被调用的位置或作为参数传递给函数的位置定义匿名函数对象的简便方法. Lambda 通常用于封装传递给算法或异步方法的少量代码行. 本文定义了 lambda 是什么,将 lambda 与其他编程技术进行比较,描述其优点,并提供一个基本示例. Lambda 表达式的各部分 ISO C++ 标准展示了作为第三个参数传递给 std::sort() 函数的简单 lambda: #include <algorit

  • 一文读懂c++11 Lambda表达式

    1.简介 1.1定义 C++11新增了很多特性,Lambda表达式(Lambda expression)就是其中之一,很多语言都提供了 Lambda 表达式,如 Python,Java ,C#等.本质上, Lambda 表达式是一个可调用的代码单元[1]^{[1]}[1].实际上是一个闭包(closure),类似于一个匿名函数,拥有捕获所在作用域中变量的能力,能够将函数做为对象一样使用,通常用来实现回调函数.代理等功能.Lambda表达式是函数式编程的基础,C++11引入了Lambda则弥补了C

  • 一文搞懂C++11万能引用和右值引用

    目录 前言 正文 万能引用 结语 参考: 前言 我们通过一个问题来进入今天的话题:1.形如 “type&&” 的结构,就是右值引用吗?2.以下哪些属于右值引用? ① void fun(Widget && param);② Widget && var1= Widget();③ auto && var2 = var1;④ template<typename T> void f(std::vector<T>&&

  • 一文读懂Spring Bean的生命周期

    目录 一.前言 1.1 什么是 Bean 1.2 什么是 Spring Bean 的生命周期 二.Spring Bean 的生命周期 三.Spring Bean 的生命周期的扩展点 3.1 Bean 自身的方法 3.2 容器级的方法(BeanPostProcessor 一系列接口) 3.2.1 InstantiationAwareBeanPostProcessor 源码分析 3.2.2 BeanPostProcessor 源码分析 3.3 工厂后处理器方法(BeanFactoryProcesso

  • 一文读懂modbus slave和modbus poll使用说明

    modbus slave和modbus poll使用说明 1.使用环境: win7/win10  32/64位系统  Virtual Serial Port Driver 9.0 虚拟com端口工具 2.说明: 最近项目开发使用到了modbus协议,由于刚接触这个协议,在使用第三方工具进行调试的时候使用到了modbus poll和modbus slave工具,以下是简单的使用记录,希望以后对需要者有所帮助. 3.modbus poll和modbus slave是一款实用的modbus开发和调试工

  • 一文读懂MySQL 表分区

    目录 1. 什么是表分区 2. 分区的两种方式 2.1 水平切分 2.2 垂直切分 3. 为什么需要表分区 4. 分区实践 4.1 RANGE 分区 4.2 LIST 分区 4.3 HASH 分区 4.4 KEY 分区 4.5 COLUMNS 分区 5. 常见分区命令 6. 小结 松哥之前写过文章跟大家介绍过用 MyCat 实现 MySQL 的分库分表,不知道有没有小伙伴研究过,MySQL 其实也自带了分区功能,我们可以创建一个带有分区的表,而且不需要借助任何外部工具,今天我们就一起来看看. 1

  • 一文读懂ava中的Volatile关键字使用

    在本文中,我们会介绍java中的一个关键字volatile. volatile的中文意思是易挥发的,不稳定的.那么在java中使用是什么意思呢? 我们知道,在java中,每个线程都会有个自己的内存空间,我们称之为working memory.这个空间会缓存一些变量的信息,从而提升程序的性能.当执行完某个操作之后,thread会将更新后的变量更新到主缓存中,以供其他线程读写. 因为变量存在working memory和main memory两个地方,那么就有可能出现不一致的情况. 那么我们就可以使

  • 一文读懂JAVA中HttpURLConnection的用法

    针对JDK中的URLConnection连接Servlet的问题,网上有虽然有所涉及,但是只是说明了某一个或几个问题,是以FAQ的方式来解决的,而且比较零散,现在对这个类的使用就本人在项目中的使用经验做如下总结: 1:> URL请求的类别: 分为二类,GET与POST请求.二者的区别在于: a:) get请求可以获取静态页面,也可以把参数放在URL字串后面,传递给servlet, b:) post与get的不同之处在于post的参数不是放在URL字串里面,而是放在http请求的正文内. 2:>

  • 一文读懂c++之static关键字

    一.静态变量 与C语言一样,可以使用static说明自动变量.根据定义的位置不同,分为静态全局变量和静态局部变量. 全局变量是指在所有花括号之外声明的变量,其作用域范围是全局可见的,即在整个项目文件内都有效.使用static修饰的全局变量是静态全局变量,其作用域有所限制,仅在定义该变量的源文件内有效,项目中的其他源文件中不能使用它. 块内定义的变量是局部变量,从定义之处开始到本块结束处为止是局部变量的作用域.使用static修饰的局部变量是静态局部变量,即定义在块中的静态变量.静态局部变量具有局

  • 一文读懂Java Iterator(迭代器)

    Java Iterator(迭代器)不是一个集合,它是一种用于访问集合的方法,可用于迭代 ArrayList和HashSet等集合. Iterator 是 Java 迭代器最简单的实现,ListIterator 是 Collection API 中的接口, 它扩展了 Iterator 接口. 迭代器 it 的两个基本操作是 next .hasNext 和 remove. 调用 it.next() 会返回迭代器的下一个元素,并且更新迭代器的状态. 调用 it.hasNext() 用于检测集合中是否

  • 一文读懂vue动态属性数据绑定(v-bind指令)

    v-bind的基本用法 一.本节说明 前面的章节我们学习了如何向页面html标签进行插值操作,那么如果我们想动态改变html标签的属性,该怎么办呢? 这就是我们这节开始要讲的内容v-bind. 二. 怎么做 ":"为v-bind的简写形式,也可称为语法糖 三. 效果 四. 深入 在上图中将a标签的href属性值设置为toutiao,VUE实例将自动去data里面寻找toutiao属性进行值绑定. 不只是a标签,所有的html标签属性都可以通过v-bind进行值绑定,然后通过改变数据动态

随机推荐