33-3435 lambda表达式-(易学和易用性)

一种匿名表达式
书写的时候不需要指定名字

1. 基本用法

lambda表达式是C++11最重要也是最常用的特性之一,这是现代编程语言的一个特点,lambda表达式有如下的一些优点:

声明式的编程风格:就地匿名定义目标函数或函数对象,不需要额外写一个命名函数或函数对象。
简洁:避免了代码膨胀和功能分散,让开发更加高效。
在需要的时间和地点实现功能闭包,使程序更加灵活。

lambda表达式定义了一个匿名函数,并且可以捕获一定范围内的变量。lambda表达式的语法形式简单归纳如下:

[capture](params) opt -> ret {body;};

其中capture是捕获列表,params是参数列表,opt是函数选项,ret是返回值类型,body是函数体。

capture代表捕捉方式,有多种

1. 变量参数

1.捕获列表[]: 捕获一定范围内的变量

2.参数列表(): 和普通函数的参数列表一样,如果没有参数参数列表可以省略不写。
    auto f = [](){return 1;}    // 没有参数, 参数列表为空
    auto f = []{return 1;}      // 没有参数, 参数列表省略不写
3.opt 选项, 不需要可以省略
    mutable: 可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)
    exception: 指定函数抛出的异常,如抛出整数类型的异常,可以使用throw();

4.返回值类型:在C++11中,lambda表达式的返回值是通过返回值后置语法来定义的。

5.函数体:函数的实现,这部分不能省略,但函数体可以为空。

2. 捕获列表 - 捕捉方式

lambda表达式的捕获列表可以捕获一定范围内的变量,具体使用方式如下:

    [] - 不捕捉任何变量
    [&] - 捕获外部作用域中所有变量, 并作为引用在函数体内使用 (按引用捕获)
    [=] - 捕获外部作用域中所有变量, 并作为副本在函数体内使用 (按值捕获)  ----就是指值拷贝
        **拷贝的副本在匿名函数体内部是只读的**,想要修改需要添加关键字mutable
        
    [=, &foo] - 按值捕获外部作用域中所有变量, 并按照引用捕获外部变量 foo
    (除了foo这个变量以外的都是值拷贝,foo是引用)
    [bar] - 按值捕获 bar 变量, 同时不捕获其他变量
    [&bar] - 按引用捕获 bar 变量, 同时不捕获其他变量
    [this] - 捕获当前类中的this指针
        让lambda表达式拥有和当前类成员函数同样的访问权限
        如果已经使用了 & 或者 =, 默认添加此选项

下面通过一个例子,看一下初始化列表的具体用法:

#include <iostream>
#include <functional>
using namespace std;

class Test
{
public:
    void output(int x, int y)
    {
        auto x1 = [] {return m_number; };                      // error
        auto x2 = [=] {return m_number + x + y; };             // ok
        auto x3 = [&] {return m_number + x + y; };             // ok
        auto x4 = [this] {return m_number; };                  // ok
        auto x5 = [this] {return m_number + x + y; };          // error
        auto x6 = [this, x, y] {return m_number + x + y; };    // ok
        auto x7 = [this] {return m_number++; };                // ok
    }
    int m_number = 100;
};

    x1:错误,没有捕获外部变量,不能使用类成员 m_number
    x2:正确,以值拷贝的方式捕获所有外部变量
    x3:正确,以引用的方式捕获所有外部变量
    x4:正确,捕获this指针,可访问对象内部成员
    x5:错误,捕获this指针,可访问类内部成员,没有捕获到变量x,y,因此不能访问。
    x6:正确,捕获this指针,x,y
    x7:正确,捕获this指针,并且可以修改对象内部变量的值

