[C++11] Lambda 表达式

lambda 表达式(Lambda Expressions)作为一种匿名函数,为开发者提供了简洁、灵活的函数定义方式。相比传统的函数指针和仿函数,lambda 表达式在简化代码结构、提升代码可读性和编程效率方面表现出色。


Lambda 表达式的基本语法

在 C++ 中,lambda 表达式的格式如下:

cpp 复制代码
[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**:函数体,实现具体的功能。
cpp 复制代码
// 1、捕捉为空也不能省略
// 2、参数为空可以省略
// 3、返回值可以省略,可以通过返回对象⾃动推导
// 4、函数题不能省略
auto func1 = []
{
    cout << "hello bit" << endl;
    
    return 0;
};

示例代码

以下是一个简单的 lambda 表达式示例:

cpp 复制代码
auto add = [](int x, int y) -> int {
    return x + y;
};
std::cout << add(3, 5) << std::endl; // 输出:8

捕捉列表的使用

捕捉列表的分类

捕捉列表可以在 lambda 表达式中允许访问外部作用域的变量。捕捉方式主要包括:

  1. 显式捕捉 :在捕捉列表中显⽰的传值捕捉和传引⽤捕捉,捕捉的多个变量⽤逗号分割,例如在捕捉列表中使用传值[x, y]或传引用[&z]捕捉变量。
  2. 隐式捕捉 :使用 =(按值捕捉)或 &(按引用捕捉)进行隐式捕捉,这样我们 lambda 表达式中⽤了哪些变量,编译器就会⾃动捕捉那些变量,底层汇编也是这样的。
  3. 混合捕捉 :可混合显式与隐式捕捉,<font style="color:rgb(31,35,41);">[=, &x]</font>表⽰其他变量隐式值捕捉,x引⽤捕捉;<font style="color:rgb(31,35,41);">[&, x, y]</font>表⽰其他变量引⽤捕捉,x和y值捕捉。当使⽤混合捕捉时,第⼀个元素必须是&或=,并且&混合捕捉时,后⾯的捕捉变量必须是值捕捉,同理=混合捕捉时,后⾯的捕捉变量必须是引⽤捕捉。

使用时的注意事项

  1. 全局位置的<font style="color:rgb(31,35,41);">lambda</font>

lambda 表达式如果在函数局部域中,他可以捕捉 lambda 位置之前定义的变量。但是不能捕捉静态局部变量和全局变量,静态局部变量和全局变量也不需要捕捉,lambda 表达式中可以直接使⽤。这也意味着 lambda 表达式如果定义在全局位置,捕捉列表不需要捕捉任何变量,必须为空

cpp 复制代码
// 全局
auto f1 = [](int a, int b){
    return a + b;
}
  1. mutable修饰lambda

默认情况下, lambda 捕捉列表是被const修饰的,也就是说传值捕捉的过来的对象不能修改,mutable加在参数列表的后⾯可以取消其常量性,也就说使⽤该修饰符后,传值捕捉的对象就可以修改了,但是修改还是形参对象,不会影响实参。使⽤该修饰符后,参数列表不可省略(即使参数为空)。

cpp 复制代码
int a = 5, b = 10;
auto func = [=]() mutable {
    a++;         // 可以修改按引用捕捉的变量
    b++;
};

注意 :默认情况下捕捉的变量为 const,无法在 lambda 中修改,除非在参数列表后加 mutable 修饰符,如上例中的 mutable

捕捉的具体示例

  1. 显式捕捉:
cpp 复制代码
int a = 0, b = 1, c = 2, d = 3;
auto func1 = [a, &b]
	{
		// 值捕捉的变量不能修改,引⽤捕捉的变量可以修改
		//a++;
		b++;
		int ret = a + b;
		return ret;
	};
cout << func1() << endl;
  1. 隐式值捕捉 / 隐式引用捕捉:
cpp 复制代码
// 隐式值捕捉
// ⽤了哪些变量就捕捉哪些变量
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. 混合捕捉:
cpp 复制代码
// 混合捕捉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 表达式

相当于直接替代仿函数来使用。

cpp 复制代码
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(),并且捕捉的变量会作为构造函数的参数。如下所示:

cpp 复制代码
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的底层就是仿函数。

相关推荐
向宇it40 分钟前
【从零开始入门unity游戏开发之——C#篇26】C#面向对象动态多态——接口(Interface)、接口里氏替换原则、密封方法(`sealed` )
java·开发语言·unity·c#·游戏引擎·里氏替换原则
@菜鸟进阶记@43 分钟前
java根据Word模板实现动态填充导出
java·开发语言
卖芒果的潇洒农民1 小时前
Lecture 6 Isolation & System Call Entry
java·开发语言
SomeB1oody1 小时前
【Rust自学】6.1. 定义枚举
开发语言·后端·rust
SomeB1oody1 小时前
【Rust自学】5.3. struct的方法(Method)
开发语言·后端·rust
无 证明2 小时前
new 分配空间;引用
数据结构·c++
Kisorge2 小时前
【C语言】指针数组、数组指针、函数指针、指针函数、函数指针数组、回调函数
c语言·开发语言
轻口味3 小时前
命名空间与模块化概述
开发语言·前端·javascript
晓纪同学4 小时前
QT-简单视觉框架代码
开发语言·qt
威桑4 小时前
Qt SizePolicy详解:minimum 与 minimumExpanding 的区别
开发语言·qt·扩张策略