C++Lambda表达式

函数对象是C++中以参数形式传递函数的一个很好的方法,我们将函数包装成类,并且利用()运算符重载实现。
typedef class function
{
public:
    void operator()(double x) 
    {
        cout << x << endl;
    }
} FUNCTION;

function是一个类,我们可以实例化一个对象function func;,然后通过func(3.14)的方式来调用这个类的成员函数,如果某个函数需要这个函数作为回调函数,则可以将这个function类的对象传入即可

因为这是一个类的定义,因此我们完全可以在其中定义一些包含额外信息的成员和一些构造函数,让这个函数对象可以做更多不同的可定制的任务,最终的行为实际上只是调用了这个()运算符重载函数。这种做法比C++函数指针要容易理解得多,也不容易写错。

C++对于变量声明期的控制在新标准中完全向前兼容,也就是局部变量一定在退出代码块时被销毁,而不是观察其是否被引用。因此,尽管C++的Lambda表达式中允许引用其代码上下文中的值,但是实际上并不能够保证引用的对象一定没有被销毁

C++对于变量声明期的控制在新标准中完全向前兼容,也就是局部变量一定在退出代码块时被销毁,而不是观察其是否被引用。因此,尽管C++的Lambda表达式中允许引用其代码上下文中的值,但是实际上并不能够保证引用的对象一定没有被销毁


Lambda表达式的基本语法是:
[外部变量访问方式说明符] mutable (参数表) -> 返回值类型
{
   语句块
}
//mutable参数根据需要选择
[=] (int x, int y) -> bool {return x%10 < y%10; }
image.png

“外部变量访问方式说明符”可以是=或&,表示{}中用到的、定义在{}外面的变量在{}中是否允许被改变。=表示不允许,&表示允许。当然,在{}中也可以不使用定义在外面的变量。“-> 返回值类型”可以省略。
=表示值传递,&表示引用传递,例如,&s就表示s变量采用引用传递,不同的说明项之间用逗号分隔,可以为空,但是方括号不能够省略。第一项可以是单独的一个=或者&,表示,所有上下文变量若无特殊说明一律采用值传递/引用传递,什么都不写默认为值传递。

Lambda表达式和TR1标准对应的 function<返回类型 (参数表)>对象 是可以互相类型转换的,这样,我们也可以将Lambda表达式作为参数进行传递,也可以作为返回值返回。

#include <iostream>
#include <string>
#include <functional> //这是TR1的头文件,定义了function类模板
using namespace std;

typedef class hello {
public:
    void operator()(double x) {
        cout << x << endl;
    }
} hello; //函数对象的定义,也是非常常用的回调函数实现方法

void callhello(string s, hello func) {
    cout << s;
    func(3.14);
} //一个普通的函数

void callhello(string s, const function<void (double x)>& func) {
    cout << s;
    func(3.14);
} //这个函数会接受一个字符串和一个Lambda表达式作为参数

void callhello(string s, double d) {
    [=] (double x) {
        cout << s << x << endl;
    }(d);
} //这个函数体内定义了一个Lambda表达式并立即调用

function<void (double x)> returnLambda(string s) {
    cout << s << endl;
    function<void (double x)> f = ([=/*这里必须使用值传递,因为s变量在returnLambda返回后就被销毁*/] (double x) {
        cout << s << x << endl;
    });
    s = "changed"; //这里对s的修改Lambda表达式是无法感知的,调用这句语句前s在Lambda表达式中的值已经确定了
    return f;
} //这个函数接受了一个值传递的字符串变量s,我们将Lambda表达式作为返回值返回

function<void (double x)> returnLambda2(string& s) {
    cout << s << endl;
    function<void (double x)> f = ([&s/*这里可以使用引用传递,因为s是引用方式传入的,不随函数返回而消亡*/] (double x) {
        cout << s << x << endl;
    });
    s = "changed"; //这里对s的修改Lambda表达式是可以感知的,因为s以引用方式参与到Lambda表达式上下文中
    return f;
} //这个函数接受了一个引用传递的字符串变量s,将Lambda表达式作为返回值返回


int main()
{
    hello h;
    callhello("hello:", h); //用函数对象的方式实现功能
    callhello("hello lambda:", -3.14); //这个函数体内定义了一个Lambda表达式并立即调用
    int temp = 6;
    callhello("hello lambda2:", [&] (double x) -> void {
        cout << x << endl;
        cout << temp++ << endl;
    }); //这个函数会接受一个字符串和一个Lambda表达式作为参数
    cout << temp << endl;

     function<void (double x)> f = returnLambda("lambda string"); //这个函数接受了一个值传递的字符串变量s,我们将Lambda表达式作为返回值返回
    f(3.3);
    string lambdastring2 = "lambda string2"; //这个变量在main函数返回时才被销毁

    f = returnLambda2(lambdastring2); //这个函数接受了一个引用传递的字符串变量s,将Lambda表达式作为返回值返回
    f(6.6);
     
    system("pause");
}


值捕获的坑:

关于值捕获要注意的地方是,与参数传值类似,值捕获的前提是变量可以拷贝,不同之处则在于,被捕获的变量在 lambda 表达式被创建时拷贝,而非调用时才拷贝,例如:

void main()
{
    int x = 10;
    auto val_lam = [=]() {return x + 1; };
    x = 20;
    printf("a is %d\n", val_lam());    //lambda函数的值为11,而不是21
}

我们看到lambda函数的值为11,而不是21。因为值捕获在创建时就传入了。
值捕获还有一个需要注意的地方,不容易理解,也是其特性,就是外部值的连续性:

void main()
{
    int x = 10;
    auto val_lam = [=]() mutable {x++; return x + 1; };
 
    printf("a is %d\n", val_lam());     //12 
    printf("a is %d\n", val_lam());     //13
    printf("a is %d\n", val_lam());     //14
    printf("x is %d\n", x);             //此时x还是10
}

看到没,多次调用lambda函数,发现其记住了外部变量的值。几年前学习到这个特性的时候,也是很不能理解,后来用多了lua的闭包函数和upvalue,我现在可以会心的一笑了。事实上利用这个特性可以实现高阶函数

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

推荐阅读更多精彩内容

  • C++ lambda表达式与函数对象 lambda表达式是C++11中引入的一项新技术,利用lambda表达式可以...
    小白将阅读 85,227评论 15 118
  • 什么是lambda表达式 lambda表达式是一个可调用的代码单元,我们可以理解为一个未命名的内联函数,当定义一个...
    土豆吞噬者阅读 863评论 0 0
  • 声明 本文内容来自微软 MVP solenovex 的视频教程——真会C#? - 第4章 委托、事件、Lambda...
    JeetChan阅读 759评论 0 3
  • 从** C#3.0开始,可以使用一种新的方法把实现代码赋予委托: Lambda表达式**。只要有委托参数类型的地方...
    天堂迈舞阅读 9,760评论 0 5
  • 我们可以向一个算法传递任何类别的可调用对象(callable object)。对于一个对象或个表达式,如果可以对其...
    赵者也阅读 298评论 0 0