C++ Lambda 完整详解
一、Lambda 是什么
Lambda 是 C++11 引入的匿名可调用对象,无需定义独立函数 / 仿函数类,就地书写一段可执行逻辑,常搭配算法(
sort/for_each)、异步线程、回调使用。基础语法总览
[捕获列表](参数列表) mutable -> 返回类型 { 函数体 };拆解五部分:
[]:捕获外部变量(核心难点)():形参列表,无参可省略()(C++14 起)mutable:可选,允许修改值捕获的变量- **
-> T:尾置返回类型,**能自动推导时可省略{}:函数执行体
二、捕获列表
[]全规则(重点)捕获决定 lambda 能否访问函数外部局部变量,分值捕获、引用捕获、隐式捕获、this 捕获。
1. 空捕获
[]不捕获任何外部变量,仅能使用全局变量、静态变量、自身入参。
int a = 10; auto f = [](){ cout << a; }; // 报错,未捕获a2. 值捕获
[x]拷贝一份变量,lambda 内只读,无法修改原变量,也不能修改拷贝副本(不加
mutable)
int num = 100; auto f = [num](){ cout << num; }; f(); // 输出100 num = 200; f(); // 依旧100,捕获的是拷贝时的值3. 引用捕获
[&x]捕获变量的引用,lambda 内读写都会同步修改外部原变量
int num = 100; auto f = [&num](){ num++; }; f(); cout << num; // 1014. 隐式批量捕获
[=]:全部外部局部变量值捕获
[&]:全部外部局部变量引用捕获int a=1,b=2,c=3;
auto f1 = ={ cout << a << b << c; }; // 全部值拷贝
auto f2 = &{ a++;b++;c++; }; // 全部引用5. 混合捕获(常用)
批量隐式 + 单独指定捕获方式,单独变量必须和批量方式相反
[=, &x]:默认全部值捕获,唯独 x 用引用
[&, x]:默认全部引用捕获,唯独 x 用值拷贝int a=1,b=2;
auto f = =, &a{
a++; // 引用修改原a
cout << b; // 值拷贝,只读
};6. this 捕获
[this]/[*this]仅在类成员函数内使用,访问成员变量 / 成员函数:
[this]:捕获对象指针,引用原对象,修改成员会影响外部
[*this](C++17):拷贝当前对象副本,修改仅作用于副本,不影响原对象struct Test{
int val = 10;
void func(){
auto f1 = this{ val++; };
auto f2 = *this{ val++; }; // 副本,外部val不变
f1();
}
};7. 初始化捕获(C++14)
捕获时创建临时变量,可移动捕获
unique_ptr等不可拷贝对象
auto p = make_unique<int>(10); auto f = [ptr=move(p)](){ cout << *ptr; }; // p 已被移动,外部失效捕获禁止事项
- 不能同时
[=, &],冲突;- 全局变量不需要捕获,直接访问;
- 捕获列表不能重复写同一个变量:
[a,a]编译报错。
三、mutable 关键字
值捕获默认拷贝的变量是
const,无法修改副本;加mutable解除 const 限制,仅修改拷贝副本,不影响外部原变量。
int x = 10; auto f = [x]() mutable { x++; // 允许修改副本 cout << x; }; f(); // 11 cout << x; // 外部依旧10mutable 存在时,参数括号
()不能省略。
四、返回类型
1. 自动推导(绝大多数场景省略
->)
多条 return 返回同类型:自动推导
单条 return、无 return(返回 void):自动推导
auto add = [](int a,int b){ return a+b; };
2. 必须显式指定
-> T的场景函数体内多条
return且返回值类型不同:
// 必须写 -> double,否则推导失败 auto f = [](int x)->double{ if(x>0) return x; else return 0.5; };3. 返回引用
int x=10; auto getX = [&]()->int& { return x; }; getX() = 100; // 外部x变为100
五、参数列表特性
C++14 支持泛型 lambda(
auto形参,模板效果)auto print = [](auto val){ cout << val; };
print(10);
print("hello");
print(3.14);C++20 支持模板 lambda,更精细控制泛型
auto f = []
(T a){}; 支持右值引用、默认参数
auto f = [](int a, int b=10){ return a+b; };
六、lambda 本质(底层原理)
编译器遇到 lambda,会自动生成一个匿名仿函数类:
- 捕获的变量作为类的成员;
operator()重载为函数体;- mutable 控制
operator()是否为 const;示例:
int a=10; auto lam = [a](int x){ return a+x; };编译器等价生成:
class __Lambda_xxx{ private: int a; // 值捕获成员 public: __Lambda_xxx(int _a):a(_a){} int operator()(int x) const { // 无mutable则const return a+x; } }; __Lambda_xxx lam(a);特性推导:
- 无捕获的 lambda
[]()可隐式转为普通函数指针;- 有捕获的 lambda 无法转函数指针,只能用
function包装。function 包装 lambda
用于存储、传递可调用对象:
#include <functional> std::function<int(int,int)> add = [](int a,int b){return a+b;}; cout << add(1,2);
七、常用实战场景
1. 标准算法 sort 自定义排序
vector<int> v = {3,1,4,2}; // 降序排序 sort(v.begin(),v.end(),[](int a,int b){ return a>b; }); // 按结构体成员排序 struct Person{ string name; int age; }; vector<Person> ps = {{"A",20},{"B",18}}; sort(ps.begin(),ps.end(),[](const Person& x,const Person& y){ return x.age < y.age; });2. for_each 遍历
vector<int> v={1,2,3}; for_each(v.begin(),v.end(),[](int x){ cout << x*2 << " "; });3. 线程回调 std::thread
#include <thread> int num = 10; thread t([&num](){ for(int i=0;i<5;i++) num++; }); t.join();4. 递归 lambda(难点)
lambda 不能捕获自身,需用
function先声明再捕获:
function<int(int)> fib; fib = [&fib](int n)->int{ if(n<=2) return 1; return fib(n-1)+fib(n-2); }; cout << fib(10);5. 移动捕获资源(unique_ptr)
auto ptr = make_unique<string>("test"); auto f = [p=move(ptr)](){ cout << *p; }; f();
function 完整详解
一、std::function 是什么
头文件:
<functional>``std::function<R(Args...)是通用可调用对象包装器,可以统一存放、传递任意可调用类型:普通函数、函数指针、lambda、仿函数(重载operator()的类)、类成员函数、静态成员函数。核心作用
- 统一类型:不同形式可调用物可以存到同一个容器、参数传递;
- 延迟调用、回调、存储 lambda(尤其是递归 lambda);
- 替代笨重的函数指针,支持捕获的 lambda。
二、基础语法
#include <functional> // R:返回值,Args...:参数列表 std::function<返回类型(参数1类型, 参数2类型...)> 变量名;示例基础用法
cpp#include <iostream> #include <functional> using namespace std; // 普通函数 int add(int a, int b) { return a + b; } int main() { // 包装普通函数 function<int(int, int)> f1 = add; cout << f1(1, 2) << endl; // 包装lambda function<int(int, int)> f2 = [](int x, int y) { return x * y; }; cout << f2(3, 4) << endl; // 无返回无参数 function<void()> f3 = []() { cout << "hello function\n"; }; f3(); return 0; }
三、能包装的所有可调用对象
1. 普通全局 / 静态函数
void print(string s) { cout << s; } function<void(string)> f = print; f("test");2. Lambda(有无捕获都可以)
函数指针不能存带捕获的 lambda,但
function可以,这是最大优势:
int x = 10; // 带引用捕获的lambda,只能用function存 function<int()> f = [&x](){ return x + 5; }; cout << f();3. 仿函数(重载 operator () 的类)
struct FuncObj { int operator()(int a) { return a * a; } }; int main() { function<int(int)> f = FuncObj(); cout << f(6); return 0; }4. 类静态成员函数
struct Test { static int sub(int a, int b) { return a - b; } }; function<int(int, int)> f = Test::sub; cout << f(10, 3);5. 普通成员函数(特殊写法,必须绑定对象)
成员函数隐藏第一个
this*参数,要用bind或传入对象:
#include <functional> #include <iostream> using namespace std; struct Person { int age = 18; void show(int num) { cout << age + num; } }; int main() { Person p; // 方式1:bind绑定对象 function<void(int)> f1 = bind(&Person::show, &p, placeholders::_1); f1(5); // 方式2:function签名带上对象指针 function<void(Person*, int)> f2 = &Person::show; f2(&p, 10); return 0; }
四、关键成员函数
operator():调用包装的可调用对象
f(1,2);
bool operator bool():判断是否持有有效可调用对象
function<void()> f; if (!f) { cout << "为空,不能调用"; }
reset():清空,置为空
f.reset();
target<T>():取出底层存储的可调用对象(少用)
五、经典场景:递归 Lambda
lambda 不能直接捕获自身,必须先用
function声明,再赋值、捕获自己:
#include <iostream> #include <functional> using namespace std; int main() { function<int(int)> fib; fib = [&fib](int n) -> int { if (n <= 2) return 1; return fib(n - 1) + fib(n - 2); }; cout << fib(10); return 0; }
六、配合容器存储回调
可以把多个不同 lambda 统一放进 vector:
#include <iostream> #include <functional> #include <vector> using namespace std; int main() { vector<function<int(int)>> ops; ops.push_back([](int x) { return x + 1; }); ops.push_back([](int x) { return x * 2; }); ops.push_back([](int x) { return x * x; }); for (auto& f : ops) { cout << f(5) << " "; } return 0; }
七、std::function vs 函数指针
特性 函数指针 std::function 存储带捕获 lambda ❌ 不支持 ✅ 支持 存储仿函数、成员函数 ❌ 麻烦 ✅ 统一包装 类型统一存放容器 限制多 任意可调用统一类型 开销 极小 少量堆内存开销 空判断 需判空指针 直接 if 判断 无捕获 lambda 可以隐式转函数指针;有捕获 lambda只能用 function。
八、常见坑
空
function调用直接崩溃
function<void()> f; f(); // 未赋值,运行时报错解决:调用前判断
if(f)成员函数绑定忘记传对象,调用崩溃
递归 lambda 必须
[&fib]引用捕获 function 变量频繁创建销毁大量 function 会有少量性能损耗,高频计算场景尽量直接 lambda 调用
九、bind 搭配 function
bind用于提前绑定参数,生成新可调用对象存入 function:
#include <functional> #include <iostream> using namespace std; int add(int a, int b) { return a + b; } int main() { // 固定第一个参数为10,只留第二个参数可变 function<int(int)> add10 = bind(add, 10, placeholders::_1); cout << add10(5); // 15 return 0; }
placeholders::_1、_2代表调用时传入的第 1、2 个参数。
十、无返回值简写 void
// 接收两个int,无返回 function<void(int, int)> print = [](int a, int b) { cout << a << b; };
十一、题目运用
逆波兰表达式求值
150. 逆波兰表达式求值
https://leetcode.cn/problems/evaluate-reverse-polish-notation/
给你一个字符串数组
tokens,表示一个根据 逆波兰表示法 表示的算术表达式。请你计算该表达式。返回一个表示表达式值的整数
通用写法
cppclass Solution { public: int shift(string& x) { int i = 0, t = 0,sign=1; while (i < x.size()) { if(x[i]=='-') { sign=-1; i=1; } t = t * 10 + (x[i] - '0'); i++; } int result=sign*t; return result; } int evalRPN(vector<string>& tokens) { stack<int> st; int n = 0, x = 0, sum = 0; char c; string d; while (n < tokens.size()) { //是不是数字 d = (tokens[n]); if ((d[0] >= '0' && d[0] <= '9')||(d.size() > 1 && d[0] == '-')) { x = stoi(d); st.push(x); } //不是数字 else { sum = st.top(); st.pop(); c=tokens[n][0]; if (c == '+') { st.top() += sum; } else if (c == '-') { st.top() -= sum; } else if (c == '*') { st.top() *= sum; } else { st.top() /= sum; } } n++; } return st.top(); } };优化写法
cppclass Solution { public: int evalRPN(vector<string>& tokens) { stack<int> st; // function作为map的映射可调⽤对象的类型 map<string, function<int(int, int)>> opFuncMap = { {"+", [](int x, int y) { return x + y; }}, {"-", [](int x, int y) { return x - y; }}, {"*", [](int x, int y) { return x * y; }}, {"/", [](int x, int y) { return x / y; }}}; for (auto& str : tokens) { if (opFuncMap.count(str)) // 操作符 { int right = st.top(); st.pop(); int left = st.top(); st.pop(); int ret = opFuncMap[str](left, right); st.push(ret); } else { st.push(stoi(str)); } } return st.top(); } };
bind 完整详解
头文件:
<functional>一、作用
bind是参数绑定工具,接收一个可调用对象(函数、函数指针、lambda、成员函数、仿函数),生成一个新的可调用包装器:
- 预先固定一部分参数;
- 调整参数顺序;
- 适配类成员函数(补齐隐藏的
this参数);- 配合
function存储回调。
二、基础语法
auto new_callable = std::bind(可调用对象, 绑定参数列表...);绑定参数分两类:
- 普通常量:直接写值,永久固定;
- 占位符
placeholders::_1 / _2 / _3...:代表调用新函数时传入的第 1/2/3 个实参。
三、基础示例:固定部分参数
#include <iostream> #include <functional> using namespace std; int add(int a, int b) { return a + b; } int main() { // 固定第一个参数 a=10,只留第二个参数动态传入 auto add10 = bind(add, 10, placeholders::_1); cout << add10(5); // 10+5=15 cout << add10(20); // 10+20=30 // 两个参数全部固定,调用时无需传参 auto add20 = bind(add, 10, 10); cout << add20(); // 20 return 0; }
四、调整参数顺序
通过调换占位符顺序,改变入参映射关系:
int sub(int a, int b) { return a - b; } int main() { // 原:sub(a,b) = a-b // bind后:调用时传(x,y),映射为 sub(y, x) auto reverse_sub = bind(sub, placeholders::_2, placeholders::_1); cout << reverse_sub(10, 3); // sub(3,10) = -7 return 0; }
五、传参绑定规则:值绑定 / 引用绑定
1. 默认:值拷贝绑定
bind 默认会拷贝传入的变量,后续外部修改不影响绑定内部:
void print(int x) { cout << x; } int main() { int num = 10; auto f = bind(print, num); num = 100; f(); // 输出10,不是100 return 0; }2. std::ref /std::cref 引用绑定
想要绑定变量引用,需要用
ref()(可读写引用)、cref()(const 只读引用):
void modify(int& x) { x *= 2; } int main() { int num = 10; auto f = bind(modify, ref(num)); f(); cout << num; // 20,成功修改外部变量 return 0; }
六、绑定类成员函数(高频难点)
成员函数自带隐藏第一个参数
this,bind 必须手动传入对象指针 / 对象。示例 1:普通成员函数
struct Person { int base = 5; int calc(int x) { return base + x; } }; int main() { Person p; // &Person::calc:成员函数地址 // &p:this指针,固定绑定对象p // _1:调用时传入calc的形参x auto func = bind(&Person::calc, &p, placeholders::_1); cout << func(3); // 5+3=8 return 0; }示例 2:绑定对象副本 vs 对象引用
bind(..., p, ...):拷贝一份 p,修改不影响原对象bind(..., &p, ...):绑定原对象指针,修改同步示例 3:静态成员函数
静态成员无
this,绑定方式和普通全局函数一致:
struct Test { static int mul(int a, int b) { return a*b; } }; auto f = bind(Test::mul, 2, placeholders::_1); cout << f(5); // 10示例 4:绑定成员变量
可以直接绑定类成员变量,生成取值可调用对象:
struct P { int age = 18; }; P p; auto get_age = bind(&P::age, &p); cout << get_age(); // 18
七、配合 function 使用
bind 返回的匿名对象可以存入
function,统一管理回调:
#include <vector> int add(int a,int b) {return a+b;} int main() { vector<function<int(int)>> calls; // 批量生成固定左值的加法器 calls.push_back(bind(add, 1, placeholders::_1)); calls.push_back(bind(add, 10, placeholders::_1)); for(auto& f : calls) { cout << f(5) << " "; // 6 15 } return 0; }
八、bind 搭配 lambda 的对比
- 固定少量参数:
bind代码更短;- 复杂逻辑、多参数调整、泛型:lambda 可读性更强;
- 类成员函数封装回调:bind 无可替代。
等价对比:
int add(int a,int b) {return a+b;} // bind写法 auto add10 = bind(add, 10, placeholders::_1); // lambda等价写法 auto add10_lam = [](int b){ return add(10, b); };
九、占位符说明
placeholders::_1 // 调用时第1个实参 placeholders::_2 // 调用时第2个实参 placeholders::_3 // 调用时第3个实参 ...占位符只关心调用顺序,bind 内部可以重复使用同一个占位符:
auto double_add = bind(add, placeholders::_1, placeholders::_1); cout << double_add(6); // 6+6=12
十、常见坑点
- 成员函数忘记传对象指针,运行崩溃;
- 需要引用传参时不用
std::ref,拿到的永远是拷贝快照;- 占位符序号写错,参数错位;
- bind 存储可调用对象会产生少量内存开销,极致性能场景优先 lambda;
- 绑定临时对象,后续调用时对象已销毁,野引用崩溃。
十一、完整综合示例
cpp#include <iostream> #include <functional> using namespace std; void show(string prefix, int num) { cout << prefix << num << endl; } struct Student { string name = "张三"; void say(int score) { cout << name << "分数:" << score << endl; } }; int main() { // 1. 固定第一个参数 auto show_info = bind(show, "成绩:", placeholders::_1); show_info(95); // 2. 成员函数绑定 Student s; auto stu_say = bind(&Student::say, &s, placeholders::_1); stu_say(88); // 3. 引用绑定外部变量 int x = 100; auto ref_print = bind([](int& v){v++;}, ref(x)); ref_print(); cout << x; // 101 return 0; }

