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