lambda 表达式(Lambda Expressions)作为一种匿名函数,为开发者提供了简洁、灵活的函数定义方式。相比传统的函数指针和仿函数,lambda 表达式在简化代码结构、提升代码可读性和编程效率方面表现出色。
Lambda 表达式的基本语法
在 C++ 中,lambda 表达式的格式如下:
[capture-list] (parameters) -> return type {
function body
}
各部分含义:
**[capture-list]**
:捕捉列表,指定lambda
表达式可以访问的外部变量。捕捉列表,该列表总是出现在<font style="color:rgb(31,35,41);">lambda</font>
函数的开始位置,编译器根据[]来判断接下来的代码是否为<font style="color:rgb(31,35,41);">lambda</font>
函数,捕捉列表能够捕捉上下⽂中的变量供<font style="color:rgb(31,35,41);">lambda</font>
函数使⽤,捕捉列表可以传值和传引⽤捕捉,捕捉列表为空也不能省略。**(parameters)**
:参数列表,类似普通函数的参数,如果不需要参数传递,则可以连同<font style="color:rgb(31,35,41);">( )</font>
⼀起省略。**-> return type**
:返回值类型。通常可以省略,由编译器推导。**function body**
:函数体,实现具体的功能。
// 1、捕捉为空也不能省略
// 2、参数为空可以省略
// 3、返回值可以省略,可以通过返回对象⾃动推导
// 4、函数题不能省略
auto func1 = []
{
cout << "hello bit" << endl;
return 0;
};
示例代码
以下是一个简单的 lambda 表达式示例:
auto add = [](int x, int y) -> int {
return x + y;
};
std::cout << add(3, 5) << std::endl; // 输出:8
捕捉列表的使用
捕捉列表的分类
捕捉列表可以在 lambda 表达式中允许访问外部作用域的变量。捕捉方式主要包括:
- 显式捕捉:在捕捉列表中显⽰的传值捕捉和传引⽤捕捉,捕捉的多个变量⽤逗号分割,例如在捕捉列表中使用传值
[x, y]
或传引用[&z]
捕捉变量。 - 隐式捕捉:使用
=
(按值捕捉)或&
(按引用捕捉)进行隐式捕捉,这样我们 lambda 表达式中⽤了哪些变量,编译器就会⾃动捕捉那些变量,底层汇编也是这样的。 - 混合捕捉:可混合显式与隐式捕捉,
<font style="color:rgb(31,35,41);">[=, &x]</font>
表⽰其他变量隐式值捕捉,x引⽤捕捉;<font style="color:rgb(31,35,41);">[&, x, y]</font>
表⽰其他变量引⽤捕捉,x和y值捕捉。当使⽤混合捕捉时,第⼀个元素必须是&或=,并且&混合捕捉时,后⾯的捕捉变量必须是值捕捉,同理=混合捕捉时,后⾯的捕捉变量必须是引⽤捕捉。
使用时的注意事项
- 全局位置的
<font style="color:rgb(31,35,41);">lambda</font>
lambda 表达式如果在函数局部域中,他可以捕捉 lambda 位置之前定义的变量。但是不能捕捉静态局部变量和全局变量,静态局部变量和全局变量也不需要捕捉,lambda 表达式中可以直接使⽤。这也意味着 lambda 表达式如果定义在全局位置,捕捉列表不需要捕捉任何变量,必须为空。
// 全局
auto f1 = [](int a, int b){
return a + b;
}
mutable
修饰lambda
默认情况下, lambda 捕捉列表是被const修饰的,也就是说传值捕捉的过来的对象不能修改,mutable加在参数列表的后⾯可以取消其常量性,也就说使⽤该修饰符后,传值捕捉的对象就可以修改了,但是修改还是形参对象,不会影响实参。使⽤该修饰符后,参数列表不可省略(即使参数为空)。
int a = 5, b = 10;
auto func = [=]() mutable {
a++; // 可以修改按引用捕捉的变量
b++;
};
注意:默认情况下捕捉的变量为
const
,无法在 lambda 中修改,除非在参数列表后加mutable
修饰符,如上例中的mutable
。
捕捉的具体示例
- 显式捕捉:
int a = 0, b = 1, c = 2, d = 3;
auto func1 = [a, &b]
{
// 值捕捉的变量不能修改,引⽤捕捉的变量可以修改
//a++;
b++;
int ret = a + b;
return ret;
};
cout << func1() << endl;
- 隐式值捕捉 / 隐式引用捕捉:
// 隐式值捕捉
// ⽤了哪些变量就捕捉哪些变量
auto func2 = [=] {
int ret = a + b + c;
return ret;
};
cout << func2() << endl;
// 隐式引用捕捉
// ⽤了哪些变量就捕捉哪些变量
auto func3 = [&] {
a++;
c++;
d++;
};
func3();
cout << a << " " << b << " " << c << " " << d << endl;
- 混合捕捉:
// 混合捕捉1
auto func4 = [&, a, b] {
//a++;
//b++;
c++;
d++;
return a + b + c + d;
};
func4();
cout << a << " " << b << " " << c << " " << d << endl;
// 混合捕捉2
auto func5 = [=, &a, &b] {
a++;
b++;
/*c++;
d++;*/
return a + b + c + d;
};
func5();
cout << a << " " << b << " " << c << " " << d << endl;
Lambda 表达式的应用场景
常见应用
- 排序函数中的比较器:利用 lambda 表达式可简化排序代码。
- 自定义线程执行函数:lambda 可定义线程任务,便于封装。
- 智能指针的删除器:lambda 表达式可以方便地作为
unique_ptr
等的自定义删除器。
示例:用于商品排序的 Lambda 表达式
相当于直接替代仿函数来使用。
struct Goods {
std::string name;
double price;
int rating;
Goods(const std::string &n, double p, int r) : name(n), price(p), rating(r) {}
};
std::vector<Goods> items = {{"苹果", 2.5, 5}, {"橙子", 3.0, 4}, {"香蕉", 1.5, 3}};
// 使用 lambda 表达式按价格升序排序
std::sort(items.begin(), items.end(), [](const Goods &a, const Goods &b) {
return a.price < b.price;
});
Lambda 表达式的原理
Lambda 表达式在底层通过创建一个仿函数对象来实现。当我们定义一个 lambda 表达式时,编译器会生成一个包含捕捉列表和函数体的匿名类,lambda 表达式实际上是该类的一个 operator()
,底层是仿函数对象。
汇编层的实现
通过汇编代码可看到,lambda 表达式生成的对象会自动调用 operator()
,并且捕捉的变量会作为构造函数的参数。如下所示:
class Rate
{
public:
Rate(double rate)
: _rate(rate)
{}
double operator()(double money, int year)
{
return money * _rate * year;
}
private:
double _rate;
};
int main()
{
double rate = 0.49;
// lambda
auto r2 = [rate](double money, int year) {
return money * rate * year;
};
// 函数对象
Rate r1(rate);
r1(10000, 2); // 仿函数
r2(10000, 2); // lambda
auto func1 = [] {
std:: cout << "hello world" << std::endl;
};
func1();
return 0;
}
从call
的内容不难看出lambda
的底层就是仿函数。