C++中Lambda函数的含义及其本质

简介: C++中Lambda函数的含义及其本质

学习形如钻子,必须集中在一点钻下去才有力量。


大家好,这篇文章和大家分享一下Lambda函数,以及对它的理解,看完这一篇文章,你会对Lambda会有一个基本的认识。


第一到第四都是lambda函数的含义以及用法,第五是lambda函数的本质,实际上就是一个重载函数,很巧妙。


Lambda函数是C++11标准新增的语法糖,也称为lambda表达式或者匿名函数。


Lambda函数的特点是:简洁,距离近,高校和功能强大。

语法:

7bf85af841b245fdbe191d3dff5162f1.png

示例:

[](int a)->int{cout<<"lambda表达式很好用"<<endl;}


捕获列表是lambda的核心,整个lambda也就这个比较复杂,要记得东西比较多,其他部分比较简单,所以把捕获列表这个重点放在最后解释,请听我一一道来。

此时你可能看不懂,但是没关系啦。,后面一一展开说明各个部分所起的作用和所表达的意思。


一.参数列表


参数列表是随机的,类似普通函数的参数列表,如果没有参数列表,()可以省略不写。

lambda函数与普通函数参数列表的不同:

(1)lambda函数不能有默认参数。

(2)所有参数必须有参数名。

(3)不支持可变参数。


二、返回类型


用后置的方法书写返回类型,类似于普通函数的返回类型,如果不写返回类型,编译器会根据函数体中的代码推断出来。如果有返回类型,建议显式的指定,自动推断可能与预期不一致。


三、函数体


类似于普通函数的函数体。


四、捕获列表


前言&引入:

通过捕获列表,lambda函数可以访问父作用域函数体定义的非静态变量)中的非静态局部变量(静态局部变量可以直接访问,不能访问全局变量)。

此话抽象,不妨直接上代码解释上面这一句话:

#include<iostream>
using namespace std;
int main()
{
    int ii = 10;
    auto  f = [ii](int a) {cout << a + ii; };
    f(1);//f(1)里面的1对应(int a),把1传给参数a,然后执行函数体里面的内容打印出11
    return 0;
}


注意:因为这个变量ii在lambda函数的捕获列表中,如果ii不在的话,编译器就会报错。

代码验证及其报错如下:


ce1a93f50fcca81d2d7ea22c046804aa.png


捕获列表书写在[]中,与函数参数的传递类似,捕获方式可以是值和引用。以下列出了不同的捕获列表的方式。


image.png


1.值捕获


·与传递参数类似,采用值捕获(还记得交换两个整形变量使用的方法有值传递和址传递的方法吗?这里可·以仿造这个知识点进行理解)的前提是变量可以拷贝。


·与传递参数不同,变量的值是在lambda函数创建时拷贝,而不是调用时拷贝。

例如:

size_t v1 = 42;
auto f = [ v1 ]  { return v1; };    // 使用了值捕获,将v1拷贝到名为f的可调用对象。
v1 = 0;    auto j = f();        // j为42,f保存了我们创建它是v1的拷贝。


由于被捕获的值是在lambda函数创建时拷贝,因此在随后对其修改不会影响到lambda内部的值。


默认情况下,如果以传值方式捕获变量,则在lambda函数中不能修改变量的值。


2. 引用捕获


·和函数引用参数一样,引用变量的值在lambda函数体中改变时,将影响被引用的对象。

        size_t v1 = 42;
        auto f = [ &v1 ]  { return v1; };     // 引用捕获,将v1拷贝到名为f的可调用对象。
        v1 = 0;
        auto j = f();                         // j为0。


注意:如果采用引用方式捕获变量,就必须保证被引用的对象在lambda执行的时候是存在的。

3. 隐式捕获

· 除了显式列出我们希望使用的父作域的变量之外,还可以让编译器根据函数体中的代码来推断需要捕获哪些变量,这种方式称之为隐式捕获。

· 隐式捕获有两种方式,分别是[=]和[&]。[=]表示以值捕获的方式捕获外部变量,[&]表示以引用捕获的方式捕获外部变量。


        int a = 123;
        auto f = [ = ]  { cout << a << endl; };        //值捕获
        f();     // 输出:123
        auto f1 = [ & ] { cout << a++ << endl; };         //引用捕获
        f1();    //输出:123(采用了后++)
        cout << a << endl;         //输出 124


4.混合方式捕获


