lambda表达式

lambda表达式

lambda表达式的定义

​ 对于可调用的解释:对于一个对象或一个表达式,如果可以对其使用调用运算符(调用运算符为()),则称它为可调用的。即,如果e是一个可调用的表达式,则我们可以编写代码e(args),其中args是一个逗号分隔的一个或多个参数的列表。

​ 一个lambda表达式表示一个可调用的代码单元。我们可以将其理解为一个未命名的内联函数。与任何函数类似,一个lambda具有一个返回类型、一个参数列表和一个函数体。但与函数不同,lambda可能定义在函数内部。一个lambda表达式具有如下的形式:

[capture list](parameter list) ->return type {function body}

​ 其中,capture list(捕获列表)是一个lambda所在函数中定义局部变量的列表(通常为空):return type\partmeter list和function body与任何普通函数一样,分别表示返回类型、参数列表和函数体,但是与普通函数不同,lambda必须使用尾置返回来指定类型。可以忽略参数列表和返回类型,但必须包含捕获列表和函数体。

​ eg: auto f = [ ]{return 42;};

​ lambda的调用方式与普通函数的调用方式相同,都是使用调用运算符。

​ cout<<f( )<<endl;//打印42

​ 在lambda中忽略括号和参数列表等价于指定一个空参数列表,在此例中,当调用fs时,参数列表是空的。如果忽略返回类型,lambda根据函数体中的代码推断出返回类型。如果函数体只是一个return 语句,则返回类型从返回的表达式类型推断而来。否则返回类型是void。

NOTE:如果lambda的函数体包含任何单一return语句之外的内容,且未指定返回类型则返回void。

向lambda传递参数

​ 与一个普通函数调用类似,调用一个lambda时给定的实参用来初始化lambda的形参。通常,实参和形参的类型必须匹配。但与普通函数不同,**lambda不能有默认参数。**因此,一个lambda调用的实参数目永远与形参数目相等。一旦形参初始化完毕,就可以执行函数体。

使用捕获列表

​ 一个lambda可以出现在一个函数中,使用其局部变量,但它只能使用那些明确指明的变量。**一个lambda通过将局部变量包含在其捕获列表中来指出将会使用这些变量。**捕获列表指引lambda在其内部包含访问局部变量所需的信息。

Note:一个lambda只有在其捕获列表中捕获一个它所在函数中的局部变量,才能在函数体中使用该变量。

lambda捕获与返回

​ 类似参数传递,变量的捕获方式也可以是值或引用。

值捕获

​ 与传值参数类似,采用值捕获的前提是变量可以拷贝。与参数不同,被捕获的变量的值是在lambda创建时拷贝,而不是调用时拷贝。由于捕获变量的值是lambda创建时拷贝的,因此随后对其修改不会影响到lambda内对应的值。

引用捕获

一个以引用方式捕获的变量与其他任何类型的引用行为类似。当我们在lambda函数体内使用此变量时,实际上使用的是引用所绑定的对象。

如果我们采用引用方式捕获一个变量,就必须确保被引用的对象在lambda执行的时候是存在的。

int c = 87;
//值捕获
auto f6 = [c]{return c;};
//引用捕获
auto f7 = [&c]{return c;};
c = 188;
cout<<"test4值捕获:"<<f6()<<endl;//输出87
cout<<"test4引用捕获:"<<f7()<<endl;//输出188

NOTE:一般来说,我们应该尽量减少捕获的数据量,来避免潜在的捕获导致的问题。而且,如果可能的话,应该避免捕获指针或引用。

隐式捕获

除了显式列出我们希望使用的来自所在函数的变量之外,还可以让编译器根据lambda体中的代码来推断我们要使用哪些变量。为了指示编译器推断捕获列表,应在捕获列表中写一个&或=。&告诉编译器采用捕获引用方式,=表示采用值捕获方式。

​ 如果我们希望对一部分变量采用值捕获,对其他变量采用引用捕获,可以混合使用隐式捕获和显式捕获。当我们混合使用隐式捕获和显式捕获时,捕获列表中的第一个元素必须是一个&或=。此符号指定了默认捕获方式为引用或值。 捕获方式如下表。

在这里插入图片描述

eg:

   //test4,捕获方式
    int c = 87;
    //值捕获
    auto f6 = [c]{return c;};
    //引用捕获
    auto f7 = [&c]{return c;};
    c = 188;
    cout<<"test4值捕获:"<<f6()<<endl;//输出87
    cout<<"test4引用捕获:"<<f7()<<endl;//输出188

    //test5,隐式捕获
    int d = -9;
    auto f8 = [=]{return d;};
    cout<<"test5,隐式捕获:"<<f8()<<endl;
    //混合使用隐式捕获和显式捕获
    auto f9 = [=,&d]{d=c+d;return d;};
    cout<<"混合使用隐式捕获和显式捕获:"<<f9()<<endl;

可变lambda

​ 默认情况下,对于一个值被拷贝的变量,lambda不会改变其值(默认情况这个变量不是左值,无法修改),如果我们希望改变一个被捕获的变量的值,就必须在参数列表后加上关键字mutable。修改的是lambda中局部变量的值。

    //test6,可变lambda
    int e = 666;
    auto f10 = [=]()mutable{return ++e;};
    //编译error:表达式必须是可修改的左值
    //auto f11 = [e]{e = 11;return ++e;};
    cout<<"test6,可变lambda,f10()="<<f10()<<endl;//667
    cout<<"test6,可变lambda e = "<<e<<endl;//666

