C++中的Lambda表达式

作者: kophy | 来源:发表于2016-07-14 16:55 被阅读634次

    1. 引言

    最近刷Leetcode经常看discuss,通常是佩服别人算法漂亮。但做第373题Find K Pairs with Smallest Sums时,被这个答案语言上的优雅震惊了。代码片段如下:

    auto cmp = [&nums1, &nums2](pair<int, int> a, pair<int, int> b) { 
        return nums1[a.first] + nums2[a.second] > 
                 nums1[b.first] + nums2[b.second];
    };
    priority_queue<pair<int, int>, vector<pair<int, int>>, 
        decltype(cmp)> min_heap(cmp);
    

    通过使用auto、decltype和Lambda表达式等C++ 11新特性,大大压缩了代码量,降低了编写和理解难度,所以决定花时间好好研究一下Lambda表达式。

    2. 什么是Lambda表达式?

    查Lambda表达式资料时很容易被函数闭包、Lambda演算、形式系统这些深奥名词淹没而放弃学习,其实Lambda表达式就是匿名函数(annoymous function)——允许我们使用一个函数,但不需要给这个函数起名字。还是有点难懂?没关系,看完下面这个例子就清楚了。

    int main() {
        vector<int> data;
        for (int i = 0; i < 10; ++i)
            data.push_back(i);
        sort(data.begin(), data.end());
        for (int i = 0; i < data.size(); ++i)
            cout << data[i] << endl;
        return 0;
    }
    

    这段代码的含义是初始化data,对data里的元素排序后输出。algorithm库里的sort默认采用升序,想用倒序怎么办呢?对,自己定义一个比较函数cmp,作为参数传给sort:

    bool cmp(int &a, int &b);
    
    int main() {
        vector<int> data;
        for (int i = 0; i < 10; ++i)
            data.push_back(i);
        sort(data.begin(), data.end(), cmp);
        for (int i = 0; i < data.size(); ++i)
            cout << data[i] << endl;
        return 0;
    }
    
    bool cmp(int &a, int &b) {
        return a > b;
    }
    

    在定义了函数bool cmp(int &a, int &b)后,相同的函数签名变得不可用,我不能再用bool cmp(int &a, int &b)这个签名定义一个别的比较函数:

    bool cmp(int &a, int &b) {
        return (a % 3) > (b % 3);
    }
    

    问题是排序这件事通常不会反复做,那么用cmp比较大小是个一次性的临时需求,排序之后它的任务就已经完成了。所以给它特意起个名字污染命名空间似乎有点不太合算,可不可以不给它起cmp这个名字,又能使用比较大小的功能呢?答案当然是可以的,通过与cmp等价的匿名函数:

    int main() {
        vector<int> data;
        for (int i = 0; i < 10; ++i)
            data.push_back(i);
        sort(data.begin(), data.end(), [](int &a, int &b)->bool {
             return a > b;
             });
        for (int i = 0; i < data.size(); ++i)
            cout << data[i] << endl;
        return 0;
    }
    

    其中

    [](int &a, int &b)->bool {
             return a > b;
    }
    

    就是传说中的Lambda表达式了,先不管[]部分,(int &a, int &b)->bool表示接受两个int引用类型的参数,返回值是bool类型,{}里是函数体,是不是很简单?

    关于Lambda表达式的意义可以参考知乎上的提问,我自己的理解是Lambda表达式实现了函数名字和功能的分离,允许在不起名字的情况下定义和使用功能。举个生活中的例子,点外卖时我们关心的只是外卖小哥送货上门的“功能”,不需要特意记住他的“名字”。

    名字嘛,无所谓

    3. 怎么用Lambda表达式?

    Lambda表达式的具体语法可以参考cppreference上的Guide。一个Lambda表达式的形式通常为:

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

    其中( params ) -> ret定义了这个匿名函数的参数和返回类型, { body }定义了这个匿名函数的功能,捕捉列表[ capture-list ]是做什么的呢?概括地讲,它使这个匿名函数可以访问外部(父作用域)变量。

    还是举个例子:

    int main() {
        int a = 0;
        auto f = ([]()->void {cout << a << endl;});
        f();
        return 0;
    }
    

    这段代码的含义是定义了一个匿名函数赋给f并运行f,但编译时会报错:
    error: 'a' is not captured

    因为变量a在函数f的外部,想要访问a的话需要把它加到[ capture-list ]里,也就是:

    int main() {
        int a = 0;
        auto f = ([a]()->void {cout << a << endl;});
        f();
        return 0;
    }
    

    捕捉方式有按值和按引用两种。比如[a, &b]表示按值捕捉a,按引用捕捉b;[&, a]表示按引用捕捉所有父作用域变量,除了a按值捕捉,[=,&b]表示按值捕捉所有父作用域变量,除了b按引用捕捉。

    假设有数组data,想生成只保留data中偶数的新数组result,可以用:

    int main() {
        vector<int> data;
        vector<int> result;
        for (int i = 0; i < 10; ++i)
            data.push_back(i);
        for_each(data.begin(), data.end(), [&result](int &elem){
                    if (elem % 2 == 0)
                        result.push_back(elem);
                 });
        for_each(result.begin(), result.end(), [](int &elem){
                 cout << elem << endl;
                 });
        return 0;
    }
    

    4. 参考资料

    相关文章

      网友评论

      本文标题:C++中的Lambda表达式

      本文链接:https://www.haomeiwen.com/subject/gwbejttx.html