【C++】lambda表达式与std::function/bind包装器

目录

[1. lambda的语法](#1. lambda的语法)

[2. 捕捉列表](#2. 捕捉列表)

[3. 应用场景](#3. 应用场景)

[4. lambda的底层原理](#4. lambda的底层原理)

[5. std::function:统一的可调用对象包装器](#5. std::function:统一的可调用对象包装器)

[6. std::bind:参数适配器](#6. std::bind:参数适配器)


1. lambda的语法

lambda本质是一个匿名函数对象,可以定义在函数内部。完整语法:

text

复制代码
[capture-list] (parameters) -> return-type { function-body }
  • capture-list :捕捉列表,捕捉上下文中需要使用的变量。即使为空也不能省略[]

  • parameters:参数列表,参数为空可以连同括号省略。

  • return-type:返回值类型,可以省略让编译器推导。

  • function-body:函数体,不可以省略。

最简单的例子:

cpp

复制代码
auto add = [](int x, int y) -> int { return x + y; };
auto hello = [] { cout << "hello" << endl; };

lambda之所以用auto接收,是因为它的类型是编译器生成的匿名仿函数类,使用者无法写出具体类型名。

2. 捕捉列表

lambda默认只能使用自己的参数和全局、静态局部变量。想使用外层局部变量,必须捕捉。

  • 显式捕捉[x, &y],x值捕捉,y引用捕捉。

  • 隐式捕捉[=]全部值捕捉,[&]全部引用捕捉。

  • 混合捕捉[=, &x]除x引用外全部值捕捉,[&, x]除x值外全部引用捕捉。混合时第一个必须是=&,且后续捕捉类型必须相反。

cpp

复制代码
int a = 0, b = 1, c = 2;

auto f1 = [a, &b] { return a + b; };  // a值捉,b引用捉
auto f2 = [=] { return a + b + c; };  // 全值捉
auto f3 = [&] { a++; c++; };          // 全引用捉
auto f4 = [=, &a] { /* a引用,其他值 */ };

值捕捉默认是const 的,不可修改。要允许修改(但不影响外部变量),加mutable

cpp

复制代码
auto f5 = [=]() mutable { a++; return a; };  // OK,修改的是副本

注意,全局变量和静态局部变量不能捕捉,也不需要捕捉,lambda内部直接可用。

3. 应用场景

lambda最大的好处是就地定义可调用对象,比写函数指针或仿函数类轻量得多。排序就是典型场景:

cpp

复制代码
vector<Goods> v = { {"苹果", 2.1, 5}, {"香蕉", 3, 4}, ... };

// 按价格升序
sort(v.begin(), v.end(),
     [](const Goods& g1, const Goods& g2) { return g1._price < g2._price; });

// 按评价降序
sort(v.begin(), v.end(),
     [](const Goods& g1, const Goods& g2) { return g1._evaluate > g2._evaluate; });

无需为每种比较规则写一个仿函数类,代码在原地一目了然。

后续在并发编程、智能指针定制删除器等场景中,lambda还会反复出现,适应性很广。

4. lambda的底层原理

编译后不存在什么"lambda语法",编译器会为每个lambda表达式生成一个独一无二的仿函数类。捕捉列表中的变量变成这个类的成员变量,函数体就是operator()的实现。

cpp

复制代码
auto r = [rate](double money, int year) { return money * rate * year; };
// 等价于:
class __lambda_1 {
    double _rate;
public:
    __lambda_1(double r) : _rate(r) {}
    double operator()(double money, int year) const {
        return money * _rate * year;
    }
};
__lambda_1 r(rate);

值捕捉的变量默认被const调用,所以不加mutable时不能在lambda体内修改。引用捕捉不存在这个问题,因为它保存的是引用,修改直接影响外部。

5. std::function:统一的可调用对象包装器

在lambda之前,可调用对象有函数指针、仿函数、成员函数指针等,类型各自为政。std::function提供了一个统一的包装器。

cpp

复制代码
#include <functional>

int f(int a, int b) { return a + b; }

struct Functor {
    int operator()(int a, int b) { return a + b; }
};

auto lambda = [](int a, int b) { return a + b; };

function<int(int, int)> f1 = f;
function<int(int, int)> f2 = Functor();
function<int(int, int)> f3 = lambda;

对于成员函数,需要额外处理隐含的this参数:

cpp

复制代码
class Plus {
public:
    double plusd(double a, double b) { return a + b; }
};

function<double(Plus*, double, double)> f4 = &Plus::plusd;
Plus pd;
f4(&pd, 1.1, 1.1);

function<double(Plus, double, double)> f5 = &Plus::plusd;
f5(pd, 1.1, 1.1);   // 传对象,内部可能拷贝

function的真正威力在于可以把可调用对象存入容器,实现策略表。比如逆波兰表达式求值:

cpp

复制代码
map<string, function<int(int, int)>> opMap = {
    {"+", [](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; }}
};

int ret = opMap[op](left, right);  // 运算符与操作直接映射

新增运算符只需要加一组键值对,不用动核心逻辑。function在这里充当了多态回调的粘合剂。

6. std::bind:参数适配器

bind把一个可调用对象和部分参数绑定,返回一个新的可调用对象。核心用途是调整参数个数和顺序

cpp

复制代码
using namespace std::placeholders;

int Sub(int a, int b) { return a - b; }

auto sub1 = bind(Sub, _1, _2);   // 等价原函数
auto sub2 = bind(Sub, _2, _1);   // 调换参数顺序
auto sub3 = bind(Sub, 100, _1);  // 绑死第一个参数为100
auto sub4 = bind(Sub, _1, 100);  // 绑死第二个参数为100

sub3(5);   // 100 - 5
sub4(5);   // 5 - 100

对于成员函数,可以把对象绑死,省去每次都传对象:

cpp

复制代码
Plus pd;
auto f6 = bind(&Plus::plusd, &pd, _1, _2);  // 绑死对象指针
f6(1.1, 2.2);

在实际应用中,bind可以封装出高度特化的函数对象。比如一个计算复利的lambda,通过bind绑死利率和年限,就能得到不同产品的利息计算函数:

cpp

复制代码
auto calc = [](double rate, double money, int year) -> double {
    double ret = money;
    for (int i = 0; i < year; ++i)
        ret += ret * rate;
    return ret - money;  // 利息部分
};

auto func_3year_1_5 = bind(calc, 0.015, _1, 3);
cout << func_3year_1_5(1000000) << endl;

bind在C++11中是一个重要的适配器,不过后来lambda本身也足够灵活,很多bind的场景可以直接用lambda替代,代码可读性反而更好。选择哪个,看团队习惯和个人偏好。

相关推荐
ECT-OS-JiuHuaShan7 小时前
严格证明:还原论是ASCII,整体论是UTF-8的历史意义和价值
开发语言·人工智能·算法·量子计算
AC赳赳老秦7 小时前
时间开销自动统计:OpenClaw 记录工作任务时长、分析时间分配、给出优化建议
java·大数据·开发语言·python·自动化·deepseek·openclaw
祉猷并茂,雯华若锦7 小时前
Appium 3.x 实战:元素定位与常见错误解析
开发语言·python·appium
Lethehong7 小时前
飞算Java:从需求梳理到风险闭环,搭建云盾企业信息安全与权限管理平台
java·开发语言
旖-旎7 小时前
《LeetCode 416 分割等和子集》
c++·算法·leetcode·动态规划·背包问题
爱喝水的鱼丶8 小时前
SAP-ABAP:ALV数据导出增强——实现Excel/PDF/CSV多格式自定义导出
开发语言·性能优化·sap·abap·erp
Mortalbreeze8 小时前
深入理解 Linux 线程机制(四):线程同步——条件变量与信号量
linux·运维·服务器·开发语言·c++
不会c+8 小时前
C语言:入门到精通(408考研版)系列七 派生的数据类型
c语言·开发语言
不定积分要+C_yyy8 小时前
Java基础核心精讲:基本数据类型与包装类区别、自动装箱拆箱与缓存陷阱实战解析
java·开发语言
xingxiliang8 小时前
ReliableAgent:类似工程级可用的agent示例
java·开发语言·agent