前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >【c++11】lambda表达式

【c++11】lambda表达式

作者头像
用户11029103
发布2025-01-24 08:03:53
发布2025-01-24 08:03:53
5600
代码可运行
举报
文章被收录于专栏:技术学习技术学习
运行总次数:0
代码可运行

1.lambda表达式

在C++98中,如果想要对一个数据集合中的元素进行排序,可以使用std::sort方法

代码语言:javascript
代码运行次数:0
复制
#include <algorithm>
#include <functional>
int main()
{
	int array[] = { 4,1,8,5,3,7,0,9,2,6 };
	// 默认按照小于比较,排出来结果是升序
	std::sort(array, array + sizeof(array) / sizeof(array[0]));
	// 如果需要降序,需要改变元素的比较规则
	std::sort(array, array + sizeof(array) / sizeof(array[0]), greater<int>());
	return 0;
}

如果待排序元素为自定义类型,需要用户定义排序时的比较规则:

代码语言:javascript
代码运行次数:0
复制
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()
{
	vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,
   3 }, { "菠萝", 1.5, 4 } };
	sort(v.begin(), v.end(), ComparePriceLess());
	sort(v.begin(), v.end(), ComparePriceGreater());
}

随着C++语法的发展,人们开始觉得上面的写法太复杂了,每次为了实现一algorithm算法,都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了极大的不便。因此,在C++11语法中出现了Lambda表达式

代码语言:javascript
代码运行次数:0
复制
int main()
{
	vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,
   3 }, { "菠萝", 1.5, 4 } };
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
		return g1._price < g2._price; });
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
		return g1._price > g2._price; });
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
		return g1._evaluate < g2._evaluate; });
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
		return g1._evaluate > g2._evaluate; });
}

2.lambda表达式语法

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

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

代码语言:javascript
代码运行次数:0
复制
int main()
{
	// 最简单的lambda表达式, 该lambda表达式没有任何意义
	[] {};

	// 省略参数列表和返回值类型,返回值类型由编译器推导为int
	int a = 3, b = 4;
	[=] {return a + 3; };

	// 省略了返回值类型,无返回值类型
	auto fun1 = [&](int c) {b = a + c; };
	fun1(10);
		cout << a << " " << b << endl;

	// 各部分都很完善的lambda函数
	auto fun2 = [=, &b](int c)->int {return b += a + c; };
	cout << fun2(10) << endl;

	// 复制捕捉x
	int x = 10;
	auto add_x = [x](int a) mutable { x *= 2; return a + x; };
	cout << add_x(10) << endl;
	return 0;
}

通过上述例子可以看出,lambda表达式实际上可以理解为无名函数,该函数无法直接调用,如果想要直接调用,可借助auto将其赋值给一个变量

代码语言:javascript
代码运行次数:0
复制
struct ComparePriceGreater
{
	bool operator()(const Goods& gl, const Goods& gr)
	{
		return gl._price > gr._price;
	}
};
int main()
{
	vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,
   3 }, { "菠萝", 1.5, 4 } };
	sort(v.begin(), v.end(), ComparePriceGreater());

	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
		return g1._price < g2._price; });
}

Lambda 的代码:

代码语言:javascript
代码运行次数:0
复制
[](const Goods& g1, const Goods& g2) {
    return g1._price < g2._price;
}

编译器实际上会生成一个类似于以下的匿名类

代码语言:javascript
代码运行次数:0
复制
class LambdaGeneratedClass {
public:
    bool operator()(const Goods& g1, const Goods& g2) const {
        return g1._price < g2._price;
    }
};

这表明,Lambda 是由编译器生成的一个仿函数对象,但它不是手动定义的仿函数。


Lambda 和仿函数的本质区别

特性

仿函数

Lambda

定义方式

明确定义类,手动实现 operator()

使用匿名语法,编译器生成类

状态捕获

静态状态(类的成员变量)

支持捕获外部变量,动态状态捕获

语法简洁性

需要额外定义类,使用略繁琐

直接内联编写,简洁明了

使用场景

可在多处复用,逻辑复杂的情况适用

适用于简短的、局部的逻辑


从语法上,Lambda 和仿函数都是通过实现 operator() 的方式实现可调用性。因此,Lambda 在功能上可以视为仿函数的一个特例。但从本质上看,Lambda 是编译器生成的匿名类的实例,而仿函数是用户手动定义的类。可以认为 Lambda 是一种更灵活、更轻量的仿函数形式

3.捕获列表

