跳到主要内容 C++11 新特性详解:Lambda 表达式、std::function 与 bind | 极客日志
C++ 算法
C++11 新特性详解:Lambda 表达式、std::function 与 bind 介绍 C++11 引入的 Lambda 表达式基础语法、捕获列表及 mutable 关键字,阐述其底层仿函数原理。同时讲解 std::function 包装器如何统一可调用对象接口,以及 std::bind 适配器调整参数顺序和绑定固定参数的用法。结合逆波兰表达式求值案例展示实际应用效果。
女王 发布于 2026/3/30 更新于 2026/4/13 1 浏览前言
随着 C++11 的引入,现代 C++ 语言在语法层面上变得更加灵活、简洁。其中最受欢迎的新特性之一就是 lambda 表达式(Lambda Expression) ,它让我们可以在函数内部直接定义匿名函数 。配合 std::function 包装器 使用,可以大大提高代码的表达力与可维护性。
一、lambda 表达式基础语法
lambda 表达式本质上就是一个匿名函数对象,与普通函数不同的是,它可以定义在函数内部;
一般情况下我们是使用 auto 或者模版参数定义的对象去接受 lambda 对象。
lambda 表达式本质上是一个可调用对象(函数对象) ,其语法格式如下:
[capture](parameter_list)-> return_type { function_body };
各部分含义如下:
部分 含义 []捕捉列表(capture list) ()参数列表(与函数类似) -> return_type返回类型(可省略,自动推导) {}函数体
int main () {
auto add = [](int a, int b) -> int { return a + b; };
std::cout << add (2 , 3 );
}
也可以省略 -> int,由编译器自动推导:
int main () {
auto add = [](int a, int b) { return a + b; };
std::cout << add (2 , 3 );
}
这里呢,参数列表,如果不需要传参,可以省略, 也可以省略;返回值类型可以省略,让编译器自行推导。
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown 转 HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
HTML 转 Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
()
而参数列表和函数体,就算为空,参数列表的 [] 和函数体的 {} 也不能省略。
捕获列表 捕获列表决定了 Lambda 表达式如何访问其所在作用域的变量 。
捕获方式 语法 说明 值捕捉 [x]捕获变量 x 的当前值(拷贝) 引用捕捉 [&x]捕获变量 x 的引用 隐式值捕捉 [=]lambda 使用了哪些变量,编译器就会对哪些变量进行值捕捉隐式引用捕捉 [&]lambda 使用了哪些变量,编译器就会对哪些变量进行引用捕捉混合捕捉 [=, &y]除 y 外的所有变量为值捕获,y 为引用捕获 混合捕捉 [&, x]除 x 外的所有变量为引用捕捉,x 为值捕捉
int main () {
int x = 10 , y = 20 ;
auto f1 = [=]() { return x + y; };
auto f2 = [&]() { x += y; };
auto f3 = [x, &y]() { y += x; };
return 0 ;
}
这里 lambda 表达式如果在函数局部域中,它可以捕捉 lambda 位置之前定义的变量,但是不能捕捉静态局部变量和全局变量(静态局部变量和全局变量也不需要捕捉,lambda 表达式中也可以直接使用)。
如果 lambda 定义在全局,那捕捉列表必须为空
值捕获的变量在 Lambda 中是'只读'的,不能修改,除非加上 mutable。
mutable 关键字 默认情况下,lambda 捕捉列表的值是被 const 修饰的,值捕获的变量不能在 Lambda 中被修改 。要想修改值捕获的副本,可以使用 mutable:
int main () {
int a = 5 ;
auto f = [a]() mutable { a += 10 ;
std::cout << a << std::endl;
};
f ();
std::cout << a << std::endl;
return 0 ;
}
lambda 的实际应用场景 std::vector<int > vec = {1 , 2 , 3 , 4 , 5 };
std::for_each(vec.begin (), vec.end (), [](int x) { std::cout << x << " " ; });
auto it = std::find_if (vec.begin (), vec.end (), [](int x) { return x > 3 ; });
if (it != vec.end ()) std::cout << *it;
std::sort (vec.begin (), vec.end (), [](int a, int b) { return a > b;
lambda 的原理
lambda 的原理和范围 for 非常相似,编译之后从汇编的角度来看,我们就要发现根本就没有 lambda 和 范围 for;
范围 for 的底层是迭代器,而 lambda 的底层是仿函数对象,简单来说我们写了一个 lambda,编译器就会生成一对应的仿函数。
而仿函数的类名是编译器按照一定的规则生产的,保证不同的 lambda 生成的仿函数不同。lambda 的参数/返回值/函数体就是仿函数 operator() 的参数/返回类型/函数体。lambda 捕捉列表的本质是生成的仿函数的成员变量。
简单来说就是捕捉列表的量都是 lambda 类构造函数的实参;
值捕捉:lambda 生成的仿函数的成员就是对捕捉变量的值拷贝。引用捕捉:lambda 生成的仿函数的成员就是对捕捉变量的引用。
而也支持隐式捕捉,这个就是编译器看需要用哪些对象,就传哪些对象(用多少捕捉多少 )。
二、包装器
function
function 是一个类模板,也是一个包装器。std::function 的实例化对象可以包装存储其它可以调用的对象:包括 函数指针、仿函数、lambda、bind 表达式;
其中存储的对象被称为 std::function 的目标;如果 std::function 不含目标,那它为空(调用空目标导致抛出 std::bad_function_call 异常)。
template <class T >
class function ;
template <class Ret , class ... Args>
class function <Ret (Args...)>;
以上是 function 的原型,他被定义头文件中
#include <functional>
int main () {
std::function<int (int , int )> func;
func = [](int a, int b) { return a + b; };
std::cout << func (3 , 4 ) << std::endl;
return 0 ;
}
function 的优势在于统一函数接口、做函数回调或作为参数传递。
函数指针、仿函数、lambda 这些可以调用的对象的类型各不相同,std::function 的优势就在于统一类型;
只要它们返回值,参数都相同,function 就能对它们进行包装;这样在很多的地方就可以声明这些可调用对象的类型;
int fun (int a, int b) { return a + b; }
struct Fun {
public :
int operator () (int a, int b) { return a + b; }
};
class Plus {
public :
Plus (int n = 10 ) : _n(n) {}
static int plusi (int a, int b) { return a + b; }
double plusd (double a, double b) { return (a + b) * _n; }
private :
int _n;
};
int main () {
std::function<int (int , int )> f1 = fun;
std::function<int (int , int )> f2 = Fun ();
std::function<int (int , int )> f3 = [](int a, int b) { return a + b; };
std::cout << f1 (1 , 1 ) << std::endl;
std::cout << f2 (1 , 1 ) << std::endl;
std::cout << f3 (1 , 1 ) << std::endl;
std::function<int (int , int )> f4 = &Plus::plusi;
std::cout << f4 (1 , 1 ) << std::endl;
std::function<double (Plus*, double , double )> f5 = &Plus::plusd;
Plus pd;
std::cout << f5 (&pd, 1.1 , 1.1 ) << std::endl;
std::function<double (Plus, double , double )> f6 = &Plus::plusd;
std::cout << f6 (pd, 1.1 , 1.1 ) << std::endl;
std::function<double (Plus&&, double , double )> f7 = &Plus::plusd;
std::cout << f7 (std::move (pd), 1.1 , 1.1 ) << std::endl;
std::cout << f7 (Plus (), 1.1 , 1.1 ) << std::endl;
return 0 ;
}
这里再来看一道可以使用 function 包装优化的题目
class Solution {
public :
int evalRPN (std::vector<std::string>& tokens) {
std::stack<int > st;
for (auto & e : tokens) {
if (e == "+" || e == "-" || e == "*" || e == "/" ) {
int right = st.top (); st.pop ();
int left = st.top (); st.pop ();
switch (e[0 ]) {
case '+' : st.push (left + right); break ;
case '-' : st.push (left - right); break ;
case '*' : st.push (left * right); break ;
case '/' : st.push (left / right); break ;
}
} else {
st.push (std::stoi (e));
}
}
return st.top ();
}
};
这道题,向上述这样写,特别难受好吧,我们可以使用 function 进行优化:
我们知道 +、-、*、/ 运算它返回值和参数类型都是相同的,那我们不妨将其包装起来;
然后使用 map 存储运算符和对应的 函数/调用对象;
这样直接使用 map 的 [] 就可以访问到要调用的函数/对象。
class Solution {
public :
int evalRPN (std::vector<std::string>& tokens) {
std::stack<int > st;
std::map<std::string, std::function<int (int , int )>> mp = {
{"+" , [](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 & e : tokens) {
if (mp.count (e))
{
int right = st.top (); st.pop ();
int left = st.top (); st.pop ();
st.push (mp[e](left, right));
} else {
st.push (std::stoi (e));
}
}
return st.top ();
}
};
如果再多几个运算符,我们只需要在 mp 再新增即可。
从这个角度来看:lambda 算是统一了那些可调用对象的类型,这样对于可调用对象(函数指针、仿函数、lambda),只要参数和返回值相同,那我们就可以使用 function 包装起来,方便调用。
bind 与占位符 simple (1 )
template <class Fn, class ... Args>
bind (Fn&& fn, Args&&... args) ;
with returntype (2 )
template <class Ret, class Fn, class ... Args>
bind (Fn&& fn, Args&&... args) ;
bind 是一个函数模版,它也是一个可调用对象的包装器;简单来说它就是一个函数适配器,可以对接受的 Fn 可调用对象进行处理后返回一个可调用对象。bind 可以用来调整参数个数和参数的顺序。bind 也在这个头文件中。
int Fun (int a, int b) { return (a - b) * 10 ; }
int Func (int a, int b, int c) { return (a - b - c) * 10 ; }
int main () {
auto fun1 = bind (Fun, _1, _2);
std::cout << fun1 (10 , 5 ) << std::endl;
auto Fun2 = bind (Fun, _2, _1);
std::cout << Fun2 (10 , 5 ) << std::endl;
auto fun3 = bind (Fun, 100 , _1);
std::cout << fun3 (5 ) << std::endl;
auto fun4 = bind (Fun, _1, 100 );
std::cout << fun4 (5 ) << std::endl;
auto fun5 = bind (Func, 100 , _1, _2);
std::cout << fun5 (5 , 1 ) << std::endl;
auto fun6 = bind (Func, _1, 100 , _2);
std::cout << fun6 (5 , 1 ) << std::endl;
auto fun7 = bind (Func, _1, _2, 100 );
std::cout << fun7 (5 , 1 ) << std::endl;
return 0 ;
}
调用 bind:auto newCallable = bind(callable, arg_list)(这里 newCallable 本身就是一个可调用对象,arg_list 是参数列表,对应给定的 callable 的参数(也是可调用对象)
这样我们调用 newCallable,newCallable 就会调用 callable,并传给它 arg_list 中的参数。
当我们使用 function 去包装类的非静态成员函数时,我们在调用时总是需要传该类型的对象或者该类型对象的指针,来完成调用;
这样的设计好难看,我每一次调用还要创建一个该类型的对象,那我还不如直接去调用呢
std::function<double (Plus&&, double , double )> f1 = &Plus::plusd;
Plus pd;
std::cout << f1 (std::move (pd), 1.1 , 1.1 ) << std::endl;
std::cout << f1 (Plus (), 1.1 , 1.1 ) << std::endl;
而 bind 这个绑定,我们可以同来绑定一些固定的参数;
就比如这里需要传递该类类型的对象或者该类型对象的指针,我们使用 bind 绑定,直接锁死这个参数,那这样在调用时就不用显示传递了。
std::function<double (double , double )> f2 = bind (&Plus::plusd, Plus (), _1, _2);
std::cout << f2 (1.1 , 1.1 ) << std::endl;