学习目标:
例如:
- 了解Lambda的优点
- 掌握Lambda表达式的使用
- 了解Lambda表达式的底层原理
学习内容:
- Lambda表达式的语法
文章目录
Lambda表达式
lambda表达式的底层实现涉及到闭包(Closure)的概念。闭包是一个函数对象,它可以捕获外部作用域中的变量,并在其生命周期内访问和修改这些变量。lambda表达式的底层实现就是通过创建闭包来实现的。
具体而言,lambda表达式在底层会被转化为一个函数对象。这个函数对象中包含了捕获的外部变量,并且重载了函数调用运算符
operator()
。函数对象可以像普通的函数一样被调用,其执行的代码就是lambda表达式中的代码。lambda表达式的展开过程包括以下几个步骤:
- 语法解析:将lambda表达式解析为函数对象的声明和定义。
- 生成函数对象:根据lambda表达式的参数、返回类型和捕获列表等信息,生成一个函数对象。
- 生成仿函数类:根据生成的函数对象,生成一个仿函数类(Functor),其中重载了函数调用运算符
operator()
。- 类型推导:根据lambda表达式中的代码和上下文,进行类型推导,确定函数对象的参数类型和返回类型。
- 生成代码:根据类型推导的结果,生成调用函数对象的代码。
- 调用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; } };