一个引用捕获的变量是否可以修改依赖于此引用指向的是一个const类型还是一个非const类型。

指定lambda返回类型

​ 尽管C++primer 第五版书中,说明如果一个lambda体包含return之外的任何语句,则编译器假定此lambda返回void。但是在gnu编译器下,mingw7.2.0版本下以下代码编译通过:

vector<int> v1 = {1,2,3,4,5,6,7,8};
transform(v1.begin(),v1.end(),v1.begin(),[](int i){
    if(i>4) return -i;else return i;
});//编译器仍然能够推断lambda返回类型
for(auto x : v1) cout<<x<<",";
cout<<endl;

如果要指定lambda的返回类型,必须尾置返回

lambda是函数对象

函数调用运算符

​ operator()是函数调用运算符。如果类重载了函数调用运算符,则我们可以像使用函数一样使用该类的对象。又因为这样的类又能存储状态,所以与普通函数相比它们更加灵活。

eg:

struct absint{
    int operator() (int val) const{
        return val < 0 ? -val : val;
    }
};
//使用举例
//定义一个对象,然后调用其函数调用运算符
absint a;
int ans = a(-5);
//使用临时对象,然后调用其函数调用运算符
int ans1 = absint()(99);

lambda是函数对象

​ 当我们编写一个lambda后,编译器将该表达式翻译成一个未命名的类的未命名对象。在lambda表达式产生的类中含有一个重载的函数调用运算符。

表示lambda及相应捕获行为的类

当一个表达式通过引用捕获变量时,将由程序确保lambda执行时,引用所引的对象确实存在。因此,编译器可以直接使用该引用而无须在lambda产生的类中将其存储为数据成员。(在GNU,mingw7.2.0下,仍然为引用创建了数据成员(指针))

相反,通过值捕获的变量被拷贝到lambda中。因此,这种lambda产生的类必须为每个捕获的变量建立对应的数据成员,同时创建构造函数,令器使用捕获的变量的值来初始化数据成员。

lambda表达式产生的类不含默认构造函数、赋值运算符及默认析构函数;它是否含有默认的拷贝/移动构造函数则通常要视捕获的数据成员类型而定。

 	//test8,lambda大小,以下在64为系统下运行,
    //f15中应该含有三个指针,所以其大小为24
    int x ,y,z;
    auto f13 = []{return 1;};//1
    cout<<"f13 size():"<<sizeof(f13)<<endl;
    auto f14 = [x,y,z]{return x+y+z;};//12
    cout<<"f14 size():"<<sizeof(f14)<<endl;
    auto f15 = [&x,&y,&z]{return x+y+z;};
    cout<<"f15 size():"<<sizeof(f15)<<endl;//24

完整测试代码:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main(){
    //lambda 可以不要形参列表以及返回类型
    //但必须要有捕获列表和函数体
    auto f1 = []{return 42;};
    cout<<f1()<<endl;

    //局部变量a,b
    int a = 2,b = 89;
    //test1,传递形参,形参不能是默认形参
    auto f2 = [](int x,int y){return x>y?x:y;};
    cout<<"test1:"<<f2(a,b)<<endl;


    //test2,返回类型的书写方式,也可以不写,让编译器推断
    auto f3 = [](int x,int y)->int{return x>y?x:y;};
    auto f4 = [](int x,int y){
        int a = 0;
        if(x>y) return x;
        else return y;
    };


    //test3,捕获列表中是捕获的变量
    auto f5 = [a](int x,int y)->int{return x>a?x:y;};
    cout<<f5(1,4)<<endl;

    //test4,捕获方式
    int c = 87;
    //值捕获
    auto f6 = [c]{return c;};
    //引用捕获
    auto f7 = [&c]{return c;};
    c = 188;
    cout<<"test4值捕获:"<<f6()<<endl;//输出87
    cout<<"test4引用捕获:"<<f7()<<endl;//输出188

    //test5,隐式捕获
    int d = -9;
    auto f8 = [=]{return d;};
    cout<<"test5,隐式捕获:"<<f8()<<endl;
    //混合使用隐式捕获和显式捕获
    auto f9 = [=,&d]{d=c+d;return d;};
    cout<<"混合使用隐式捕获和显式捕获:"<<f9()<<endl;

    //test6,可变lambda
    int e = 666;
    auto f10 = [=]()mutable{return ++e;};
    //编译error:表达式必须是可修改的左值
    //auto f11 = [e]{e = 11;return ++e;};
    cout<<"test6,可变lambda,f10()="<<f10()<<endl;
    cout<<"test6,可变lambda e = "<<e<<endl;
    
    //test7,指定返回类型
    auto f12 = [](int i){
        if(i > 0) return i;
        else return -i;
    };
    vector<int> v1 = {1,2,3,4,5,6,7,8};
    transform(v1.begin(),v1.end(),v1.begin(),[](int i){
        if(i>4) return -i;else return i;
    });
    for(auto x : v1) cout<<x<<",";
    cout<<endl;

    //test8,lambda大小
    int x ,y,z;
    auto f13 = []{return 1;};//1
    cout<<"f13 size():"<<sizeof(f13)<<endl;
    auto f14 = [x,y,z]{return x+y+z;};//12
    cout<<"f14 size():"<<sizeof(f14)<<endl;
    auto f15 = [&x,&y,&z]{return x+y+z;};
    cout<<"f15 size():"<<sizeof(f15)<<endl;//24
    system("pause");
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值