【C++】Lambda表达式的使用

简介: 【C++】Lambda表达式的使用

学习目标:

例如:

  • 了解Lambda的优点
  • 掌握Lambda表达式的使用
  • 了解Lambda表达式的底层原理

学习内容:

  1. Lambda表达式的语法

文章目录

Lambda表达式

lambda表达式的底层实现涉及到闭包(Closure)的概念。闭包是一个函数对象,它可以捕获外部作用域中的变量,并在其生命周期内访问和修改这些变量。lambda表达式的底层实现就是通过创建闭包来实现的。

具体而言,lambda表达式在底层会被转化为一个函数对象。这个函数对象中包含了捕获的外部变量,并且重载了函数调用运算符operator()。函数对象可以像普通的函数一样被调用,其执行的代码就是lambda表达式中的代码。

lambda表达式的展开过程包括以下几个步骤:

  1. 语法解析:将lambda表达式解析为函数对象的声明和定义。
  2. 生成函数对象:根据lambda表达式的参数、返回类型和捕获列表等信息,生成一个函数对象。
  3. 生成仿函数类:根据生成的函数对象,生成一个仿函数类(Functor),其中重载了函数调用运算符operator()
  4. 类型推导:根据lambda表达式中的代码和上下文,进行类型推导,确定函数对象的参数类型和返回类型。
  5. 生成代码:根据类型推导的结果,生成调用函数对象的代码。
  6. 调用lambda表达式:通过调用函数对象的operator(),执行lambda表达式中的代码。

排序案例

对于一个简单的数组来说:

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main(void)
{
  vector<int>arr = { 9,8,5,6,3,2,1,5,12,13,14,520 };
  sort(arr.begin(), arr.end());
  for (auto& e : arr)
  {
    cout << e << " ";
    e++;
  }
  cout << endl;
  sort(arr.begin(), arr.end(), greater<int>());
  for (auto& e : arr)
  {
    cout << e << " ";
    e++;
  }
  return 0;
}

这样排序和简单,但是实际开发当中都是在类当中排序,如下。

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
struct Goods
{
  string _name;  // 名字
  double _price; // 价格
  int _evaluate; // 评价
  Goods(const char* str, double price, int evaluate)
    :_name(str)
    , _price(price)
    , _evaluate(evaluate)
  {}
};
struct ComparePriceLess
{
  bool operator()(const Goods& gl, const Goods& gr)
  {
    return gl._price < gr._price;
  }
};
struct ComparePriceGreater
{
  bool operator()(const Goods& gl, const Goods& gr)
  {
    return gl._price > gr._price;
  }
};
int main(void)
{
  vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,3 }, { "菠萝", 1.5, 4 } };
  sort(v.begin(), v.end(), ComparePriceGreater());
  for (auto& e : v)
  {
    cout << e._name<<" "<<e._price<<" "<<e._evaluate<<endl;
  }
  cout << "==============================" << endl;
  sort(v.begin(), v.end(), ComparePriceLess());
  for (auto& e : v)
  {
    cout << e._name << " " << e._price << " " << e._evaluate << endl;
  }
  return 0;
}

每一种排序方式都要写一个类,然后重载operator(),这样是不是有点太麻烦了,因此,C++11增加了Lambda表达式,来简化这一过程。

如下是Lambda表达式的使用样例:

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
struct Goods
{
  string _name;  // 名字
  double _price; // 价格
  int _evaluate; // 评价
  Goods(const char* str, double price, int evaluate)
    :_name(str)
    , _price(price)
    , _evaluate(evaluate)
  {}
};
int main(void)
{
  vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,3 }, { "菠萝", 1.5, 4 } };
  sort(v.begin(), v.end(), [](Goods& s1, Goods& s2)->bool{
    return s1._price < s2._price;
    });
  for (auto& e : v)
  {
    cout << e._name<<" "<<e._price<<" "<<e._evaluate<<endl;
  }
  cout << "==============================" << endl;
  sort(v.begin(), v.end(), [](Goods& s1, Goods& s2)->bool {
    return s1._price > s2._price;
    });
  for (auto& e : v)
  {
    cout << e._name << " " << e._price << " " << e._evaluate << endl;
  }
  return 0;
}

其实Lambda表达式就是一个匿名函数。

Lambda表达式语法

语法格式:[捕捉列表](参数列表)mutable->返回类型{函数体}

捕捉列表 能够捕捉当前栈帧的所有对象(全局的也可以捕捉到)
参数列表 和函数中的参数列表一样,int a,int b这些
mutable 默认情况下Lambda表达式是一个const函数,不能被修改,而mutable可以取消const,可以对参数进行修改
返回类型 返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回 值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推 导。
函数体 函数的具体实现,在该函数体内,除了可以使用其参数外,还可以使用所有捕获 到的变量。