·lambda函数还支持混合方式捕获,即同时使用显式捕获和隐式捕获。


·混合捕获时,捕获列表中的第一个元素必须是 = 或 &,此符号指定了默认捕获的方式是值捕获或引用捕获。

注意:显式捕获的变量必须使用和默认捕获不同的方式捕获。

例如:


        int i = 10;
        int  j = 20;
        auto f1 = [ =, &i] () { return j + i; };        // 正确,默认值捕获,显式是引用捕获
        auto f2 = [ =, i] () { return i + j; };        // 编译出错,默认值捕获,显式值捕获,冲突
        auto f3 = [ &, &i] () { return i +j; };        // 编译出错,默认引用捕获,显式引用捕获,                                                                                               冲突了


5.修改值捕获变量的值


·在lambda函数中,如果以传值方式捕获变量,则函数体中不能修改该变量,否则会引发编译错误。

·在lambda函数中,如果希望修改值捕获变量的值,可以加mutable选项,但是,在lambda函数的外部,变量的值不会被修改。


直接上代码:


int a = 123; auto f = [a]()mutable { cout << ++a << endl; }; // 不会报错 cout << a << endl; // 输出:123 f(); // 输出:124 cout << a << endl; // 输出:123

五.Lambda函数的本质

当我们编写了一个lambda函数之后,编译器将它翻译成一个类,该类中有一个重载了()的函数。

1.采用值捕获

采用值捕获时,lambda函数生成的类用捕获变量的值初始化自己的成员变量。

例如:


    int a =10;
    int b = 20;
    auto addfun = [=] (const int c ) -> int { return a+c; };
    int c = addfun(b);    
    cout << c << endl;
    等同于:
    class Myclass
    {
        int m_a;        // 该成员变量对应通过值捕获的变量。
    public:
        Myclass( int a ) : m_a(a){};    // 该形参对应捕获的变量。
        // 重载了()运算符的函数,返回类型、形参和函数体都与lambda函数一致。
        int operator()(const int c) const
        {
            return a + c;
        }
    };


默认情况下,由lambda函数生成的类是const成员函数,所以变量的值不能修改。如果加上mutable,相当于去掉const。这样上面的限制就能讲通了。


2.采用引用捕获


注意:如果lambda函数采用引用捕获的方式,编译器直接引用就行了。

唯一需要注意的是,lambda函数执行时,程序必须保证引用的对象有效。

看到这里希望能助你一臂之力,让你对Lambda有更好的理解。


2023.02.07

From:努力进大厂的新青年

相关文章
|
28天前
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。
|
1月前
|
存储 前端开发 C++
C++ 多线程之带返回值的线程处理函数
这篇文章介绍了在C++中使用`async`函数、`packaged_task`和`promise`三种方法来创建带返回值的线程处理函数。
45 6
|
1月前
|
C++
C++ 多线程之线程管理函数
这篇文章介绍了C++中多线程编程的几个关键函数,包括获取线程ID的`get_id()`,延时函数`sleep_for()`,线程让步函数`yield()`,以及阻塞线程直到指定时间的`sleep_until()`。
23 0
C++ 多线程之线程管理函数
|
1月前
|
编译器 C语言 C++
C++入门3——类与对象2-2(类的6个默认成员函数)
C++入门3——类与对象2-2(类的6个默认成员函数)
23 3
|
1月前
|
编译器 C语言 C++
详解C/C++动态内存函数(malloc、free、calloc、realloc)
详解C/C++动态内存函数(malloc、free、calloc、realloc)
157 1
|
1月前
|
存储 编译器 C++
C++入门3——类与对象2-1(类的6个默认成员函数)
C++入门3——类与对象2-1(类的6个默认成员函数)
30 1
|
1月前
|
编译器 C语言 C++
C++入门6——模板(泛型编程、函数模板、类模板)
C++入门6——模板(泛型编程、函数模板、类模板)
41 0
C++入门6——模板(泛型编程、函数模板、类模板)
|
2月前
|
编译器 C++
【C++核心】函数的应用和提高详解
这篇文章详细讲解了C++函数的定义、调用、值传递、常见样式、声明、分文件编写以及函数提高的内容,包括函数默认参数、占位参数、重载等高级用法。
22 3
|
2月前
|
算法 编译器 程序员
C++ 11新特性之Lambda表达式
C++ 11新特性之Lambda表达式
17 0
|
3月前
|
编译器 C++ 容器
【C++】String常见函数用法
【C++】String常见函数用法