首页 > 编程语言 >C++ Lambda表达式的完整介绍

C++ Lambda表达式的完整介绍

时间:2023-05-09 22:34:12浏览次数:47  
标签:函数 int 捕获 C++ plus Lambda 表达式 lambda

c++在c++11标准中引入了lambda表达式,一般用于定义匿名函数,使得代码更加灵活简洁。lambda表达式与普通函数类似,也有参数列表、返回值类型和函数体,只是它的定义方式更简洁,并且可以在函数内部定义。

什么是Lambda表达式

最常见的lambda的表达式写法如下

auto plus = [] (int v1, int v2) -> int { return v1 + v2; }

int sum = plus(1, 2);

这里只是计算两个数的和,我们一般情况下肯定是不会这么用的,更多的时候,我们都是和stl的一些算法结合使用,例如自定义一个结构体的排序规则和打印。

struct Item

{

    Item(int aa, int bb) : a(aa), b(bb) {} 

    int a;

    int b;

};

 

int main()

{

    std::vector<Item> vec;

    vec.push_back(Item(1, 19));

    vec.push_back(Item(10, 3));

    vec.push_back(Item(3, 7));

    vec.push_back(Item(8, 12));

    vec.push_back(Item(2, 1));

 

    // 根据Item中成员a升序排序

    std::sort(vec.begin(), vec.end(),

        [] (const Item& v1, const Item& v2) { return v1.a < v2.a; });

 

    // 打印vec中的item成员

    std::for_each(vec.begin(), vec.end(),

        [] (const Item& item) { std::cout << item.a << " " << item.b << std::endl; });

return 0;

}

这样的写法让我们代码更加简洁、清晰,可读性更强。

在c++的官方文档中,给出了lamda表达式的四种写法,这里知乎的排版有点难用,所以直接在官方文档上截了一个图。

<img src="https://pic4.zhimg.com/v2-cd4aeb07f429759ecbd6943adbe6449b_b.jpg" data-caption="" data-size="normal" data-rawwidth="569" data-rawheight="157" class="origin_image zh-lightbox-thumb" width="569" data-original="https://pic4.zhimg.com/v2-cd4aeb07f429759ecbd6943adbe6449b_r.jpg"/>

 

下面介绍一下lambda的四种表达式的含义,以及表达式中各个成分的,其实说白就是在自己理解的基础上翻译一下官方文档。

四种表达式的含义

(1)完整的lambda表达式,包含了lambda表达式的所有成分。

(2)常量lambda表达式,捕获的变量都是常量,不能在lambda表达式的body中进行修改。

(3)和(2)基本一致,唯一的区别就是,lambda表达式的函数返回值可以通过函数体推导出来。一般情况函数返回值类型明确或者没有返回值的情况下可以这样写。

(4)lambda表达式的函数没有任何参数,但是可以添加lambda-specifiers,lambda-specifiers是什么我们后续再介绍。

lambda表达式各个成员的解释

captures 捕获列表,lambda可以把上下文变量以值或引用的方式捕获,在body中直接使用。

tparams 模板参数列表(c++20引入),让lambda可以像模板函数一样被调用。

params 参数列表,有一点需要注意,在c++14之后允许使用auto左右参数类型。

lambda-specifiers lambda说明符一些可选的参数,这里不多介绍了,有兴趣的读者可以去官方文档上看。这里比较常用的参数就是mutable和exception。其中,表达式(1)中没有trailing-return-type,是因为包含在这一项里面的。

trailing-return-type 返回值类型,一般可以省略掉,由编译器来推导。

body 函数体,函数的具体逻辑。

捕获列表

上面介绍完了lambda表达式的各个成分,其实很多部分和正常的函数没什么区别,其中最大的一个不同点就是捕获列表。我在刚开始用lambda表达式的时候,还一直以为这个没啥用,只是用一个 [] 来标志着这是一个lambda表达式。后来了解了才知道,原来这个捕获列表如此强大,甚至我觉得捕获列表就是lambda表达式的灵魂。下面先介绍几种常用的捕获方式。

[] 什么也不捕获,无法lambda函数体使用任何

[=] 按值的方式捕获所有变量

[&] 按引用的方式捕获所有变量

