【C++】C++11 Lambda表达式

目录

[1. Lambda 基础知识](#1. Lambda 基础知识)

[1.1 基本语法](#1.1 基本语法)

[1.2 捕获列表](#1.2 捕获列表)

[1.3 基础示例](#1.3 基础示例)

示例1

示例2

示例3

示例4

示例5

[2. Lambda 的原理](#2. Lambda 的原理)

[2.1 编译器背后生成](#2.1 编译器背后生成)

[2.2 编译器生成细节](#2.2 编译器生成细节)

[2.3 lambda调用与执行流程](#2.3 lambda调用与执行流程)

[2.4 小结](#2.4 小结)

[3. Lambda 的典型应用场景](#3. Lambda 的典型应用场景)

[3.1 传统写法:定义独立仿函数结构体](#3.1 传统写法:定义独立仿函数结构体)

[3.2 Lambda 写法:就地写逻辑,极简高效](#3.2 Lambda 写法:就地写逻辑,极简高效)


1. Lambda 基础知识

Lambda 表达式是 C++11 引入的核心特性之一,本质是匿名函数对象,可以在代码中就地定义、就地使用,无需像普通函数那样单独声明 / 定义,极大简化了回调函数、临时逻辑的编写。

1.1 基本语法

Lambda 的完整语法格式如下(方括号 [] 和花括号 {} 是必须的,其余部分可选):

复制代码
[capture](parameters) mutable noexcept -> return_type {
    // 函数体(执行逻辑)
}

各部分含义:

部分 作用
[capture] 捕获列表:指定从外部作用域捕获哪些变量(值 / 引用)到 Lambda 内部使用
(parameters) 参数列表:和普通函数的参数列表一致(可省略,无参数时可直接写 () 或省略)
mutable 可选:允许修改按值捕获的变量(默认按值捕获的变量是 const 的)
noexcept 可选:声明 Lambda 不会抛出异常
-> return_type 返回值类型:可省略(C++11 起编译器会自动推导返回值类型)
{} 函数体:Lambda 要执行的逻辑

1.2 捕获列表

Lambda 表达式中,默认只能直接使用函数体内部定义的变量参数列表中的参数 、**全局变量 / 静态变量,**若要使用外层局部作用域的非静态变量则必须通过捕获列表捕获。

捕获列表决定了 Lambda 能否访问外部变量,以及访问方式(值 / 引用),常见用法:

捕获方式 语法 含义
空捕获 [] 不捕获任何外部变量
显式值捕获 [x,y] 拷贝x、y到Lambda内部,默认是const,需加mutable才能修改,修改仅影响副本
显式引用捕获 [&x,&y] 引用x、y,指向原变量,修改会影响外部
隐式值捕获 [=] 捕获所有用到的外部变量(Lambda 内是副本,默认不可修改,若修改加mutable)
隐式引用捕获 [&] 捕获所有用到的外部变量(Lambda 内操作的是原变量,修改会影响外部)
混合捕获 [=, &x] 默认值捕获,除 x 按引用捕获,其余变量按值捕获
混合捕获 [&, x] 默认引用捕获,除 x 按值捕获,其余变量按引用捕获
捕获this [this] 捕获当前类的 this 指针(成员函数内的 Lambda 可访问类的成员变量 / 函数)

1.3 基础示例

示例1

最简单的 Lambda(无捕获、无参数、无返回值):

复制代码
int main()
{
	//lambda表达式本身(定义一个匿名类对象)后面的 () → 立刻调用这个对象的 operator()
	[]() {
		cout << "hello" << endl;
		}();

	//简化写法(无参数时可省略 ())
	[] {
		cout << "hello" << endl;
		}();

	return 0;
}

示例2

带参数和返回值的lambda:

Lambda 表达式被编译器处理后,会生成一个匿名的、独一无二的函数对象类型,这个类型没有名字,无法手动写出它的类型名,所以一般用auto或模版参数接收lambda对象。

复制代码
int main() 
{
	// 显式指定返回值类型(-> int)
	auto add = [](int x, int y) -> int {
		return x + y;
		};
	cout << add(3, 5) << endl;  // 输出:8

	// 省略返回值类型(编译器自动推导)
	auto multiply = [](int x, int y) {
		return x * y;  // 推导返回值为 int
		};
	cout << multiply(4, 6) << endl; // 输出:24

	return 0;
}

示例3

捕获外部变量:

复制代码
int main() 
{
	int a = 10, b = 20;

	// 1. 按值捕获 a,按引用捕获 b
	auto lambda1 = [a, &b]() {
		// a = 100; 错误!按值捕获的变量默认不可修改(加mutable才可修改)
		b = 200;  // 正确!按引用捕获可修改原变量
		cout << "a = " << a << ", b = " << b << endl;  // 输出:a=10, b=200
		};
	lambda1();
	cout << "外部 b = " << b << endl;  // 输出:外部 b=200(引用捕获修改了原变量)

	// 2. 按值捕获 + mutable(允许修改捕获的副本)
	auto lambda2 = [a]() mutable {
		a = 100;  // 正确!mutable 解除 const 限制
		cout << "lambda2 内 a = " << a << endl;  // 输出:100
		};
	lambda2();
	cout << "外部 a = " << a << endl;  // 输出:10(值捕获仅修改副本,不影响原变量)

	return 0;
}

int main()
{
	int a = 30, b = 40, c = 50;

	// 1. 隐式值捕获([=]) 捕获所有用到的
	auto lambda3 = [=]() mutable {
		a = 3000;
		b = 4000;
		return a + b; //编译器不会捕获c,只会捕获a、b
		};
	cout << lambda3() << endl; // 7000
	cout << "外部 a=" << a << ", b=" << b << endl;  // 输出:30,40(值捕获不影响原变量)

	// 2. 隐式引用捕获([&])
	auto lambda4 = [&]() mutable {
		a = 3000;
		b = 4000;
		return a + b; //编译器不会捕获c,只会捕获a、b
		};
	cout << lambda4() << endl; // 7000
	cout << "外部 a=" << a << ", b=" << b << endl;  // 输出:3000,4000(引用捕获修改原变量)

	return 0;
}

int main()
{
	int a = 3, b = 4, c = 5, d = 6;
	//混合捕捉:默认按引用捕捉,a、b按值捕捉
	auto func4 = [&, a, b]
	{
			//a++; error
			//b++;
			c++;
			d++;
			return a + b + c + d;
	};
	cout << func4() << endl; // 20 
	cout << a << " " << b << " " << c << " " << d << endl; // 3 4 6 7

	//混合捕捉:默认按值捕捉,a、b按引用捕捉
	auto func5 = [=, &a, &b]
		{
			a++;
			b++;
			//c++; error
			//d++;
			return a + b + c + d;
		};

	cout << func5() << endl; // 22
	cout << a << " " << b << " " << c << " " << d << endl; // 4 5 6 7

	return 0;
}

示例4

全局作用域的 Lambda:

全局 / 静态变量无需捕获,Lambda 无论定义在何处,都可以直接访问全局 / 静态变量,因此全局 Lambda 的捕获列表必须为空,因为全局作用域没有 "局部变量" 可捕获。

复制代码
// 全局变量(全局作用域)
int global_var = 10;

// 全局静态变量(仅当前文件可见)
static int static_global_var = 20;

// 1. 全局Lambda(定义在全局作用域)
// 全局Lambda的捕获列表必须为空([]),但可直接访问全局/静态变量
auto global_lambda = []() {	
	global_var = 100;// 直接访问并修改全局变量(无需捕获)	
	static_global_var = 200;// 直接访问并修改全局静态变量(无需捕获)
	};

// 2. 局部Lambda(定义在函数内)
void test() 
{
	// 函数内静态变量
	static int static_local_var = 30;
	// 函数内局部变量(需要捕获才能访问)
	int local_var = 40;

	// 局部Lambda:捕获局部变量local_var,直接访问全局/静态变量
	auto local_lambda = [local_var]() mutable {
		
		global_var = 1000; // 直接修改全局变量(无需捕获)		
		static_global_var = 2000; // 直接修改全局静态变量(无需捕获)		
		static_local_var = 3000;// 直接修改函数内静态变量(无需捕获)		
		local_var = 4000; // 修改捕获的局部变量(需要mutable)

		};

	local_lambda();
}

int main() 
{
	global_lambda(); // 执行全局Lambda
	test(); // 执行局部Lambda

	return 0;
}

示例5

类成员函数中的 Lambda(捕获 this):

复制代码
class MyClass 
{
private:
	int num = 100;
public:
	void test() 
	{
		// 捕获this指针,访问类的成员变量
		auto lambda = [this]() {
			num = 200;  // 等价于 this->num = 200
			cout << num << endl;  // 输出:200
			};
		lambda();
	}
};

int main() 
{
	MyClass obj;
	obj.test();
	return 0;
}

2. Lambda 的原理

Lambda 和范围 for一样,都是 C++ 语法糖!范围 for 底层是迭代器遍历, lambda 底层是仿函数对象, 编译之后,它们都不复存在。
lambda = 编译器自动生成的匿名类 + 自动创建的匿名对象

示例代码:

复制代码
int main()
{
	double rate = 0.49;

	auto r = [rate](double money, int year)
		{
			return money * rate * year;
		};

	r(10000, 2);

	return 0;
}

2.1 编译器背后生成

编译器识别 [...] 结构,确定这是一个 Lambda 表达式后在背后偷偷生成如下匿名类与对象:

复制代码
//创建匿名仿函数类(闭包类)
class __lambda_abc123 //编译器自动生成唯一类名
{
    // 捕获的变量 → 变成【成员变量】值捕获->拷贝成员 引用捕获->引用成员
    double _rate;

public:
    // 捕获变量 → 变成【构造函数参数】构造函数执行时,会将外部变量rate的值,赋值给类的私有成员变量_rate
    __lambda_abc123 (double rate)
        : _rate(rate)
    {}

    // lambda 函数体 → 变成 operator()
    double operator()(double money, int year) const
    {
        // 用的是【成员变量 _rate】
        return money * _rate * year;
    }
};

//创建匿名仿函数类对象
__lambda_abc123 r(rate); 

2.2 编译器生成细节

  • 生成匿名类:编译器会自动生成一个独一无二的匿名仿函数类( 类名是随机且唯一的,如__lambda_123**)。**

  • 捕获列表处理 :捕获的变量会转化为类的私有成员变量

    • 引用捕获 → 引用成员变量
    • 值捕获 → 拷贝成员变量
  • 构造函数生成:生成带参数的构造函数,用外部捕获的变量初始化类成员。

  • 函数体映射 :Lambda 函数体直接作为 operator() 的实现。默认情况下 operator() 会自动添加 const 修饰,若要修改捕获变量,需添加 mutable 关键字。
    auto r 接收匿名对象,用这个匿名对象初始化 r ,得到可调用变量 r 。

    __lambda_abc123 r = __lambda_abc123(rate);
    //或者编译器优化为__lambda_abc123 r(rate);

  • auto自动推导出编译器生成的匿名仿函数类类型(__lambda_abc123),因为这个类型名我们无法手动书写,只能靠auto接收。

  • 让原本"匿名、无法直接使用"的仿函数对象,有了一个可用的变量名r ,后续就可以通过r(...)调用函数。

2.3 lambda调用与执行流程

当代码执行 r(10000, 2) 时,编译器会将其翻译为对operator()的显式调用:

复制代码
r.operator()(10000, 2);

执行流程:

  1. 调用 r 对象的 operator() 方法,传入参数 money = 10000year = 2
  2. 方法内部使用已保存的成员变量 _rate = 0.49 参与计算。
  3. 返回结果:10000 * 0.49 * 2 = 9800

2.4 小结

  • 编译器会将 Lambda 表达式自动生成一个独一无二的匿名类。
  • 捕获的变量会成为该匿名类的成员变量 ,函数体成为 operator() 的实现。
  • auto 推导为该匿名类类型,调用 r(...) 本质是调用 r.operator()。
  • 整个过程是纯编译期行为,运行时无额外开销,和手写类效率完全一致。

3. Lambda 的典型应用场景

STL 算法的回调函数是 Lambda 表达式最经典、最常用的应用场景。在 C++ 标准库提供的排序、查找、遍历、统计等算法中,往往需要用户自定义比较规则、筛选条件或处理逻辑,而 Lambda 凭借语法简洁、逻辑内聚、可就地定义等优势,能够完美替代传统的函数指针与仿函数,让算法的使用更加灵活高效,成为开发中最主流的选择。
在 C++ 开发中,我们经常需要对自定义类型(如商品、学生等)进行排序,std::sort 算法允许传入一个比较函数 / 仿函数来定义排序规则。

自定义类型示例:

复制代码
struct Goods
{
	string _name;  // 名字
	double _price; // 价格
	int _evaluate; // 评价
	// ...
	Goods(const char* str, double price, int evaluate)
		:_name(str)
		, _price(price)
		, _evaluate(evaluate)
	{}
};

3.1 传统写法:定义独立仿函数结构体

在 C++11 之前,要给sort传自定义比较规则,必须单独定义仿函数结构体 (重载 operator())。

复制代码
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());

	return 0;
}

传统写法的缺点:

  • 代码冗余: 为了一个小小的比较逻辑,必须单独写结构体。
  • 可读性差: 逻辑定义和使用位置分离,需要跳转查看比较规则。
  • 维护成本高:比较规则越多(如按评价升序 / 降序),需要定义的结构体数量越多,容易造成类爆炸。

3.2 Lambda 写法:就地写逻辑,极简高效

C++11 引入 Lambda 后,我们可以在sort调用处直接写比较逻辑,无需额外定义结构体。

复制代码
int main()
{
	vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3}, { "菠萝", 1.5, 4 } };

	// 直接在sort里面写比较规则,随用随写
	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;
		});
		
	return 0;
}

Lambda 写法的优点:

  • 无额外定义:不需要写任何仿函数结构体,代码更简洁。
  • 逻辑内聚:比较逻辑直接写在sort调用处,一眼就能看懂排序规则。
  • 灵活高效:新增 / 修改排序规则只需改一行 Lambda,不会产生大量无用结构体。
  • 代码量更少:对比传统写法,行数大幅减少,可读性和可维护性显著提升。

Lambda 不仅适合sort,在所有需要自定义谓词 / 回调的 STL 算法中都有广泛应用:

  • std::find_if:查找满足条件的元素
  • std::count_if:统计满足条件的元素个数
  • std::for_each:遍历容器并执行自定义操作
  • std::transform:对容器元素做转换
    Lambda 是 STL 算法自定义回调逻辑的最佳实践,它完美解决了传统仿函数写法的冗余、可读性差、维护成本高的问题,让代码更简洁、更直观、更灵活。
相关推荐
南境十里·墨染春水2 小时前
CMake核心用法(贴合C++编译场景)
开发语言·c++
Rust语言中文社区2 小时前
【Rust日报】 Danube Messaging - 云原生消息平台
开发语言·后端·rust
kaikaile19952 小时前
微电网两阶段鲁棒优化经济调度MATLAB实现
开发语言·matlab
liuyao_xianhui2 小时前
优选算法_栈_删除字符中的所有相邻重复项_C++
开发语言·数据结构·c++·python·算法·leetcode·链表
逝水如流年轻往返染尘2 小时前
JAVA中的内部类
java·开发语言
always_TT2 小时前
C语言中的布尔值:_Bool与stdbool.h
c语言·开发语言
tankeven2 小时前
HJ154 kotori和素因子
c++·算法
!停2 小时前
C++入门基础—类和对象3
java·数据库·c++
寂静or沉默2 小时前
Java程序员技术面试:如何清晰描述项目难点?逻辑模板!Java的原因与解决方案最新发布!
java·开发语言·面试