C++11 -- 匿名函数(lambda 表达式)

0. 一道题目引入

关于sb力扣定义外部函数和变量报错这件事

最初我定义了一个 \(cmp\) 函数用来对 \(vector\) 排序,和一个全局变量 \(unordered\_map\) 用来记录元素个数。
但是 \(sb\) 力扣报我错,我也不知道为啥!于是我查看了官方题解 我是sb,这种题还要看题解,发现官方题解是这样定义自己的 \(cmp\) 函数的:

sort(nums.begin(), nums.end(), [&](int a, int b) ->bool{
    if(cnt[a] == cnt[b])    return a > b;
    return cnt[a] < cnt[b];
});

没错!它直接在 \(sort\) 里面定义了一个 “没有函数名字” 的 \(cmp\) 函数。
把 "\(cmp\) 函数" 单独拿出来就是:

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

于是我就上网 \(goole\) 了一下这个语法,才知道这是 \(C ++ 11\) 的新特性:\(lambda\) 函数,又叫做匿名函数,\(lambda\) 表达式。

这是AC代码:

class Solution {
public:
    vector<int> frequencySort(vector<int>& nums) {
        int n = nums.size();
        if(!n)  return nums;
        unordered_map<int, int> cnt;
        for(auto &x : nums)
        {
            cnt[x] ++ ;
        }
        
        sort(nums.begin(), nums.end(), [&](int a, int b) ->bool{
            if(cnt[a] == cnt[b])    return a > b;
            return cnt[a] < cnt[b];
        });
        return nums;
    }
};

1. lambda 表达式介绍

先上语法:

auto val = [captures](args) -> return_type {
	...
}

val : 对象名
[captures] : 捕获作用域内变量
(args) : 匿名函数的参数列表
-> return_type : 函数返回值类型
          如果 lambda 函数没有传回值(例如 void),其返回类型可被完全忽略。
{...} : 函数实现体

一个简单的例子:通过匿名函数实现 x+y

int main()
{
    int x = 1, y = 2;
    auto f = [&]() -> int {
	    return x + y;
    };
    
    cout << f() << endl;
    return 0;
}

我个人认为,这个语法更应该叫做 \(lambda\) 表达式,而“匿名函数”不是非常合适,因此它并是真的完全“匿名”,它也是有名字的。
在上面的例子总,我们定义了一个 “函数” \(f\)[应该是个函数吧,毕竟需要通过()调用]\(f\) 不就是它的名字吗?
但为啥还要说“匿名”呢,我觉得,\(f\) 并不能理解为一个函数的名字,它应该理解为一个“对象”的名字,\(f\) 是一个“对象”,只不过这个对象的内容不是一个 \(int\),也不是一个 \(char\),而是一个“没有名字的函数”罢了。

2. lambda 表达式的闭包

\(Lambda\) 表达式内可以访问当前作用域的变量,这是 \(Lambda\) 表达式的闭包(\(Closure\))行为。 与 \(JavaScript\) 闭包不同,\(C++\) 变量传递有传值和传引用的区别。可以通过前面的 \([]\) 来指定:

[]      // 沒有定义任何变量。使用未定义变量会引发错误(即不能引用函数自己定义的参数之外的变量)。
[x, &y] // x以传值方式传入(默认),y以引用方式传入。
[&]     // 任何被使用到的外部变量都隐式地以引用方式加以引用。
[=]     // 任何被使用到的外部变量都隐式地以传值方式加以引用。
[&, x]  // x显式地以传值方式加以引用。其余变量以引用方式加以引用。
[=, &z] // z显式地以引用方式加以引用。其余变量以传值方式加以引用。

另外有一点需要注意。对于 \([=]\)\([&]\) 的形式,\(lambda\) 表达式可以直接使用 \(this\) 指针。但是,对于 \([]\) 的形式,如果要使用 \(this\) 指针,必须显式传入:

[this]() { this->someFunc(); }();

更多关于语法的细节

3. lambda 值引用 map/unordered_map 的一个坑

简单来说,在 \(lambda\) 的捕获列表里面捕获 \(map/unordered\_map\) 要么是引用捕获,要么使用 \(at()\) 来获取容器内的元素。
这是因此当捕获为 \(map/unordered\_map\) 的值时,\(lambda\) 表达式会默认转换为 \(const map/const unordered\map\)
之所以会发生这种转换,是因为 \(map/unordered\_map\) 的一个特性:当我们引用一个关键字 \(key\) 的使用,如果这个关键字不存在则进行插入,也就说会修改容器。
另外,注意使用 \(at()\) 去获取容器内元素,如果 \(key\) 不存在,会报错:\(out\_of\_range\),因为我们引用了一个容器中不存在的 \(key\)
这也就证明了使用 \(at()\) 不会插入关键字,也就不会修改容器。

再来测试一下这个特性:当我们引用一个关键字 \(key\) 的使用,如果这个关键字不存在则进行插入,也就说会修改容器。

int main()
{
    unordered_map<int,int> m;
    m[1] = 1;
    cout << m.size() << endl; // output:1
    int x = m[2];
    cout << m.size() << endl; // output:2
    
    return 0;
}

可以发现,我们仅仅只是引用了 \(m[2]\),但容器的 \(size\) 多了一个。

具体的可以看一个博客:C++11 lambda表达式不能捕获map/unordered_map值

posted @ 2022-09-19 09:05  光風霽月  阅读(111)  评论(0编辑  收藏  举报