文章目录
- [C++11 lambda表达式使用讲解](#C++11 lambda表达式使用讲解)
-
- [一、lambda 表达式的基本语法](#一、lambda 表达式的基本语法)
- [二、lambda 表达式的使用示例](#二、lambda 表达式的使用示例)
-
- [2.1 基础用法(无捕获、无参数)](#2.1 基础用法(无捕获、无参数))
- [2.2 捕获外部变量](#2.2 捕获外部变量)
-
- [2.2.1 捕获列表](#2.2.1 捕获列表)
- [2.2.2 捕获的三种主要方式](#2.2.2 捕获的三种主要方式)
-
- (1)显式捕获(按值/按引用)
- (2)隐式捕获
- [(3)混合捕获(显式 + 隐式)](#(3)混合捕获(显式 + 隐式))
- [2.2.3 特殊变量的捕获规则](#2.2.3 特殊变量的捕获规则)
- [2.2.4 `mutable` 修饰符与捕获的可修改性](#2.2.4
mutable
修饰符与捕获的可修改性)
- [2.3 带参数和返回值](#2.3 带参数和返回值)
- [2.4 作为参数传递(回调函数)](#2.4 作为参数传递(回调函数))
- [2.5 作为智能指针的删除器](#2.5 作为智能指针的删除器)
- [三、lambda 表达式的核心特性总结](#三、lambda 表达式的核心特性总结)
C++11 lambda表达式使用讲解
在 C++11 中,lambda 表达式是一种匿名函数(没有名称的函数),可以在需要函数的地方直接定义和使用,主要用于简化代码,尤其是在需要传递简短函数作为参数的场景(如算法回调、智能指针删除器等)。
一、lambda 表达式的基本语法
lambda 表达式的完整语法格式如下:
cpp
[capture-list](parameters) mutable noexcept -> return-type {
// 函数体
}
lambda 表达式本质是一个匿名函数对象,跟普通函数不同的是他可以定义在函数内部。 lambda 表达式语法使用层而言没有类型,所以我们一般是用auto 或者模板参数定义的对象去接收 lambda 对象。
各部分含义:
-
[capture-list]
(捕获列表) :用于捕获 lambda 外部的变量,使其能在函数体内使用。这是 lambda 与普通函数的核心区别(普通函数无法直接访问外部局部变量)。
- 常见用法:
[]
:不捕获任何外部变量。[=]
:按值捕获所有外部变量(拷贝一份,函数体内不能修改)。[&]
:按引用捕获所有外部变量(可修改,需注意变量生命周期)。[x, &y]
:按值捕获x
,按引用捕获y
。
- 常见用法:
-
(parameters)
(参数列表) :与普通函数的参数列表一致,用于接收传入的参数。如果没有参数,可省略括号(但建议保留以明确意图)。
-
mutable
(可选) :默认情况下,按值捕获的变量在 lambda 内是只读的。加上
mutable
后,允许修改按值捕获的变量(但修改不会影响外部原变量)。 -
noexcept
(可选) :声明 lambda 不会抛出异常,用于优化和明确接口。
-
-> return-type
(返回值类型) :返回值类型,用追踪返回类型形式声明函数的返回值类型,没有返回值时此 部分可省略。一般返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
-
{ ... }
(函数体) :包含具体的执行逻辑,与普通函数体相同。
二、lambda 表达式的使用示例
2.1 基础用法(无捕获、无参数)
cpp
#include <iostream>
int main() {
// 定义一个简单的lambda表达式(打印信息)
auto printHello = []() {
std::cout << "Hello, lambda!" << std::endl;
};
// 调用lambda表达式(像调用函数一样)
printHello(); // 输出:Hello, lambda!
return 0;
}
auto
用于自动推导 lambda 的类型(lambda 类型是编译器生成的匿名类型,无法显式写出)。- 调用方式与普通函数相同:
printHello()
。
2.2 捕获外部变量
2.2.1 捕获列表
lambda 表达式的捕获列表是 lambda 能够访问外部作用域变量的关键机制,lambda 表达式默认只能直接使用自身函数体和参数中的变量。如果要使用外层作用域(比如定义 lambda 的函数中的局部变量)的变量,就必须通过"捕获列表"明确声明要捕获哪些外部变量。
2.2.2 捕获的三种主要方式
(1)显式捕获(按值/按引用)
-
格式:在捕获列表中显式列出变量名 ,用逗号分隔。
- 按值捕获:直接写变量名(如
[x, y]
),会拷贝一份外部变量的值到 lambda 内部,lambda 内修改该拷贝不影响外部原变量。 - **按引用捕获:**变量名前加
&
(如[&z]
),lambda 内部通过引用访问外部变量,修改会直接影响外部原变量。
- 按值捕获:直接写变量名(如
-
示例:
cppint a = 0, b = 1; // 显式按值捕获a,按引用捕获b auto func = [a, &b]() { // a++; // 错误:按值捕获的变量默认是const,不能修改 b++; // 正确:按引用捕获,可修改外部b return a + b; };
(2)隐式捕获
无需显式列出变量名,编译器会自动捕获 lambda 内部使用的外部变量。
-
**隐式按值捕获:**捕获列表写
[=]
,lambda 内使用的所有外部变量都按值捕获(拷贝)。 -
**隐式按引用捕获:**捕获列表写
[&]
,lambda 内使用的所有外部变量都按引用捕获。 -
示例(对应图中代码片段思路):
cppint a = 0, b = 1, c = 2; // 隐式按值捕获所有用到的外部变量(a、b、c) auto func = [=]() { return a + b + c; // 使用a、b、c的拷贝值 };
(3)混合捕获(显式 + 隐式)
同时使用显式和隐式捕获,需遵循规则:
-
若第一个元素是
&
(如[&, x, y]
):表示除显式列出的变量外 ,其他用到的外部变量隐式按引用 捕获;显式列出的x
、y
需按值捕获。 -
若第一个元素是
=
(如[=, &x, &y]
):表示除显式列出的变量外 ,其他用到的外部变量隐式按值 捕获;显式列出的x
、y
需按引用捕获。 -
示例(对应图中代码片段思路):
cppint a = 0, b = 1, c = 2, d = 3; // 混合捕获:隐式按引用捕获其他变量,显式按值捕获a、b auto func = [&, a, b]() { // a++; // 错误:a是按值捕获的拷贝,且默认const // b++; // 错误:同理 c++; // 正确:c是隐式按引用捕获 d++; // 正确:d是隐式按引用捕获 return a + b + c + d; };
2.2.3 特殊变量的捕获规则
- 全局变量、静态局部变量:无需捕获,lambda 可直接使用(因为它们的作用域是全局或整个函数,生命周期足够长)。如果 lambda 定义在全局作用域,捕获列表必须为空(没有局部变量可捕获)。
- 局部变量 :只能捕获 lambda 定义位置之前的局部变量,之后定义的局部变量无法捕获。
2.2.4 mutable
修饰符与捕获的可修改性
默认情况下,按值捕获 的变量在 lambda 内部是 const
修饰的(即只读)。如果需要修改按值捕获的变量,需在参数列表后加 mutable
:
-
作用:移除按值捕获变量的
const
属性,允许在 lambda 内部修改。 -
注意:修改的是"拷贝的副本",不会影响外部原变量。且加
mutable
后,即使参数列表为空,也不能省略参数列表的括号 (如[]() mutable { ... }
)。 -
示例(对应图中代码片段思路):
cppint a = 0, b = 1, c = 2, d = 3; auto func = [=]() mutable { a++; // 允许修改(修改的是拷贝) b++; c++; d++; return a + b + c + d; }; func(); // 外部a、b、c、d的值不变,因为修改的是lambda内的拷贝 cout << a << " " << b << " " << c << " " << d << endl;
2.3 带参数和返回值
cpp
#include <iostream>
int main() {
// 带参数的lambda(计算两数之和)
auto add = [](int x, int y) -> int {
return x + y;
};
// 编译器可自动推导返回类型,简化写法
auto multiply = [](int x, int y) {
return x * y; // 自动推导返回类型为int
};
std::cout << add(2, 3) << std::endl; // 输出:5
std::cout << multiply(2, 3) << std::endl; // 输出:6
return 0;
}
2.4 作为参数传递(回调函数)
lambda 最常用的场景是作为算法或函数的参数(如标准库算法 std::for_each
):
cpp
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5};
// 用lambda作为回调,打印所有元素
std::for_each(nums.begin(), nums.end(), [](int x) {
std::cout << x << " ";
}); // 输出:1 2 3 4 5
return 0;
}
2.5 作为智能指针的删除器
在之前的智能指针代码中,lambda 被用作删除器,简化自定义释放逻辑:
cpp
// 用lambda作为删除器,释放数组
auto delArrLambda = [](Date* ptr) {
std::cout << "lambda: 释放数组" << std::endl;
delete[] ptr;
};
std::unique_ptr<Date, decltype(delArrLambda)> up4(new Date[3], delArrLambda);
三、lambda 表达式的核心特性总结
- 匿名性 :lambda 没有名称,通常通过
auto
变量接收后使用,或直接作为参数传递。 - 捕获机制 :通过
[capture-list]
灵活捕获外部变量,解决了普通函数无法直接访问局部变量的问题。 - 简洁性:对于简短的函数逻辑,无需单独定义函数,直接在使用处编写,减少代码跳转。
- 类型唯一性 :每个 lambda 表达式的类型都是编译器生成的唯一匿名类型,无法显式声明,只能用
auto
或std::function
存储。
与普通函数的区别
特性 | lambda 表达式 | 普通函数 |
---|---|---|
名称 | 匿名(无名称) | 有明确名称 |
外部变量访问 | 通过捕获列表灵活访问 | 只能访问全局变量或静态变量 |
类型 | 编译器生成的匿名类型 | 函数类型(可通过函数指针引用) |
用途 | 短期使用(如回调、局部逻辑) | 长期复用、跨范围调用 |
| 有明确名称 |
| 外部变量访问 | 通过捕获列表灵活访问 | 只能访问全局变量或静态变量 |
| 类型 | 编译器生成的匿名类型 | 函数类型(可通过函数指针引用) |
| 用途 | 短期使用(如回调、局部逻辑) | 长期复用、跨范围调用 |