捕获列表位于 Lambda 表达式的 [] 部分,用于指定捕获的变量以及捕获的方式

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

  • [var]:表示值传递方式捕捉变量var
  • [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
  • [&var]:表示引用传递捕捉变量var
  • [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
  • [this]:表示值传递方式捕捉当前的this指针

注意:

  • a. 父作用域指包含lambda函数的语句块
  • b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
    • 比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量
    • [&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量
  • c. 捕捉列表不允许变量重复传递,否则就会导致编译错误。
    • 比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复
  • d. 在块作用域以外的lambda函数捕捉列表必须为空。
  • e. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都 会导致编译报错。
  • f. lambda表达式之间不能相互赋值,即使看起来类型相同
代码语言:javascript
代码运行次数:0
复制
int x = 10;
auto lambda = [x]() { 
    // 捕获 x 的副本,x 的值是 10
    std::cout << x << std::endl; 
};
x = 20;  // 外部修改 x
lambda(); // 输出 10,不受外部 x 变化影响

如果希望按值捕获的变量可修改,可以使用 mutable

代码语言:javascript
代码运行次数:0
复制
int x = 10;
auto lambda = [x]() mutable { 
    x++;  // 允许修改捕获的副本
    std::cout << x << std::endl; 
};
lambda(); // 输出 11

语法:[&var]

  • Lambda 捕获变量的引用,直接访问外部变量。
  • 捕获的变量对 Lambda 来说是可修改的。
  • 特点:Lambda 内部对变量的修改会直接影响外部变量。

示例:

代码语言:javascript
代码运行次数:0
复制
int x = 10;
auto lambda = [&x]() { 
    x++;  // 修改外部变量 x
    std::cout << x << std::endl; 
};
lambda(); // 输出 11
std::cout << x << std::endl; // 输出 11

语法:[=]

捕获外部作用域中所有变量的副本(按值)。 特点:所有捕获的变量对 Lambda 来说是只读的,除非使用 mutable 修饰符。

代码语言:javascript
代码运行次数:0
复制
int x = 10, y = 20;
auto lambda = [=]() { 
    std::cout << x + y << std::endl; 
};
lambda(); // 输出 30

按引用捕获所有变量 语法:[&]

捕获外部作用域中所有变量的引用。 特点:Lambda 内部可以修改外部变量。

代码语言:javascript
代码运行次数:0
复制
int x = 10, y = 20;
auto lambda = [&]() { 
    x++;
    y++;
    std::cout << x + y << std::endl; 
};
lambda(); // 输出 32
std::cout << x << ", " << y << std::endl; // 输出 11, 21

混合捕获

  • 可以同时使用按值捕获和按引用捕获,明确指定每个变量的捕获方式。
  • 语法:[var1, &var2]

示例:

代码语言:javascript
代码运行次数:0
复制
int x = 10, y = 20;
auto lambda = [x, &y]() { 
    // x 是按值捕获,y 是按引用捕获
    std::cout << "x = " << x << ", y = " << y << std::endl;
    y++;
};
lambda(); // 输出 x = 10, y = 20
std::cout << y << std::endl; // 输出 21

默认捕获和显式捕获

  • 如果默认捕获(如 [=][&])已经定义,还可以显式指定部分变量的捕获方式。
  • 示例:
代码语言:javascript
代码运行次数:0
复制
int x = 10, y = 20, z = 30;
auto lambda = [=, &z]() { 
    // 默认按值捕获,但 z 是按引用捕获
    std::cout << x << ", " << y << ", " << z << std::endl; 
    z++;
};
lambda(); // 输出 10, 20, 30
std::cout << z << std::endl; // 输出 31

捕获列表注意事项

无法捕获临时变量 捕获列表中只能捕获作用域中的变量,不能捕获临时对象或表达式结果。例如:

代码语言:javascript
代码运行次数:0
复制
auto lambda = [x + 1]() {}; // 错误:不能直接捕获表达式

解决方法:通过初始化捕获:

代码语言:javascript
代码运行次数:0
复制
auto lambda = [val = x + 1]() { std::cout << val << std::endl; };

初始化捕获(C++14 引入) 捕获列表中可以使用初始化捕获,为捕获的变量指定初值。语法为 [var = init_value]

代码语言:javascript
代码运行次数:0
复制
int x = 10;
auto lambda = [val = x + 5]() { std::cout << val << std::endl; };
lambda(); // 输出 15

无法捕获静态变量 Lambda 不需要捕获静态变量,因为静态变量的生命周期贯穿整个程序。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-01-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.lambda表达式
  • 2.lambda表达式语法
    • Lambda 和仿函数的本质区别
  • 3.捕获列表
    • 捕获列表注意事项
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档