lambda 表达式
关于C++Lambda表达式的学习记录
LeetCode189:轮转数组
在刷到 LeetCode189:轮转数组的地方,使用 reverse 解决这个问题很方便, 然后看题解使用了Lambda表达式,由于我对C++不是很熟,使用AI学习一下。
下面是题解。
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int size = nums.size();
auto reverse = [&] (int i, int j) {
while(i < j) {
swap(nums[i++], nums[j--]);
}
};
k %= size;
reverse(0, size-1);
reverse(0, k-1);
reverse(k, size-1);
}
};
一、整体语法拆解
先看完整结构:
auto reverse = [&](int i, int j){
while(i < j){
swap(nums[i++], nums[j--]);
}
};
我们把它拆成 5 个核心部分:
1. auto reverse:变量声明
auto:C++11 引入的自动类型推导,编译器会根据右侧 lambda 表达式的类型,自动推导reverse的类型(本质是 lambda 的闭包类型)。reverse:给这个匿名函数起的 “名字”,后续可以像调用普通函数一样用reverse(i, j)调用。
2. [&]:lambda 捕获子句(捕获列表)
这是 lambda 最关键的部分之一,决定了 lambda 内部能访问哪些外部变量,以及访问方式:
-
[&]:表示以引用方式捕获所有外部可见的变量(即 lambda 定义所在作用域的变量)。 -
这里能直接用
nums和swap(swap是全局 / 命名空间函数),就是因为[&]捕获了外部的nums变量(比如外层函数的 vector / 数组)。 -
补充捕获方式(常用):
写法 含义 []不捕获任何外部变量(空捕获) [=]以值拷贝方式捕获所有外部变量 [&x]只以引用捕获变量 x [x]只以值拷贝捕获变量 x [&,x]引用捕获所有变量,唯独 x 按值捕获
3. (int i, int j):lambda 参数列表
和普通函数的参数列表完全一致:
- 声明了两个整型参数
i和j,表示要反转的区间的起始索引和结束索引(闭区间[i, j])。 - 比如调用
reverse(0, 4),就是反转数组索引 0 到 4 的元素。
4. { ... }:lambda 函数体
核心逻辑是反转区间 [i, j] 的元素:
while(i < j){
swap(nums[i++], nums[j--]);
}
- 循环条件
i < j:保证只交换到区间中间(避免重复交换)。 swap(nums[i++], nums[j--]):swap:交换两个元素(C++ 标准库函数,需包含<algorithm>,或自定义)。i++/j--:交换后,起始索引右移、结束索引左移,直到相遇。
5. 分号 ;:语句结束
lambda 表达式是一个 “表达式”,但赋值给变量时,整体是一条语句,必须以分号结尾。
二、核心特性:lambda 表达式的本质
lambda 是 C++11 引入的匿名函数,本质是:
- 编译器会把 lambda 翻译成一个匿名类(闭包类),并创建该类的一个临时对象(闭包对象)。
auto reverse实际是把这个闭包对象赋值给变量reverse。- 调用
reverse(i, j)等价于调用闭包类的operator()成员函数。
三、使用示例
结合完整场景,比如反转数组的某段区间:
#include <iostream>
#include <vector>
#include <algorithm> // swap 需包含此头文件
using namespace std;
int main() {
vector<int> nums = {1,2,3,4,5};
// 定义 lambda 函数
auto reverse = [&](int i, int j){
while(i < j){
swap(nums[i++], nums[j--]);
}
};
// 调用:反转索引 1 到 3 的元素(2,3,4 → 4,3,2)
reverse(1, 3);
// 输出结果:1 4 3 2 5
for(int num : nums) {
cout << num << " ";
}
return 0;
}
四、关键注意事项
-
捕获的生命周期:
- 因为用
[&]引用捕获nums,所以reverse的生命周期不能超过nums(比如nums销毁后调用reverse会导致野引用)。 - 如果想避免这个问题,可以用
[=]值捕获,但swap会修改拷贝后的nums,原数组不会变(按需选择)。
- 因为用
-
lambda 的可调用性:
- 无捕获的 lambda(
[])可以隐式转换为函数指针,有捕获的不行。 - 本例有捕获,只能通过变量
reverse调用。
- 无捕获的 lambda(
-
参数和返回值:
-
本例 lambda 无返回值(默认
void),如果需要返回值,可显式指定:auto add = [](int a, int b) -> int { return a + b; };
-
五、为什么用这种写法?
- 简洁:无需单独定义一个全局 / 成员函数,就地定义就地使用。
- 闭包特性:自动捕获外部变量,无需手动传参(比如本例不用把
nums作为参数传入reverse)。 - 灵活:常用于算法场景(比如排序的自定义比较、局部的小功能函数)。
LeetCode49. 字母异位词分组
官方C++题解的方法二
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
// 自定义对 array<int, 26> 类型的哈希函数
auto arrayHash = [fn = hash<int>{}] (const array<int, 26>& arr) -> size_t {
return accumulate(arr.begin(), arr.end(), 0u, [&](size_t acc, int num) {
return (acc << 1) ^ fn(num);
});
};
unordered_map<array<int, 26>, vector<string>, decltype(arrayHash)> mp(0, arrayHash);
for (string& str: strs) {
array<int, 26> counts{};
int length = str.length();
for (int i = 0; i < length; ++i) {
counts[str[i] - 'a'] ++;
}
mp[counts].emplace_back(str);
}
vector<vector<string>> ans;
for (auto it = mp.begin(); it != mp.end(); ++it) {
ans.emplace_back(it->second);
}
return ans;
}
};
这里使用了C++更加高级的语法,又不会了,过来补充。下面内容生成自豆包。
先明确核心背景:这段代码是为了让 unordered_map 能以 array<int,26> 作为键 —— 而要实现这个目标,必须解决两个问题:
unordered_map本质是哈希表,需要 “哈希函数” 把键转换成唯一的数值(哈希值);- C++ 标准库没有给
array<int,26>提供默认的哈希函数,所以必须自己写。
下面我们一步步拆解,先补基础,再讲代码。
第一步:先补你需要的 C++ 基础概念
在看代码前,先搞懂几个核心概念,否则代码里的语法会完全看不懂:
| 概念 | 通俗解释 |
|---|---|
std::array<int,26> |
固定长度的数组(长度 26,存 int),和普通数组int arr[26]类似,但更安全(支持边界检查、可拷贝) |
unordered_map |
C++ 的哈希表,存储 “键 - 值对”,要求键必须能计算哈希值(否则无法存 / 取) |
| 哈希函数 | 把任意类型(比如数组)转换成一个size_t(无符号整数)的函数,目标是 “相同的键→相同哈希值,不同的键→不同哈希值” |
| Lambda 表达式 | 匿名函数(没有名字的函数),可以直接定义、直接使用,适合写短小的回调 / 自定义逻辑 |
std::accumulate |
STL 的 “累加函数”,遍历一个容器(比如 array),按自定义规则把所有元素 “合并” 成一个值 |
decltype |
类型推导关键字,能 “推导一个变量 / 表达式的类型”(比如 lambda 的类型是匿名的,必须用 decltype 获取) |
第二步:逐行拆解代码(从易到难)
我们把代码分成两部分:自定义哈希函数 和 创建带自定义哈希的 unordered_map,逐个细节讲透。
第一部分:自定义哈希函数 arrayHash
auto arrayHash = [fn = hash<int>{}] (const array<int, 26>& arr) -> size_t {
return accumulate(arr.begin(), arr.end(), 0u, [&](size_t acc, int num) {
return (acc << 1) ^ fn(num);
});
};
1. 外层:Lambda 表达式的 “壳”
auto arrayHash = [捕获列表] (参数列表) -> 返回值类型 { 函数体 };
auto arrayHash:用auto自动推导变量类型(因为 lambda 的类型是匿名的,没法手动写);[fn = hash<int>{}]:Lambda 的捕获列表(C++14 特性:初始化捕获):hash<int>{}:创建一个 “计算 int 类型哈希值” 的函数对象(标准库提供的,比如hash<int>{}(5)能算出 5 的哈希值);fn = ...:给这个函数对象起个名字fn,并捕获到 lambda 里(这样 lambda 内部能调用fn);
(const array<int, 26>& arr):Lambda 的参数 —— 接收一个array<int,26>类型的常量引用(加const&是为了避免拷贝,提升效率);-> size_t:Lambda 的返回值类型 —— 返回size_t(无符号整数,哈希值的标准类型);
2. 内层:std::accumulate 核心逻辑
accumulate(arr.begin(), arr.end(), 0u, [&](size_t acc, int num) {
return (acc << 1) ^ fn(num);
});
accumulate的作用是 “遍历数组,把所有元素融合成一个值”,它的 4 个参数:
| 参数 | 含义 |
|---|---|
arr.begin(), arr.end() |
遍历范围:从数组开头到结尾 |
0u |
累加的初始值:0u表示 “无符号的 0”(u是无符号标记,和size_t类型匹配) |
[&](size_t acc, int num) { ... } |
自定义的 “累加规则”(又是一个 Lambda):- acc:累加器(保存当前计算的哈希值);- num:遍历到的数组元素(比如字母 'a' 的出现次数); |
3. 累加规则的细节:(acc << 1) ^ fn(num)
这是哈希函数的核心,目的是把数组每个元素的特征融合成一个值:
acc << 1:把当前哈希值左移 1 位(相当于乘以 2)—— 扩大哈希值的范围,减少 “不同数组算出相同哈希值” 的概率(哈希冲突);fn(num):调用之前捕获的fn,计算当前元素num的哈希值(比如num=2,fn(2)会算出 2 对应的哈希值);^:异或运算符 —— 把 “左移后的哈希值” 和 “当前元素的哈希值” 做异或,融合两者的特征;
举个例子:假设数组是[1,0,0,...,0](对应字符串 "a"),计算过程:
-
初始
acc=0; -
遍历第一个元素
num=1:acc = (0 << 1) ^ fn(1) = 0 ^ 哈希(1) = 哈希(1); -
遍历剩下的元素
num=0:acc = (哈希(1) << 1) ^ fn(0),依此类推;最终得到一个唯一的哈希值,代表这个数组。
第二部分:创建带自定义哈希的 unordered_map
unordered_map<array<int, 26>, vector<string>, decltype(arrayHash)> mp(0, arrayHash);
1. unordered_map 的模板参数(尖括号里的内容)
unordered_map 的完整模板是:
template<class Key, class T, class Hash = hash<Key>, class KeyEqual = equal_to<Key>, class Allocator = allocator<pair<const Key, T>>>
class unordered_map;
我们这里用到了前 3 个参数:
| 参数 | 含义 |
|---|---|
array<int, 26> |
Key(键的类型):哈希表的键是长度 26 的 int 数组 |
vector<string> |
T(值的类型):哈希表的值是字符串数组(比如存一组字母异位词) |
decltype(arrayHash) |
Hash(哈希函数的类型):- lambda 的类型是匿名的,没法手动写,所以用decltype(arrayHash)推导arrayHash的类型;- 如果不指定这个,编译器会用默认的hash<array<int,26>>,但标准库没有这个特化版本,会编译报错; |
2. unordered_map 的构造函数参数(圆括号里的内容)
mp(0, arrayHash)
0:哈希表的 “初始桶数”(bucket count)—— 哈希表内部是用 “桶” 来存数据的,设为 0 表示让系统自动选择初始大小(不影响功能,只是性能调优);arrayHash:传入我们自定义的哈希函数实例 —— 告诉unordered_map:“计算键的哈希值时,用这个 lambda 函数”;
关键为什么必须自定义哈希?
如果直接写:
// 编译报错!!!
unordered_map<array<int,26>, vector<string>> mp;
原因是:C++ 标准库只为int、string、double等基础类型提供了hash特化版本,没有为array<int,26>提供。所以必须手动告诉编译器 “怎么计算 array 的哈希值”—— 也就是我们写的arrayHash。
第三步:用 “人话” 总结整个逻辑(场景化)
假设你要做 “字母异位词分组”(把 eat/tea/ate 分到一组),核心思路是:
- 对每个字符串,统计 26 个字母的出现次数(比如 eat→a:1, e:1, t:1,其他 0 → 对应 array<int,26>);
- 把 “字母次数数组” 作为键,字符串作为值,存入哈希表 —— 这样相同次数的字符串会自动分到一组;
- 但哈希表不认 “字母次数数组” 这个键,所以需要写一个函数(arrayHash),把数组转换成哈希表能识别的数字(size_t);
- 最后创建哈希表时,把这个函数传给它,让它知道怎么处理数组键。
第四步:补充新手容易踩的坑
-
为什么用
const array<int,26>& arr而不是array<int,26> arr?加&是引用,避免拷贝整个数组(26 个 int 虽然少,但养成习惯);加const是保证不会修改传入的数组(只读)。
-
0u为什么加u?0是有符号 int,0u是无符号 unsigned int,和
size_t(无符号)类型匹配,避免类型转换警告。 -
Lambda 的
[&]是什么?内层 lambda 的
[&]是 “按引用捕获外部变量”—— 这里捕获的是外层 lambda 的fn,所以内层能调用fn(num)。 -
为什么不用普通函数代替 lambda?
可以,但 lambda 更简洁(不用单独定义函数),且能直接捕获
fn,不用额外传参。

浙公网安备 33010602011771号