C++中最简单的Lambda表达式:[]{},但它并不能干什么。

#include<iostream>
using namespace std;
int main(void)
{
  int a = 1, b = 2,c=5;
  auto func1=[=] {return a + b; };
  cout << func1() << endl;
  auto func2 = [=](int a, int b) {return a + b + c; };
  cout << func2(a,b)<<endl;
  auto fun2 = [=, &b](int c)->int {return b += a + c; };
  cout << fun2(10) << endl;
  return 0;
}

捕捉列表

lambda函数在捕获外部变量时会创建一个闭包。闭包是指一个函数对象,它包含了函数定义时的环境信息,包括捕获的外部变量。通过捕获外部变量,lambda函数可以在其定义的作用域之外使用这些变量。闭包的存在使得lambda函数可以延长外部变量的生命周期,并且可以在函数调用结束后仍然访问这些变量。这是lambda函数的一个非常强大的特性。

[var] 捕捉值传递变量var
[=] 表达值传递的所有变量(当前栈帧)
[&var] 捕捉引用传递变量var
[&] 捕捉引用传递的所有变量(当前栈帧)、包括this
[this] 捕捉当前的this

Tip🎉:

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

比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量 [&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量

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

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

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

void (*PF)();
int main()
{
  auto f1 = []{cout << "hello world" << endl; };
  auto f2 = []{cout << "hello world" << endl; };
    // 此处先不解释原因,等lambda表达式底层实现原理看完后,大家就清楚了
  //f1 = f2;   // 编译失败--->提示找不到operator=()
    // 允许使用一个lambda表达式拷贝构造一个新的副本
  auto f3(f2);
  f3();
  // 可以将lambda表达式赋值给相同类型的函数指针
  PF = f2;
  PF();
  return 0;
}

Lambda表达式模拟

auto print = [] {cout << "hello world"; };
  print();

比如这样的一个表达式,机器会自动翻译为如下的代码:

class __lambda_Print_123
  {
  public:
    void operator()(void)
    {
      cout << "hello world" << endl;
    }
  };

还有这样的

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

会翻译成如下代码:

class __lambda_Add_123 {
  public:
    int operator()(int a, int b) const {
      return a + b;
    }
  };
目录
相关文章
|
6月前
|
存储 编译器 C语言
从C语言到C++_34(C++11_下)可变参数+ lambda+function+bind+笔试题(下)
从C语言到C++_34(C++11_下)可变参数+ lambda+function+bind+笔试题
91 5
|
2月前
|
算法 编译器 程序员
C++ 11新特性之Lambda表达式
C++ 11新特性之Lambda表达式
16 0
|
4月前
|
安全 编译器 C++
C++一分钟之-泛型Lambda表达式
【7月更文挑战第16天】C++14引入泛型lambda,允许lambda接受任意类型参数,如`[](auto a, auto b) { return a + b; }`。但这也带来类型推导失败、隐式转换和模板参数推导等问题。要避免这些问题,可以明确类型约束、限制隐式转换或显式指定模板参数。示例中,`safeAdd` lambda使用`static_assert`确保只对算术类型执行,展示了一种安全使用泛型lambda的方法。
60 1
|
5月前
|
算法 编译器 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++代码。
54 13
|
5月前
|
C++
C++语言的lambda表达式
C++从函数对象到lambda表达式以及操作参数化
|
5月前
|
C++
C++一分钟之-理解C++的运算符与表达式
【6月更文挑战第18C++的运算符和表达式构成了编程的基础,涉及数学计算、逻辑判断、对象操作和内存管理。算术、关系、逻辑、位、赋值运算符各有用途,如`+`、`-`做加减,`==`、`!=`做比较。理解运算符优先级和结合律至关重要。常见错误包括优先级混淆、整数除法截断、逻辑运算符误用和位运算误解。解决策略包括明确优先级、确保浮点数除法、正确使用逻辑运算符和谨慎进行位运算。通过实例代码学习,如 `(a &gt; b) ? &quot;greater&quot; : &quot;not greater&quot;`,能够帮助更好地理解和应用这些概念。掌握这些基础知识是编写高效、清晰C++代码的关键。
36 3
|
5月前
|
C语言 C++ 容器
c++primer plus 6 读书笔记 第五章 循环和关系表达式
c++primer plus 6 读书笔记 第五章 循环和关系表达式
|
5月前
|
计算机视觉 C++
【见微知著】OpenCV中C++11 lambda方式急速像素遍历
【见微知著】OpenCV中C++11 lambda方式急速像素遍历
49 0
|
5月前
|
C++
C++ lambda表达式
C++ lambda表达式
|
6天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
30 4