Lambda表达式

发布时间 2023-12-04 17:18:44作者: Beasts777

文章参考:爱编程的大丙 (subingwen.cn)

1. 概述

Lambda表达式是现代编程语言的一个特点,他有如下优点:

  • 声明式的编程风格:就地匿名定义目标函数或函数对象,不需要额外写一个命名函数或函数对象。
  • 简洁:避免了代码膨胀和功能分散,然开发更加高效。
  • 灵活:在需要的时间和地点实现功能闭包,使得程序更加灵活。

语法:

[capture](params) opt -> ret {body;};
  • capture:捕获列表,用于声明要捕获变量的范围。
  • params:参数列表,和普通的参数列表一样,没有就空着。
  • opt:功能选项,可以省略,包括两种:
    • mutable:可以修改按值传进来的拷贝。使用时有两点要注意:
      • 修改的是传进来的拷贝,外面的变量本身没有被修改。
      • 使用时前面必须加上参数列表,即使没有参数页要写。
    • exception:指定函数抛出的异常。
  • 返回值类型:在C++11中,Lambda表达式的返回值是通过返回值后置语法来定义的。(它和->符号同生共死)
  • 函数体:函数实现。

2. 捕获列表

主要用于指定捕获变量的范围。具体方式如下:

  • []:不捕获任何变量。
  • [=]:按值捕获。即捕获外界作用域的所有变量,并作为副本在函数体内使用。(拷贝的副本在匿名函数内部默认是只读的,除非在opt中指定muteale,但即使修改,修改的也只是副本,外界变量本身并没有变。)
  • [&]:按引用捕获。即捕获外界作用域的所有变量,并作为引用在函数体内使用。(可以修改外部变量的值)
  • [=, &temp]:按值捕获外界作用域所有的变量,同时按引用捕获temp变量。
  • [temp]:按值捕获temp变量,不捕获其余变量。
  • [&temp]:按引用捕获temp变量,不捕获其余变量。
  • [this]:捕获当前类中的this指针。
    • 让Lambda和当前类的成员函数拥有同样的访问权限。一般来说就是为了访问成员变量。
    • 可以通过this指针修改外部变量的值。
    • 如果已经使用了&/=,那么默认添加该选项。

EG:

#include <iostream>
using namespace std;

class Test
{
private: 
    int num;
public:
    void output(int x, int y)
    {
        auto x1 = [] {return num; };                      // error
        auto x2 = [=] {return num + x + y; };             // ok
        auto x3 = [&] {return num + x + y; };             // ok
        auto x4 = [this] {return num; };                  // ok
        auto x5 = [this] {return num + x + y; };          // error
        auto x6 = [this, x, y] {return num + x + y; };    // ok
        auto x7 = [this] {return num++; };                // ok
    }
};
  • x1:错误。没有捕获任何变量,因此无法访问num。
  • x2:正确。按值捕获外部作用域的变量(注意:默认是进行拷贝)。
  • x3:正确。按引用捕获外部作用于的变量。
  • x4:正确。获取this指针,可访问类内部对象。
  • x5:错误。获取this指针,可访问类内部对象,但无法访问函数的参数列表。
  • x6:正确。获取this指针,可访问类内部对象。同时按值获取xy变量。
  • x7:正确。可以通过this指针修改外部变量的值。

3. 返回值

大多数时候,函数的返回值类型非常明显,因此C++11允许在Lambda中忽略函数的返回值类型。

  • 完整的Lambda表达式:

    auto f = [](int a) -> int {return a;};
    
  • 忽略返回值类型:

    auto f = [](int a)  {return a;};
    

注意:

一般c情况下Lambda可以自动推导出返回值类型,但无法通过列表初始化自动推导出返回值类型。

auto f = []{
    return {1, 2};		// 这样会发生错误
};

4. 函数本质

使用lambda表达式捕获列表捕获外部变量,如果希望去修改按值捕获的外部变量,那么应该如何处理呢?这就需要使用mutable选项,被mutable修改是lambda表达式就算没有参数也要写明参数列表,并且可以去掉按值捕获的外部变量的只读(const)属性。

int a = 0;
auto f1 = [=] {return a++; };              // error, 按值捕获外部变量, a是只读的
auto f2 = [=]()mutable {return a++; };     // ok

为什么按值拷贝得到的变量默认是只读的:

  • lambda表达式的类型在C++11中会被看做是一个带operator()的类,即仿函数。
  • 按照C++标准,lambda表达式的operator()默认是const的,一个const成员函数是无法修改成员变量值的。

mutable选项的作用就在于取消operator()的const属性。

因为lambda表达式在C++中会被看做是一个仿函数,因此可以使用std::functionstd::bind来存储和操作lambda表达式:

#include <iostream>
#include <functional>
using namespace std;

int main(void)
{
    // 包装可调用函数
    std::function<int(int)> f1 = [](int a) {return a; };
    // 绑定可调用函数
    std::function<int(int)> f2 = bind([](int a) {return a; }, placeholders::_1);

    // 函数调用
    cout << f1(100) << endl;
    cout << f2(200) << endl;
    return 0;
}

对于没有捕获任何变量的Lambda表达式,还可以转化为一个普通的指针函数:

using func_ptr = int(*)(int);
func_ptr p1 = [](int a) {
    return a;
};
p1(123);