C++中的lambda表达式

简介: C++中的lambda表达式


引入:

首先来看一个例子

struct fruit
{
  double _price;
  int _evalute;
  string _name;
  fruit(const char* str, int a, double price)
    :_name(str)
    ,_evalute(a)
    ,_price(price)
  {
  }
};
struct ComparePriceGreater
{
  bool operator()(const fruit& g1, const fruit& gr)
  {
    return g1._price > gr._price;
  }
};
struct ComparePriceLess
{
  bool operator()(const fruit& g1, const fruit& gr)
  {
    return g1._price < gr._price;
  }
};
int main()
{
  vector<fruit> v = { {"苹果",3,2.5},{"香蕉",2,3.5},{"梨子",5,5.5} };
  sort(v.begin(), v.end(), ComparePriceGreater());//不知道怎么去比较,所以我们要传入一个仿函数
  sort(v.begin(), v.end(), ComparePriceLess());
  return 0;
}

 如图,我们知道,std::sort函数在排序时,如果是内置类型,就默认是升序排列,但是如果是自定义类型呢?在lambda表达式引入之前,我们通常是写一个仿函数,然后重载(),从而达到排序的效果。

 但是,每次为了实现这样一个算法,就都要去重新写出一个类。特别是相同类的命名问题,都给我们带来了很大的不方便。

 lambda表达式是在C++11引入的语法,一般用于定义匿名函数,使代码更加灵活方便。

lambda表达式的格式

[capture-list] (parameters) mutable -> return-type { statement}

各个部分的说明

  • capture-list:捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量提供给lambda表达式使用。
  • parameters:参数列表,与普通的函数很是相似,如果不需要传递参数,直接省略即可,甚至可以连()一起省略。
  • mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表括号不能省略。
  • returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。前边的->不可以忽略。
  • {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获
    到的变量。

 如上边所说,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。

捕捉列表说明

 捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。

 [var]:表示值传递方式捕捉变量var

 [=]:表示值传递方式捕获所有父作用域中的变量(包括this)

 [&var]:表示引用传递捕捉变量var

 [&]:表示引用传递捕捉所有父作用域中的变量(包括this)。

 [this]:表示值传递方式捕捉当前的this指针,上述例子中=改为this,大家可以尝试一下。

注意事项

a. 父作用域指包含lambda函数的语句块,即包含该lambda表达式的大括号中。

b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。

 当然可以不同类型的捕捉。比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量。[&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量。

c. 捕捉列表不允许变量重复传递,否则就会导致编译错误。比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复。

d. 在块作用域以外的lambda函数捕捉列表必须为空。

e. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。

f. lambda表达式之间不能相互赋值,即使看起来类型相同。

使用lambda表达式改进上边的代码

struct fruit
{
  double _price;
  int _evalute;
  string _name;
  fruit(const char* str, int a, double price)
    :_name(str)
    ,_evalute(a)
    ,_price(price)
  {
  }
};
int main()
{
  vector<fruit> v = { {"苹果",3,2.5},{"香蕉",2,3.5},{"梨子",5,5.5} };
  sort(v.begin(), v.end(), [](const fruit& f1, const fruit& f2) {return f1._price > f2._price; });
  sort(v.begin(), v.end(), [](const fruit& f1, const fruit& f2) {return f1._price < f2._price; });
  return 0;
}

通过上述代码,我们可以很清晰的看出代码更加简洁,没有那么冗余了。

仿函数和lambda表达式

 仿函数就是重载()运算符的对象,lambda表达式和仿函数功能很是相似,都可以作为仿函数表达式进行传递,那么他们的底层都是如何运转的呢?

我们来细细探究探究

 其实我们可以将lambda当做一个匿名函数对象

转到反汇编来观察是如何实现的。

 可以看出,lambda实际上会作为一个匿名函数对象进行传递,函数名省略,函数参数和函数体和仿函数相同。就像范围for一样,看着感觉会很复杂,其实底层还是使用迭代器进行遍历,一切为了方便而已。

我们还可以利用typeid来查看函数类型。

编译器是如何看lambda呢?其实就像仿函数一样。

class LambdaClass
{
public:
    int operator () (int a, int b) const
    {
        return a + b;
    }
};
LambdaClass plus;
int c = plus(1, 2);

 lambda表达式会把主要的部分提取出来,让我们的代码编写更加简便。

 那么捕获列表中捕获的变量呢?其实就是作为类的成员变量,如果是值拷贝,那么我们重载的()函数就是const函数的,是无法修改捕获的值的。

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);

 如果我们想要修改捕获的值呢?如改变上边的x,只需要加上关键字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);

