C++ lambda函数

Stella981
• 阅读 640

lambda函数

lambda函数是什么?还是直接看代码比较容易理解:

int main()
{
    int a = 1;
    int b = 2;
    // 定义一个lambda函数
    auto sum = [](int x, int y)->int{
        return x + y;
    };

    std::cout << sum(a, b) << std::endl;   // 3

    return 0;
}

上面的代码,定义了一个lambda函数sum,该函数接收两个int参数,返回他们的和。可以看到,lambda函数不需要定义函数名,而是用[]来代替。通常情况下,lambda函数的语法定义如下:

[capture](parameters) mutable -> return-type{statement}
1
[capture],捕捉列表。捕捉列表总是在lambda函数的开始,[]是lambda函数的引出符。编译器根据该引出符确定接下来的代码是不是lambda函数。捕捉列表能够捕捉上下文中的变量以供lambda函数使用。下文会介绍捕捉的方法。
(parameters),参数列表。和普通函数的参数列表一致,如果不需要参数可以连()一起省略。
mutable,一个修饰符。默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。在使用mutable时,参数列表不可省略,即使参数为空。
->return-type,返回类型。用追踪返回类型声明函数的返回值。不需要返回值时,可以连同符号->一起省略。另外,在返回类型明确的情况下,也可以省略,让编译器推导出返回类型。
statement,函数体。与普通函数一样,但是可以使用捕捉列表中的变量。
在lambda函数定义中,参数列表和返回类型都是可选的部分,而捕捉列表和函数体有可能为空,极端情况下最简单的lambda函数就是K[]{};,当然它什么都没有做。下面罗列一下lambda函数的各种形式:

int main()
{
    int a = 1;
    int b = 2;

    [] {};                             // 最简单
    [=] {return a + b; };              // 省略了参数列表和返回值类型
    auto func1 = [&](int c) {b = a + c; };    // 省略了返回值类型
    auto func2 = [=, &b](int c)->int { return b += a + c; };  // 完整的lambda函数

    return 0;
}

通过上面的代码可以发现,起始lambda函数和普通函数之间最特殊的地方就是,lambda函数可以捕捉上下文的数据进行使用。我们用捕捉的方式来完成最开始的例子:

int main()
{
    int a = 1;
    int b = 2;
    // 定义一个lambda函数
    auto sum = [a,b]{
        return a + b;
    };

    std::cout << sum() << std::endl;   // 3

    return 0;
}

和最开始的lambda函数相比,此处的lambda函数在原型上发生了变化,不再需要传递参数,这个变化看起来似乎没有很特殊。不过在后面的介绍中,会详细讲解捕捉列表的特点。

另外,我们可以看到捕捉列表中会使用一些特殊符号,这是有一定含义的。我们来看一下捕捉列表的几种形式:

[var]表示以值传递的方式来捕捉变量var。
[=]表示以值传递的方式捕捉所有父作用域的变量,包括this指针。
[&var]表示引用传递捕捉变量var。
[&]表示引用传递捕捉所有父作用域的变量,包括this指针。
[this]表示值传递方式捕捉当前的this指针。
以上的方式可以组合使用,但是不允许对同一个变量以同一方式重复捕捉。
在块作用域(可以理解为在{}以内的代码)以外的lambda函数捕捉列表必须为空,这种lambda和普通函数除了语法上不同以外,和普通函数差别不大。在块作用域内的lambda函数只能捕捉父作用域中的自动变量,不能捕捉非此作用域的变量或者非自动变量(如静态变量等)。
lambda与仿函数

仿函数简单的说,就是重定义了成员函数operator()的一种自定义对象。这种对象再使用时从代码上看和函数一样,但是本质上不是函数。例如:

class func {
public:
    int operator()(int x, int y)
    {
        return x + y;
    }
};

int main()
{
    func sum;
    std::cout << sum(1, 2) << std::endl;   // 3

    return 0;
}

从上面的代码可以看到,出去自定义类型func的声明和对象的定义,仿函数和lambda函数的定义和使用非常相似。实际上,仿函数是编译器实现lambda函数的一种方式,现阶段编译器通常都会把lambda函数转化成一个仿函数对象。因此,在C++11中,可以把它们视做一种等价的形式。

而lambda函数捕捉列表,其实就是仿函数中带参数的构造函数,通过构造函数来初始化成员变量。

lambda函数的基础使用

有时候,我们需要对一个方法中的变量进行状态监控,且在这个方法中会多次调用。往往我们会封装一个函数去做这件事情,但是实际上其他的地方又用不到这个函数,如果用lambda函数来做这件事,可以使代码更加易读:

void thread()
{
    int a = 1;
    int b = 2;
    auto func = [&] { std::cout << a << "," << b << std::endl; };
    int count = 0;
    while (count < 10)
    {
        int c = a + b;

        func();

        a += 1;
        b += 2;

        func();

        ++count;
    }
}

另外一种常用的地方就是作为线程的函数参数,这样也能够让代码更加直观:

int main()
{
    int a = 0;
    std::thread func([&a] {
        std::cout << a << std::endl;
    });

    func.join();

    return 0;
}

lambda捕捉方式

lambda捕捉列表的捕捉方式不同,结果也会不同。值传递的方式其值在传递的时候就已经决定了,而引用的方式则等于lambda函数调用的时候的值。

