C++11:lambda表达式详细介绍
前言
Lambda(匿名函数)表达式是C++11最重要的特性之一,lambda来源于函数式编程的概念,也是现代编程语言的一个特点。
优点如下:
- 声明式编程风格:就地匿名定义目标函数或函数对象,有更好的可读性和可维护性。
- 简洁:不需要额外写一个命名函数或函数对象,,避免了代码膨胀和功能分散。
- 更加灵活:在需要的时间和地点实现功能闭包。
概念及基本用法
lambda表达式定义了一个匿名函数,并且可以捕获一定范围内的变量。语法形式如下:
[ capture ] ( params ) opt -> ret { body; };
- capture:捕获列表
- params:参数列表
- opt:函数选项
- ret:返回值类型
- body:函数体
一个完整的lambda表达式是这样:
auto f = [](int a) -> int {return a + 1;};
cout << f(3) << endl; //输出4
以上定义了一个完整的lambda,但是在实际的使用中,可以省略其返回值的定义,编译器会根据return语句进行自动推导返回值类型。
省略过后如下:
auto f = [](int a) {return a + 1;};
需要注意的是,初始化列表不能用于返回值的自动推导:
如:auto f = [](){return {1,2};}; //error:无法推导返回值类型
另外,如果表达式没有参数列表时,也可以省略,如:
auto f = []{return 1;};
捕获变量
lambda表达式可以通过捕获列表捕获一定范围内的变量,主要有以下几种情况:
- [] 不捕获任何变量
- [&]捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获)
- [=]捕获外部作用域中所有变量,并作为副本在函数体重使用(按值捕获)
- [=,&foo] 按值捕获外部作用域中所有变量,并按引用捕获foo变量
- [bar] 按值捕获bar变量,同时不捕获其他变量
- [this] 捕获当前类中的this指针,让表达式拥有和当前类成员函数同样的访问权限。如果已经使用了&或者=,就默认添加此选项。捕获this的目的是可以在lambda中使用当前类的成员变量和成员函数。
通过示例来看具体用法:
class A
{public:int i_ = 0;void func(int x,int y){auto x1 = []{return i_;}; // error,没有捕获外部变量auto x2 = [=]{return i_ + x + y;}; //ok,按值捕获所有外部变量auto x3 = [&]{return i_ + x + y;}; //ok,按引用捕获所有外部变量auto x4 = [this]{return i_;}; //ok,捕获this指针auto x5 = [this]{return i_ + x + y;}; //error,没有捕获x和y变量auto x6 = [this,x,y]{return i_ + x + y;}; //ok,捕获了this指针和x、y变量auto x7 = [this]{return i_++;}; //ok,捕获了this指针,修改成员变量的值}
};
int a = 0 , b = 0 ;
auto f1 = []{return a;}; // error,没有捕获外部变量
auto f2 = [&]{return a++;}; //ok,捕获所有外部变量,并对a变量自加
auto f3 = [=]{return a;}; //ok,捕获所有外部变量,并返回a
auto f4 = [=]{return a++;}; //error,a变量是以复制方式捕获的,不能修改
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表达式的延迟调用很容易出错,如下:
int a = 0;
auto f = [=]{return a;};
a += 1;
cout << f() << endl;
以上示例中,lambda按值捕获了所有外部变量,在捕获的时候 a的值就已经被复制到 f 中了,之后a被修改,但是f里面存储的a仍然是捕获时的值,所以最终输出的是 0.
如果希望lambda表达式在调用的时候能够访问外部变量,需要使用引用方式捕获。
所以简单来说,按值捕获,外部变量会被复制一份存储在lambda表达式变量中。
如果是按值捕获并且又想修改外部变量,可以显示指明lambda表达式为mutable:
int a = 0;
auto f1 = [=]{return a++;}; //error,修改按值捕获的外部变量
auto f2 = [=]() mutable {return a++;}; //ok
被mutable修饰的lambda表达式就算没有参数也要写明参数列表。
lambda表达式类型
lambda表达式的类型在C++11中被称为“闭包类型”,它是一个特殊的,匿名的非nunion的类型。
可以认为它是带有一个operator()的类,即仿函数。
我们可以通过std::function和std::bind来存储和操作lambda表达式:
std::function<int(int)> f1 = [](int a){return a;};
std::function<int(void)> f2 = std::bind([](int a){return a;},123);
另外,对于没有捕获任何变量的lambda表达式,还可以被转换成一个普通的函数指针:
using func_t = int(*)(int);
func_t f = [](int a){return a;};
f(123);
lambda可以说是就地定义仿函数闭包的“语法 糖”。它的捕获列表捕获住任何外部变量,最终都会变为闭包类型的成员变量。而一个成员变量的类的operator(),如果能直接被转换为普通的函数指针,那么lambda表达式本身的this指针就丢掉了。而没有捕获任何外部变量的lambda表达式则不存在这个问题。
需要注意的是,没有捕获变量的lambda表达式可以直接转换为函数指针,而捕获变量的lambda表达式则不能转换为函数指针。如下:
typedef void(*Ptr)(int*);
Ptr p = [](int *p){delete p;}; //ok
Ptr p1 = [&](int *p){delete p;}; //error
前面说到的按值捕获无法修改捕获的外部变量,因为按照C++标准,lambda表达式的operator()默认是const的,一个const成员函数是无法修改成员变量的值,而mutable的作用,就是取消operator()的const限制。
声明式的编程风格
通过示例来看一下lambda的使用,在C++11之前,如果要用for_each函数将数组中的偶数数量打印出来,代码如下:
#include <vector>
#include <algorithm>
class Count
{public:Count(int &val):num(val){}void operator()(int val){if(!(val & 1)){++num;}}
private:int #
};int main()
{std::vector<int> v = {1,2,3,4,5,6,7};int count = 0;for_each(v.begin(),v.end(),Count(count));std::cout << count << endl;return 0;
}
如果使用lambda表达式,就可以简化一下,真正使用闭包概念来替换这里的仿函数。
#include <vector>
#include <algorithm>int main()
{std::vector<int> v = {1,2,3,4,5,6,7};int count = 0;for_each(v.begin(),v.end(),[&count](int val){if(!(val & 1)){++count;}});std::cout << count << endl;return 0;
}
lambda表达式的价值在于,就地封装短小的功能闭包,方便地表达出我们希望执行的具体操作,并让上下文结合的更加紧,代码更加简洁,更灵活,也提高了开发效率及可维护性。
参考:《深入应用C++11》
C++11:lambda表达式详细介绍相关推荐
- C++ 11 Lambda表达式
C++ 11中的Lambda表达式用于定义并创建匿名的函数对象,以简化编程工作.Lambda的语法形式如下: [函数对象参数] (操作符重载函数参数) mutable或ex ...
- C++11 lambda表达式与函数对象
C++ lambda表达式与函数对象 lambda表达式是C++11中引入的一项新技术,利用lambda表达式可以编写内嵌的匿名函数,用以替换独立函数或者函数对象,并且使代码更可读.但是从本质上来讲, ...
- C++11——lambda表达式
目录 前言 一.lambda表达式用法 二.lambda表达式语法 三.lambda表达式的原理 前言 在显示生活中,我们在用手机购物时.总是可以在页面上看到下面这样的选项. 我们知道底层这是通过排序 ...
- C++11:Lambda表达式(匿名函数)理解
C++在C11标准中引入了匿名函数,即没有名字的临时函数,又称之为lambda表达式.lambda表达式 实质上是创建一个匿名函数/对象.即你可以理解为(Lambda 表达式实际上是一个函数,只是它没 ...
- C++11 Lambda表达式(匿名函数)详解
使用 STL 时,往往会大量用到函数对象,为此要编写很多函数对象类.有的函数对象类只用来定义了一个对象,而且这个对象也只使用了一次,编写这样的函数对象类就有点浪费. 而且,定义函数对象类的地方和使用函 ...
- C++11 lambda表达式、function类模板、bind函数适配器
文章目录 lambda表达式 lambda表达式的语法 lambda表达式的原理 function模板 function与重载函数 bind函数适配器 lambda表达式 当我们在写代码的时候如果经常 ...
- [C++11]lambda表达式语法
代码如下: #include <iostream> using namespace std;void func(int x, int y) {int a;int b;[=]() {int ...
- C++11 Lambda表达式
1.简介 1.1定义 C++11新增了很多特性,Lambda表达式(Lambda expression)就是其中之一,很多语言都提供了 Lambda 表达式,如 Python,Java ,C#等.本质 ...
- Java8新特性----Lambda表达式详细探讨
Java8新特性 Lambda表达式 入门演示 案例1 如何解决 cannot be cast to java.lang.Comparable问题? 案例2 优化方式一 : 策略设计模式 优化方式二: ...
- Linq、Lambda表达式详细总结(转)
(一)输入参数 在Lambda表达式中,输入参数是Lambda运算符的 左边部分.它包含参数的数量可以为0.1或者多个.只有当输入参数为1时,Lambda表达式左边的一对小括弧才可以省略.输入参数的数 ...
最新文章
- 从智能交通到智能能源:智慧城市在7个方面的应用实践
- 黑客道诠释完整版1-7
- springboot教程(三)
- RNQOJ 98 逃亡的准备
- 【Vegas原创】X connection to localhost:11.0 broken (explicit kill or server shutdown)解决方法...
- 大型网站电商网站架构案例和技术架构的示例
- 2020年中国无人经济市场研究报告
- java quartz DateBuilder
- Qfarm /load命令
- 叮——这有一打让你666的测试终极资料包,请查收!
- springcloud分布式事务处理方案
- python 比较两文件夹的内容,具有通用性。
- 百度糯米android面试题,【百度百度糯米队列百度编程基础数据安全面试题】面试问题:编程:使用C实… - 看准网...
- 打造地图拼接利器(五)地图采集与拼接
- Python网络爬虫入门篇
- 【零基础学Python】Day12 Python循环语句
- Arduino 和LCD1602液晶屏 I2C接口实验
- RAW图像详解及使用Python读取raw格式图像并显示
- 申请CVE的姿势总结
- 五子连珠游戏使用手册