C++11 引入了很多非常实用的新特性,其中Lambda 表达式 、std::function、std::bind三者经常搭配使用,极大简化了回调函数、算法传参、函数包装的代码写法。下面结合原理与代码示例来梳理。
一、Lambda 表达式
1. Lambda 基础语法
[capture-list] (parameters) -> return_type { function_body };
-
[capture-list]:捕获列表 ,必须写,就算为空也要保留[]。作用是把 Lambda 外部作用域变量捕获到内部使用。 -
(parameters):参数列表,无参数时()可以省略。 -
-> return_type:返回值类型。如果返回类型可以推导,这部分可以省略。 -
{ function_body }:函数体,不能为空。
本质:Lambda 是匿名仿函数 。编译器遇到 Lambda,会自动生成一个唯一名字的类,类里面重载
operator(),捕获的变量变成这个类的成员变量。
示例:sort 自定义比较
vector<Goods> v;
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
return g1._price > g2._price;
});
这里直接把 Lambda 传给 sort 的第三个参数,作为比较规则,不用单独写一个全局 / 仿函数类。
2. 捕获列表规则
捕获分为值捕获 、引用捕获 、隐式捕获 、混合捕获:
-
显式捕获 :
[x, &y],x 值捕获,y 引用捕获,多个变量逗号隔开。 -
隐式捕获
-
[=]:全部外部变量值捕获 -
[&]:全部外部变量引用捕获
-
-
混合捕获
混合捕获时,
=或&必须写在捕获列表最前面-
[=, &x]:默认值捕获,唯独 x 用引用捕获 -
[&, x]:默认引用捕获,唯独 x 用值捕获
-
-
不能捕获的变量:静态局部变量、全局变量不需要捕获,可以直接在 lambda 内部使用。
mutable 关键字
值捕获默认捕获的变量在 lambda 内部是const,无法修改。 加上mutable可以取消 const 限制,允许修改捕获的副本(不会影响外部原变量)。
int a = 10;
auto f = [a]() mutable {
a++;
cout << a << endl;
};
f();
cout << a; // 输出10,a只是副本被修改
使用 mutable 时,参数列表
()不能省略,哪怕无参。
3. Lambda 底层原理
auto r2 = [rate](double money, int year) {
return money * rate * year;
};
编译器会生成类似下面这个类:
class __lambda_xxx
{
public:
double operator()(double money, int year) const
{
return money * rate * year;
}
private:
double rate; // 捕获的变量变成成员变量
};
所以 lambda 本质就是编译器自动生成的仿函数对象 。汇编层面看不到 lambda 关键字,最终就是调用operator()。
二、std::function
std::function是可调用对象包装器 ,定义在<functional>头文件。 它可以统一包装:普通函数、函数指针、仿函数、lambda、bind 返回的可调用对象。
1. 使用语法
std::function<返回类型(参数列表)> 变量名 = 可调用对象;
如果std::function对象没有包装任何可调用对象(空),调用时会抛出std::bad_function_call异常。
2. 经典示例:逆波兰表达式求值
传统写法需要switch分支判断运算符,使用std::function + lambda 可以把运算符和运算逻辑放进 map,代码更简洁:
class Solution {
public:
int evalRPN(vector<string>& tokens) {
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;}},
};
stack<int> st;
for(auto& str : tokens)
{
if(opFuncMap.count(str))
{
int right = st.top(); st.pop();
int left = st.top(); st.pop();
st.push(opFuncMap[str](left, right));
}
else
{
st.push(stoi(str));
}
}
return st.top();
}
};
优势:不同类型的可调用对象,都可以存进
std::function,统一类型,方便存入容器、做回调。
三、std::bind
std::bind是函数适配器 ,同样在<functional>头文件。 作用:绑定可调用对象,固定部分参数,调整参数顺序 ,返回一个新的可调用对象,可以交给std::function接收。
占位符 std::placeholders::_1, _2, _3...
-
_1:新可调用对象的第一个参数,传给原函数的对应位置 -
_2:新可调用对象的第二个参数
示例:
int Sub(int a, int b)
{
return (a - b) * 10;
}
int main()
{
// 固定第一个参数=10,剩下的参数用_1接收
auto newSub1 = bind(Sub, 10, placeholders::_1);
cout << newSub1(5); // (10 -5)*10 =50
// 调换参数顺序:新函数第一个参数传给b,第二个传给a
auto newSub2 = bind(Sub, placeholders::_2, placeholders::_1);
cout << newSub2(10,5); // Sub(5,10) → (5-10)*10 = -50
}
bind可以减少传入参数个数、调换参数顺序,适配回调接口的参数要求。
四、三者搭配使用总结
-
Lambda:快速写匿名的仿函数,适合一次性回调,代码简洁。
-
std::function:统一包装所有可调用对象,提供统一类型,用于回调容器。
-
std::bind:改造已有函数,固定参数、调换参数顺序,适配接口。
常见场景:容器算法传参、回调函数、事件驱动、策略模式。
补充易错点
-
Lambda 捕获引用要注意生命周期:引用捕获一个局部变量,lambda 如果延迟调用,原变量销毁会野引用。
-
值捕获默认 const,修改必须加
mutable,但修改只改副本。 -
bind 占位符
_n里的 n,代表新函数的参数序号,不是原函数。 -
std::function空对象调用会抛异常,使用前最好判断是否有效。