[=, &a] 除了变量a之外,按值的方式捕获所有局部变量,变量a使用引用的方式来捕获。这里可以按引用捕获多个,例如 [=, &a, &b,&c]。这里注意,如果前面加了=,后面加的具体的参数必须以引用的方式来捕获,否则会报错。

[&, a] 除了变量a之外,按引用的方式捕获所有局部变量,变量a使用值的方式来捕获。这里后面的参数也可以多个,例如 [&, a, b, c]。这里注意,如果前面加了&,后面加的具体的参数必须以值的方式来捕获。

[a, &b] 以值的方式捕获a,引用的方式捕获b,也可以捕获多个。

[this] 在成员函数中,也可以直接捕获this指针,其实在成员函数中,[=]和[&]也会捕获this指针。

 

#include <iostream>

 

int main()

{

    int a = 3;

    int b = 5;

    

    // 按值来捕获

    auto func1 = [a] { std::cout << a << std::endl; };

    func1();

 

    // 按值来捕获

    auto func2 = [=] { std::cout << a << " " << b << std::endl; };

    func2();

 

    // 按引用来捕获

    auto func3 = [&a] { std::cout << a << std::endl; };

    func3();

 

    // 按引用来捕获

    auto func4 = [&] { std::cout << a << " " << b << std::endl; };

    func4();

}

编译器如何看待Lambda表达式

我们把lambda表达式看成一个函数,那编译器怎么看待我们协的lambda呢?

其实,编译器会把我们写的lambda表达式翻译成一个类,并重载 operator()来实现。比如我们写一个lambda表达式为

auto plus = [] (int a, int b) -> int { return a + b; }

int c = plus(1, 2);

那么编译器会把我们写的表达式翻译为

// 类名是我随便起的

class LambdaClass

{

public:

    int operator () (int a, int b) const

    {

        return a + b;

    }

};

 

LambdaClass plus;

int c = plus(1, 2);