int main(void)
{
    int a = 10, b = 20;
    auto f1 = [] {return a; };                        // error
    auto f2 = [&] {return a++; };                     // ok
    auto f3 = [=] {return a; };                       // ok
    auto f4 = [=] {return a++; };                     // error
    auto f5 = [a] {return a + b; };                   // error
    auto f6 = [a, &b] {return a + (b++); };           // ok
    auto f7 = [=, &b] {return a + (b++); };           // ok

    return 0;
}
f1:错误,没有捕获外部变量,因此无法访问变量 a
f2:正确,使用引用的方式捕获外部变量,可读写
f3:正确,使用值拷贝的方式捕获外部变量,可读
f4:错误,使用值拷贝的方式捕获外部变量,可读不能写
f5:错误,使用拷贝的方式捕获了外部变量a,没有捕获外部变量b,因此无法访问变量b
f6:正确,使用拷贝的方式捕获了外部变量a,只读,使用引用的方式捕获外部变量b,可读写
f7:正确,使用值拷贝的方式捕获所有外部变量以及b的引用,b可读写,其他只读

在匿名函数内部,需要通过lambda表达式的捕获列表控制如何捕获外部变量,以及访问哪些变量。默认状态下lambda表达式无法修改通过复制方式捕获外部变量,如果希望修改这些外部变量,需要通过引用的方式进行捕获。

使用

// lambda 的值拷贝问题, 想要修改值拷贝需要关键字mutable,

//那对于捕获列表用的是引用方式,不加关键字可以直接修改吗

//定义了匿名函数lambda函数后,调用的方式。 有()才表示调用

[[cplus11_code/src/lambda_use.cpp|lambda_use]]

3. 返回值

很多时候,lambda表达式的返回值是非常明显的(依靠自动推导),因此在C++11中允许省略lambda表达式的返回值。

// 完整的lambda表达式定义
auto f = [](int a) -> int
{
    return a+10;  
};

// 忽略返回值的lambda表达式定义
auto f = [](int a)
{
    return a+10;  
};

一般情况下,不指定lambda表达式的返回值,编译器会根据return语句自动推导返回值的类型,但需要注意的是labmda表达式不能通过列表初始化自动推导出返回值类型。

// ok,可以自动推导出返回值类型
auto f = [](int i)
{
    return i;
}

// error,不能推导出返回值类型
auto f1 = []()
{
    return {1, 2};  // 基于列表初始化推导返回值,错误
}

4. 函数本质

本质是仿函数

使用lambda表达式捕获列表捕获外部变量,如果希望去修改按值捕获的外部变量,那么应该如何处理呢?这就需要使用mutable选项,被mutable修改是lambda表达式就算没有参数也要写明参数列表,并且可以去掉按值捕获的外部变量的只读(const)属性。

int a = 0;
auto f1 = [=] {return a++; };              // error, 按值捕获外部变量, a是只读的
auto f2 = [=]()mutable {return a++; };     // ok

最后再剖析一下为什么通过值拷贝的方式捕获的外部变量是只读的:

lambda表达式的类型在C++11中会被看做是一个带operator()的类,即仿函数。
按照C++标准,lambda表达式的operator()默认是const的,一个const成员函数是无法修改成员变量值的。

mutable选项的作用就在于取消operator()的const属性。

因为lambda表达式在C++中会被看做是一个仿函数,因此可以使用std::function和std::bind来存储和操作lambda表达式:

#include <iostream>
#include <functional>
using namespace std;

int main(void)
{
    // 包装可调用函数
    std::function<int(int)> f1 = [](int a) {return a; };
    // 绑定可调用函数
    std::function<int(int)> f2 = bind([](int a) {return a; }, placeholders::_1);

    // 函数调用
    cout << f1(100) << endl;
    cout << f2(200) << endl;
    return 0;
}

对于没有捕获任何变量的lambda表达式,还可以转换成一个普通的函数指针:

using func_ptr = int(*)(int);
// 没有捕获任何外部变量的匿名函数
func_ptr f = [](int a)
{
    return a;  
};
// 函数调用
f(1314);

lambda返回值到底是什么类型? 是推出来的不一定的吧?

当捕获列表中有变量,返回值转换成普通的函数指针能是 void(* )

引用

lamda表达式

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,125评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,293评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,054评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,077评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,096评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,062评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,988评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,817评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,266评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,486评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,646评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,375评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,974评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,621评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,642评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,538评论 2 352

推荐阅读更多精彩内容