int main()
{
    int i = 1;
    auto func1 = [=] {std::cout << i << std::endl; };
    auto func2 = [&] {std::cout << i << std::endl; };

    func1();             // 1
    func2();             // 1

    ++i;

    func1();             // 1,值传递方式结果不变
    func2();             // 2, 引用的方式受++i的影响

    return 0;
}

lambda的类型

在最开始,lambda函数的类型看起来和函数指针的很像,都是把函数赋值给了一个变量。后来了解到lambda函数是用仿函数实现的,又认为它是一种自定义的类。而事实上,lambda类型并不是简单的函数指针类型或者自定义类型。lambda函数是一个闭包(closure,特有的、匿名的且非联合体的class类型)的类,没有lambda表达式都会产生一个闭包类型的临时对象(右值)。因此,严格来说lambda函数并非函数指针,但是C++11允许lambda表达式向函数指针的转换,前提是没有捕捉任何变量且函数指针所指向的函数必须跟lambda函数有相同的调用方式:

typedef int(*pfunc)(int x, int y);

int main()
{
    auto func = [](int x, int y)->int {
        return x + y;
    };
    pfunc p1 = nullptr;
    p1 = func;

    std::cout << p1(1, 2) << std::endl;

    return 0;
}

lambda的常量性和mutable关键字

int main()
{
    int val = 0;
    auto const_val_lambda = [=] { val = 3; };    // 编译失败,不能在const的lambda函数中修改

    auto mutable_val_lambda = [=]() mutable { val = 3; };

    auto const_ref_lambda = [&] { val = 3; };

    auto const_param_lambda = [](int v) { v = 3; };
    const_param_lambda(val);

    return 0;
}

可以看到在const的lambda函数中无法修改捕捉到的参数。前面说到过,lambda函数是通过仿函数来实现的,捕捉参数相当于是仿函数类中的成员变量,而lambda函数相当于是成员函数,const成员函数自然不能修改普通成员变量。

lambda与STL

lambda对于C++最大的改变音轨是在STL库中,它使得STL的算法使用更加容易。首先我们看一下for_each算法:

int s = 0;
inline void sum(int i)
{
    s += i;
}

int main()
{
    std::vector vec = {0,1,2,3,4};
    std::for_each(vec.begin(), vec.end(), sum);
    std::cout << s << std::endl;

    int s1 = 0;
    std::for_each(vec.begin(), vec.end(), [&](int i) {
        s1 += i;
    });
    std::cout << s1 << std::endl;

    return 0;
}

可以看到,原来使用for_each算法需要定义一个函数来作为参数进行计算。用了lambda之后,代码结构更加清晰和简单。不过,针对这个例子使用C++11中的for循环更加方便,但是如果循环中的算法更加复杂,可能用lambda+for_each会比较好。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Irene181 Irene181
3年前
Python 之父为什么嫌弃 lambda 匿名函数?
Python支持lambda匿名函数,其扩展的BNF表示法是lambdaexpr::"lambda"parameterlist":"expression,也就是lambda参数序列:表达式。这是一种便捷的函数定义方式,若翻译成我们熟知的函数形式,会是这个样子:def <lambda(parameterlist):    retu
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Irene181 Irene181
3年前
Python 之父为什么嫌弃 lambda 匿名函数?
Python支持lambda匿名函数,其扩展的BNF表示法是lambdaexpr::"lambda"parameterlist":"expression,也就是lambda参数序列:表达式。这是一种便捷的函数定义方式,若翻译成我们熟知的函数形式,会是这个样子:def <lambda(parameterlist):    retu
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Wesley13 Wesley13
3年前
java Lambda表达式
简介  Lambda可以理解为简洁的表示可传递的匿名函数,包括:参数列表、函数主体、返回类型、抛出的异常列表(可以不存在)。  函数式接口:定义一个抽象方法的接口(接口可以拥有默认方法:default)。  函数式接口有什么用?    Lambda可以以内联的形式为函数式接口的抽象方法提供实现。虽然内部类也可以实现,但lambda表达
Wesley13 Wesley13
3年前
Java8特性
Java8又称jdk1.8。主要新特性:Lambda表达式 −Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中。方法引用 −方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
Stella981 Stella981
3年前
JS 对象数组Array 根据对象object key的值排序sort,很风骚哦
有个js对象数组varary\{id:1,name:"b"},{id:2,name:"b"}\需求是根据name或者id的值来排序,这里有个风骚的函数函数定义:function keysrt(key,desc) {  return function(a,b){    return desc ? ~~(ak
Wesley13 Wesley13
3年前
Java8—一万字的Lambda表达式的详细介绍与应用案例
  基于Java8详细介绍了lambda表达式的语法与使用,以及方法引用、函数式接口、lambda复合等Java8的新特性!文章目录1Lambda的概述2函数式接口2.1Consumer消费型接口2.2Supplier供给型接口2.3Function<T,R函数型接口
Stella981 Stella981
3年前
Python中的lambda的简单介绍
在学习python的过程中,lambda的语法经常出现,现在将它整理一下,以备日后查看。  1、lambda是什么?举个例子如下:1funclambdax:x12print(func(1))324print(func(2))5367以上lambda等同于以下函数8deffun