C++ 11 之 Lambda 表达式

115 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 6 天,点击查看活动详情

C++ 11 引入了 Lambda,允许我们定义内联函数,它可以被用来当作一个参数或者本地对象

Lambda 改变了 C++ 标准库的使用方式, 一般我们使用 C++ 标准库会自己写一些准则,比如调用快速排序算法,但是具体的比较准则我们可以自己写(不写就用默认的),我们写的东西就是一些函数对象或者仿函数,而引入 C++ 11 之后,这个函数定义可以写成 Lambda 的形式,这就可以被定义在语句或者表达式里面。因此我们可以使用 Lambda 作为内联函数。

一、初识 ambda 表达式

最简单的 Lambda 表达式是无参的,形式如下

[]{
     std::cout << "hello lambda"  << std::endl;
}

上面的写法没有意义,如果要使用它,可以加 ()

[]{
     std::cout << "hello lambda"  << std::endl;
}()

image.png

但这样写看着就很没必要,所以我们通常都将它记成下面的样子,可复用,调用更为方便。

auto l = []{
     std::cout << "hello lambda"  << std::endl;
};

l();

二、Lambda 表达式完整形式

[capture] (parameters) mutable -> return-type {statement}

为了方便后文叙述,我们写成下面的形式

[...](...)[...](...) mutableoptmutable_{opt} throwSpecoptthrowSpec_{opt} ->retTypeoptretType_{opt} {...}\left\{...\right\}

  1. [...][...]

    • Lambda 导入器(introducer)
    • 可写入取用的外部的变量
      • 传值

        int x = 0;
        auto var =[x]{};//pass by value
        
      • 传引用

        int y = 42;
        auto var =[&y]{};//pass by reference
        
  2. (...)(...)

    • 参数
  3. mutableoptmutable_{opt}

    • [...][...] 中内容是否可以被改写
    • mutable 关键字 可改动的
  4. throwSpecoptthrowSpec_{opt}

    • 抛出异常
  5. ->retTypeoptretType_{opt}

    • 描述 Lambda 的返回类型
  6. {...}\left\{...\right\}

    • 函数本体

  • 3、4、5 是可选的,但是这三个中如果有一个存在,则参数项 (...)(...) 存在;三个都没有,也没有参数 (...)(...) 可以不写

例子1 对象按值传递,

int id = 0;
    auto f2 = [id]()mutable{
        std::cout << "id:" << id << std::endl;
        ++id;
    };
    id = 42;
    f2();
    f2();
    f2();
    std::cout<< id<< std::endl;
  • f 中最初的 id 记为 0,所以 f() 三次是 0、1、2;
  • 最终打印才是后来的赋值 42;
  • f 里面怎么变都不影响外面的 id;
  • Lambda 的类型是匿名的函数对象 anonymous function object(or functor)
class Functor
{
private:
    int id;
public:
    void operator()(){
        std::cout<<"id:"<<id<<std::endl;
        ++id;
    }
};
Function f;

思考:不写 mutable 里面的 id 不能 ++ 么?

例子1

int id = 0;
auto f = [id]()mutable{
    std::cout << "id:" << id << std::endl;
    ++id;
};
id = 42;
f2();
f2();
f2();
std::cout<< id<< std::endl;

结果:
id=0
id=1
id=2
42 例子2

int id = 0;
auto f = [&id](int parm){
    std::cout << "id:" << id << std::endl;
    ++id;
    ++param;
};
id = 42;
f(7);
f(7);
f(7);
std::cout<< id<< std::endl;

结果:
id=42
id=43
id=44
45 例子2

int id = 0;
auto f = [id](){
    std::cout << "id:" << id << std::endl;
    ++id;
};
id = 42;
f();
f();
f();
std::cout<< id<< std::endl;

上述写法错误!

总结

  • 传引用这个里面的 id 受外界影响,同时里面也会影响外面;
  • 按值传递,如果不加 mutable,这个外界的 id 就是只读的,不可以修改;
  • lambda 表达式函数本体里面可以声明变量,可以返回数值
int tobefound = 5;
auto lambda1 = [tobefound](int val){return val == tobefound;};

写成仿函数就是下面的类里的样子, 传递 lambda 的类型

class UnNamedLocalFunction
{
    int localVar;
public:
    UnNamedLocalFunction(int var):localVar(var){}
    bool operator()(int val)
    {
        return val == localVar;
    }
}

调用

UnNamedLocalFunction lambda2(tobefound);

bool b1 = lambda1(5);
bool b2 = lambda2(5);

可以在程序中一起看看结果。

三、lambda 表达式通过捕获列表捕获变量的范围说明

  • [] 不捕获任何变量。
  • [&] 捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获)。
  • [=] 捕获外部作用域中所有变量,并作为副本在函数体中使用(按值捕获)。
  • [=,&foo] 按值捕获外部作用域中所有变量,并按引用捕获 foo 变量。
  • [bar] 按值捕获 bar 变量,同时不捕获其他变量。
  • [this] 捕获当前类中的 this 指针,让 lambda 表达式拥有和当前类成员函数同样的访问权限。如果已经使用了 & 或者 =,就默认添加此选项。捕获 this 的目的是可以在 lamda 中使用当前类的成员函数和成员变量。