调用的时候编译器会生成一个Lambda的对象,并调用opeartor ()函数。(备注:这里的编译的翻译结果并不和真正的结果完全一致,只是把最主要的部分体现出来,其他的像类到函数指针的转换函数均省略

上面是一种调用方式,那么如果我们写一个复杂一点的lambda表达式,表达式中的成分会如何与类的成分对应呢?我们再看一个 值捕获 例子。

int x = 1; int y = 2;

auto plus = [=] (int a, int b) -> int { return x + y + a + b; };

int c = plus(1, 2);

编译器的翻译结果为

class LambdaClass

{

public:

    LambdaClass(int xx, int yy)

    : x(xx), y(yy) {}

 

    int operator () (int a, int b) const

    {

        return x + y + a + b;

    }

 

private:

    int x;

    int y;

}

 

int x = 1; int y = 2;

LambdaClass plus(x, y);

int c = plus(1, 2);

 

其实这里就可以看出,值捕获时,编译器会把捕获到的值作为类的成员变量,并且变量是以值的方式传递的。需要注意的时,如果所有的参数都是值捕获的方式,那么生成的operator()函数是const函数的,是无法修改捕获的值的,哪怕这个修改不会改变lambda表达式外部的变量,如果想要在函数内修改捕获的值,需要加上关键字 mutable。向下面这样的形式。

int x = 1; int y = 2;

auto plus = [=] (int a, int b) mutable -> int { x++; return x + y + a + b; };

int c = plus(1, 2);

 

我们再来看一个引用捕获的例子。

int x = 1; int y = 2;

auto plus = [&] (int a, int b) -> int { x++; return x + y + a + b;};

int c = plus(1, 2);

编译器的翻译结果为

class LambdaClass

{

public:

    LambdaClass(int& xx, int& yy)

    : x(xx), y(yy) {}

 

    int operator () (int a, int b)

    {

        x++;

        return x + y + a + b;

    }

 

private:

    int &x;

    int &y;

};

我们可以看到以引用的方式捕获变量,和值捕获的方式有3个不同的地方:1. 参数引用的方式进行传递; 2. 引用捕获在函数体修改变量,会直接修改lambda表达式外部的变量;3. opeartor()函数不是const的。

针对上面的集中情况,我们把lambda的各个成分和类的各个成分对应起来就是如下的关系:

捕获列表,对应LambdaClass类的private成员

参数列表,对应LambdaClass类的成员函数的operator()的形参列表

mutable,对应 LambdaClass类成员函数 operator() 的const属性 ,但是只有在捕获列表捕获的参数不含有引用捕获的情况下才会生效,因为捕获列表只要包含引用捕获,那operator()函数就一定是非const函数

返回类型,对应 LambdaClass类成员函数 operator() 的返回类型

函数体,对应 LambdaClass类成员函数 operator() 的函数体。

引用捕获和值捕获不同的一点就是,对应的成员是否为引用类型。

 

参考文章

Lambda expressions

C++ Lambda 编译器实现原理

 

 

https://zhuanlan.zhihu.com/p/384314474

标签:函数,int,捕获,C++,plus,Lambda,表达式,lambda
From: https://www.cnblogs.com/im18620660608/p/17386521.html

相关文章

  • C++11 lambda表达式精讲
    lambda表达式是C++11最重要也最常用的一个特性之一,C#3.5和Java8中就引入了lambda表达式。 lambda来源于函数式编程的概念,也是现代编程语言的一个特点。C++11这次终于把lambda加进来了。 lambda表达式有如下优点:声明式编程风格:就地匿名定义目标函数或函数对......
  • Django笔记二十三之case、when操作条件表达式搜索、更新等操作
    本文首发于公众号:Hunter后端原文链接:Django笔记二十三之条件表达式搜索、更新等操作这一篇笔记将介绍条件表达式,就是如何在model的使用中根据不同的条件筛选数据返回。这个操作类似于数据库中ifelifelse的逻辑。以下是本篇笔记的目录:model和数据准备When和Case......
  • 1009 说反话(C++)
    一、问题描述:给定一句英语,要求你编写程序,将句中所有单词的顺序颠倒输出。输入格式:测试输入包含一个测试用例,在一行内给出总长度不超过80的字符串。字符串由若干单词和若干空格组成,其中单词是由英文字母(大小写有区分)组成的字符串,单词之间用1个空格分开,输入保证句子末尾没有......
  • c++打卡练习(23)
    亲密数如果整数A的全部因子(包括1,不包括A本身)之和等于B;且整数B的全部因子(包括1,不包括B本身)之和等于A,则将整数A和B称为亲密数。求3000以内的全部亲密数。流程图:伪代码:源代码:#include<iostream>usingnamespacestd;intmain(){ inta,i,b,n; printf("Therearefollowing......
  • C++异常和错误处理机制:如何使您的程序更加稳定和可靠
    在C++编程中,异常处理和错误处理机制是非常重要的。它们可以帮助程序员有效地处理运行时错误和异常情况。本文将介绍C++中的异常处理和错误处理机制。什么是异常处理?异常处理是指在程序执行过程中发生异常或错误时,程序能够捕获并处理这些异常或错误的机制。例如,当程序试图访问......
  • C++异常和错误处理机制:如何使您的程序更加稳定和可靠
    在C++编程中,异常处理和错误处理机制是非常重要的。它们可以帮助程序员有效地处理运行时错误和异常情况。本文将介绍C++中的异常处理和错误处理机制。什么是异常处理?异常处理是指在程序执行过程中发生异常或错误时,程序能够捕获并处理这些异常或错误的机制。例如,当程序试图访问一......
  • 【C++容器基础】
    容器汇总: 向量(vector): 队列(deque):列表(list): 数组(array): 字符串(string): 映射(map):无序 集合(set):无序不重复 ......
  • js基础---date的格式化与exec()正则表达式
    exec():获取字符串中符合正则表达式的内容。正则表达式中的匹配模式:i代表忽略大小写,g代表全局匹配......
  • 8. 正则表达式
    1.findall语法findall("正则表达式","要匹配的字符串")把匹配的内容返回到列表匹配数字\dimportrelist=re.findall("\d","123331231%%%@@@你好")print(list)匹配非数字\Dimportrelist=re.findall("\D","123331231%%%@@@你好"......
  • C++
    派生类练习#include<iostream>#include<string>usingnamespacestd;classAnimal{public:Animal(){}voidset_weight(intw){m_nWeightBase=w;}intget_weight(){returnm_......