C++:lambda表达式

2020-05-10  本文已影响0人  wangawu121

lambda表达式

一、定义

lambda表达式表示一个可调用的代码单元,可以将其理解为一个未命名的内联函数。

与函数相同的是,lambda具有返回类型、参数列表和函数体;而与函数不同的是,lambda可以定义在函数内部。

一个lambda表达式形式如下:

[捕获列表](参数列表) -> 返回类型 {函数体}

二、传递lambda给泛型算法

如我们有一个容器名为words,里边存储了一些词汇,我们要把它用内置泛型算法sort()进行字母表升序排序,此时需要给sort()传递一个可调用对象,该对象读入两个string、使用小于号<其对其字母顺序进行比较并返回一个bool。

此时我们就可以在sort()的参数表上直接定义一个lambda:

//sort读入容器的首元素位置迭代器、尾后元素迭代器位置作为范围,调用我们给它的函数来对中间的元素进行排序
sort(words.begin(),words.end(),[](const string &a,const string& b){return a.size()<b.size();});

三、lambda的捕获和返回

1.lambda生成新类型和一个该类型的对象

当定义一个lambda时,编译器生成一个与lambda对应的新的未命名的类类型。

当我们向一个函数传递一个作为参数的lambda时,实际上同时定义了一个新类型和该类型的一个对象;类似地,用auto定义一个用lambda初始化的变量时,定义了一个由该lambda生成的类型的对象。

默认情况下,从lambda生成的类都包含一个对应这个lambda所捕获的变量的数据成员。类似于普通类的数据成员,lambda的数据成员也会在lambda对象创建时被初始化。

2.使用捕获列表

在某个函数体内的lambda要使用函数的局部变量需要把该变量放入捕获列表:

#include<iostream>
using namespace std;

void Print(string s){
    //定义一个lambda f,它的捕获列表包含了函数Print里的局部变量s
    auto f = [s] {
        cout << "调用lambda打印:" << s << endl;
        return; };
    //在函数体内调用f
    f();
}

int main() {
    Print("AAAAAAAAAAAAAAAAA");
}

运行结果:


image-20200509221104863.png

如果我们不在f的捕获列表里包含s,就会出现以下错误:


image-20200509220831266.png

lambda可以直接使用在当前函数之外的已经定义的变量,比如上边我们包含了头文件iostream,于是可以在f里面使用cout。

3.值捕获和引用捕获,可变lambda

捕获列表里写的是变量名时,使用值捕获,如果是&变量名,则使用引用捕获。

值捕获:

采用值捕获的前提是变量可以拷贝。捕获与传参不同,被捕获的变量会在lambda定义时被拷贝,而传参会在函数被调用时被拷贝。

一个值捕获的例子:

void func() {
    size_t num1 = 10086;
    //定义一个lambda,对num1采用值捕获
    auto f = [num1] {return num1; };
    auto num2 = f(); //num2的值为10086
}

由于num1在声明lambda时已经被拷贝,后续我们再修改num1并不会影响到lambda的返回值。

引用捕获:

采用引用捕获时,当我们在lambda里使用这个变量,实际上使用的是这个引用绑定的对象。

所以如果我们定义lambda后再去修改引用捕获的变量,则调用lambda的返回值也会受到影响:

void func() {
    size_t num1 = 10086;
    //对num1采用引用捕获
    auto f = [&num1] {return num1; };
    //然后我们改变num1的值
    num1 += 10000;
    auto num2 = f(); //num2的值会是20086
}

注意:

隐式捕获:

可以在捕获列表[]内打上=、&,分别表示隐式的值捕获和引用捕获。

如下,我们在[]里打上=表示隐式值捕获,然后在lambda里使用函数func的局部变量num1,则lambda f会隐式地值捕获num1。

void func() {
    size_t num1 = 10086;
    //对num1采用隐式的值捕获
    auto f = [=] {return num1; }; //如果是隐式引用捕获则是auto f = [&] {return num1; };
    auto num2 = f(); //num2为10086
}
void func() {
    size_t num1 = 10086;
    size_t num2 = 10000;
    //对num1采用隐式的值捕获,num2采用显示引用捕获
    auto f = [=, &num2] {return num1 + num2;};
    auto num3 = f(); //num3为20086
}

4.可变lambda

对于值捕获

一般我们不会修改值捕获的变量的值,如果要改变,就必须使用关键字mutable:

void func() {
    size_t num1 = 10086;
    //定义一个lambda,对num1采用值捕获
    auto f = [num1] () mutable {return ++num1; };
    auto num2 = f(); //num2的值为10087
}

如果没有加mutable,试图修改值捕获变量的行为会被报错:


image-20200509234551334.png

对于引用捕获

如果捕获对象不是const的,则可以修改而不需要使用mutable:

void func() {
    size_t num1 = 10086;
    //定义一个lambda,对num1采用值捕获
    auto f = [&num1] (){return ++num1; };
    auto num2 = f(); //num2的值为10087
}

5.指定lambda的返回类型

C++ primer里说到,(C++11)默认情况下,如果你不指定返回类型,如果一个lambda的函数体包含任何除了return之外的语句,编译器就会假定该lambda返回void。

如果避免如此,我们就要用->类型显示地指定lambda地返回类型:

void func() {
    int num1 = -10086;
    //定义一个lambda,功能是返回捕获的num1的绝对值
    //显式地用->int表示返回类型是int
    auto Abs = [num1] () ->int {
        cout << "调用lambda表达式f" << endl;
        if (num1 > 0) return num1;
        else return -num1;
     };
    auto num2 = Abs(); 
    cout << num2 << endl;
}

不过我发现新的版本下不打->类型也能正常返回,可能特性又变化了。

上一篇下一篇

猜你喜欢

热点阅读