C++中的Lambda表达式

一.前言

最近博主在LeetCode上刷题时意中碰到435. 无重叠区间这道题,题目:给定一个区间的集合intervals ,其中 intervals[i] = [starti, endi] 。返回需要移除区间的最小数量,使剩余区间互不重叠 。乍一看这不就是活动安排问题嘛,如何安排活动使得不冲突的活动数量最多,下面是博主最初提交的题解:

bool compare(vector<int> s1, vector<int> s2) {
    return s1[1] < s2[1];
}

class Solution {
public:
    int eraseOverlapIntervals(vector<vector<int>>& intervals) {
        sort(intervals.begin(), intervals.end(), compare);
        int n = intervals.size();
        int right = intervals[0][1];
        int c = 1;
        for (int i = 1; i < n; i++) {
            if (intervals[i][0] >= right) {
                right = intervals[i][1];
                c++;
            }
        }
        return n - c;
    }
};

提交结果是timeout,按道理说这时间复杂度应该可以AC了,在我想了一会儿后,决定去看看官方的题解。结果发现题解中的贪心法思路跟我一致,只不过官方题解中并没有像我一样定义一个compare函数,而是采用lambda表达式。我按官方的思路也改成了lambda表达式,结果AC了,下面是对sort函数的修改:

sort(intervals.begin(), intervals.end(), [](const auto& u, const auto& v) {
    return u[1] < v[1];
});

这一下子就让我对lambda表达式的兴趣上来了,貌似使用Lambda表达式的执行效率更高,虽然在其它编程语言如python中确实经常用它,但是C++中却用的很少。为此,我专门去学习了一下C++中的Lambda表达式,于是便有了此文。

二.Lambda表达式详解

2.1 Lambda表达式的形式

Lambda表达式长啥样呢?其语法形式如下所示:

C++中的Lambda表达式_第1张图片

Lambda表达式各部分说明如下:

部分 说明
1 捕获列表,用来捕获Lambda子句的上下文变量,然后在Lambda的函数体中使用
2 形参列表,与函数的形参列表相似,C++14后允许使用auto作为类型说明符(可选参数)
3 用来说明是否可以修改捕获的变量(可选参数)
4 异常规范(可选参数)
5 返回类型,若没有指定会自动推断Lambda表达式的返回类型(可选参数)
6 Lambda正文,即函数体

捕获列表

捕获列表是Lambda表达式的开头,通过它可以指定捕获哪些变量以及是按值访问变量和按引用访问变量。在捕获列表中,以&为前缀的变量是按引用访问,而没有则是按值访问。为更好的理解,下面是一个栗子:

int a = 2, b = 3;
auto fun = [a, &b]() mutable {
    a = 4;
    b = 5;
    return a + b;
};
cout << a << " " << b << " " << fun() << endl;
// 2 5 9

在该栗子中,Lambda表达式的捕获列表中a是按值访问,b是按引用访问,所以在Lambda表达式函数体内修改a的值,不影响外部的a的值,而由于b是按引用访问,所以会对外部的b的值进行修改。

在Lambda表达式中,空捕获列表表示Lambda表达式的函数体不能访问上下文变量,也就是说如下形式是不行的:

int a = 2, b = 3;
auto fun = []() mutable {
    a = 4;
    b = 5;
    return a + b;
};
//VS中报的错误:E1735	封闭函数局部变量不能在 lambda 体中引用,除非其位于捕获列表中

另外,在Lambda表达式的捕获列表中,[&]表示所有变量都是按引用捕获的,[=]表示所有变量都是按值捕获的

// 变量按引用捕获
int a = 2, b = 3;
auto fun = [&]() mutable {
    a = 4;
    b = 5;
    return a + b;
};
cout << a << " " << b << " " << fun() << endl;
// 4 5 9

// 变量按值捕获
int a = 2, b = 3;
auto fun = [=]() mutable {
    a = 4;
    b = 5;
    return a + b;
};
cout << a << " " << b << " " << fun() << endl;
// 2 3 9

在使用[&][=]时,还可以为特定的变量显式指定相反的模式(例如默认按值访问时,可以为某个变量指定按引用访问):

// 默认按值访问的同时设置变量b可以按引用访问
auto fun = [=, &b]() mutable {
    a = 4;
    b = 5;
    return a + b;
};
cout << a << " " << b << " " << fun() << endl;
// 2 5 9

注意若捕获列表指定为[&]时,则在捕获列表中不能再显式指定某个变量为按引用访问,例如[&, &b]这种形式会引发报错。同理,[=]也类似。

2.2 为何使用Lambda表达式?

参考《C++ Primer Plus》关于Lambda表达式的介绍,再结合个人理解,我认为Lambda有如下突出优势:

  • 距离:Lambda的定义和使用在相同的地方,这样方便查看和修改。
  • 效率:Lambda表达式的执行效率要比函数指针高,这也解释了为什么会出现前言中的博主碰到的那种情况的原因。
  • 功能:Lambda表达式可以访问作用域内的任何动态变量。

为了特意说明Lambda的效率问题,下面创建了包含10万个元素的数组,然后分别采用Lambda表达式和函数调用的形式来传递sort()方法的比较指导原则,然后分别统计对应的执行时间:

#include 
#include 
#include 
#include 
#include

using namespace std;

bool compare(vector<int> s1, vector<int> s2) {
    return s1[1] < s2[1];
}

int main() {
    vector<vector<int>> nums(100000);
    generate(nums.begin(), nums.end(), []() {
        vector<int> s = { rand() % 1000, rand() % 1000 };
        return s;
        });
    vector<vector<int>> nums1 = nums; // 拷贝一份nums
    
    DWORD start_time = GetTickCount();
    sort(nums.begin(), nums.end(), compare);
    DWORD end_time = GetTickCount();
    cout << "普通函数的执行时长:" << (end_time - start_time) << " ms" << endl;
    
    start_time = GetTickCount();
    sort(nums1.begin(), nums1.end(), [](const auto& u, const auto& v) {
        return u[1] < v[1];
        });
    end_time = GetTickCount();
    cout << "Lambda的执行时长:" << (end_time - start_time) << " ms" << endl;

    // 普通函数的执行时长:3641 ms
    // Lambda的执行时长:156 ms

	return 0;
}

从结果可以看出Lambda表达式要比调用函数快得多。因此,推荐大家在使用sort()方法进行排序的时候使用Lambda表达式。

三.结语

参数资料:

  • 《C++ Primer Plus》

以上便是本文的全部内容,要是觉得不错的话就点个赞或关注一下博主吧,你们的支持是博主继续创作的不解动力,当然若是有任何问题也敬请批评指正!!!

你可能感兴趣的:(C/C++,c++,贪心算法,算法)