如果是值引用呢?

我们只需要进行引用捕获即可。

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;
};

 此时在函数中改变传入的值就可以改变捕获的值。

 对应仿函数,我们可以最后再把lambda表达式和仿函数类的各个成分对引起来就是如下关系。

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

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

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

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

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

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

本文结束,如果有问题还请及时提出,我会虚心改正,感谢大家的观看。

目录
相关文章
|
5月前
|
存储 编译器 C语言
从C语言到C++_34(C++11_下)可变参数+ lambda+function+bind+笔试题(下)
从C语言到C++_34(C++11_下)可变参数+ lambda+function+bind+笔试题
73 5
|
6天前
|
算法 编译器 程序员
C++ 11新特性之Lambda表达式
C++ 11新特性之Lambda表达式
|
3月前
|
安全 编译器 C++
C++一分钟之-泛型Lambda表达式
【7月更文挑战第16天】C++14引入泛型lambda,允许lambda接受任意类型参数,如`[](auto a, auto b) { return a + b; }`。但这也带来类型推导失败、隐式转换和模板参数推导等问题。要避免这些问题,可以明确类型约束、限制隐式转换或显式指定模板参数。示例中,`safeAdd` lambda使用`static_assert`确保只对算术类型执行,展示了一种安全使用泛型lambda的方法。
43 1
|
4月前
|
算法 编译器 C++
C++一分钟之—Lambda表达式初探
【6月更文挑战第22天】C++的Lambda表达式是匿名函数的快捷方式,增强函数式编程能力。基本语法:`[capture](params) -&gt; ret_type { body }`。例如,简单的加法lambda:`[](int a, int b) { return a + b; }`。Lambda可用于捕获外部变量(值/引用),作为函数参数,如在`std::sort`中定制比较。注意点包括正确使用捕获列表、`mutable`关键字和返回类型推导。通过实践和理解这些概念,可以写出更简洁高效的C++代码。
46 13
|
4月前
|
C++
C++语言的lambda表达式
C++从函数对象到lambda表达式以及操作参数化
|
4月前
|
C++
C++一分钟之-理解C++的运算符与表达式
【6月更文挑战第18C++的运算符和表达式构成了编程的基础,涉及数学计算、逻辑判断、对象操作和内存管理。算术、关系、逻辑、位、赋值运算符各有用途,如`+`、`-`做加减,`==`、`!=`做比较。理解运算符优先级和结合律至关重要。常见错误包括优先级混淆、整数除法截断、逻辑运算符误用和位运算误解。解决策略包括明确优先级、确保浮点数除法、正确使用逻辑运算符和谨慎进行位运算。通过实例代码学习,如 `(a &gt; b) ? &quot;greater&quot; : &quot;not greater&quot;`,能够帮助更好地理解和应用这些概念。掌握这些基础知识是编写高效、清晰C++代码的关键。
31 3
|
4月前
|
C语言 C++ 容器
c++primer plus 6 读书笔记 第五章 循环和关系表达式
c++primer plus 6 读书笔记 第五章 循环和关系表达式
|
4月前
|
计算机视觉 C++
【见微知著】OpenCV中C++11 lambda方式急速像素遍历
【见微知著】OpenCV中C++11 lambda方式急速像素遍历
40 0
|
4月前
|
C++
C++ lambda表达式
C++ lambda表达式
|
17天前
|
编译器 C++
C++ 类构造函数初始化列表
构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。
60 30
下一篇
无影云桌面