C++11 新特性:Lambda表达式、std::function与std::bind

C++11 引入了很多非常实用的新特性,其中Lambda 表达式std::functionstd::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. 捕获列表规则

捕获分为值捕获引用捕获隐式捕获混合捕获

  1. 显式捕获[x, &y],x 值捕获,y 引用捕获,多个变量逗号隔开。

  2. 隐式捕获

    1. [=]:全部外部变量值捕获

    2. [&]:全部外部变量引用捕获

  3. 混合捕获

    混合捕获时,=&必须写在捕获列表最前面

    1. [=, &x]:默认值捕获,唯独 x 用引用捕获

    2. [&, x]:默认引用捕获,唯独 x 用值捕获

  4. 不能捕获的变量:静态局部变量、全局变量不需要捕获,可以直接在 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可以减少传入参数个数、调换参数顺序,适配回调接口的参数要求。

四、三者搭配使用总结

  1. Lambda:快速写匿名的仿函数,适合一次性回调,代码简洁。

  2. std::function:统一包装所有可调用对象,提供统一类型,用于回调容器。

  3. std::bind:改造已有函数,固定参数、调换参数顺序,适配接口。

常见场景:容器算法传参、回调函数、事件驱动、策略模式。

补充易错点

  1. Lambda 捕获引用要注意生命周期:引用捕获一个局部变量,lambda 如果延迟调用,原变量销毁会野引用。

  2. 值捕获默认 const,修改必须加mutable,但修改只改副本。

  3. bind 占位符_n里的 n,代表新函数的参数序号,不是原函数。

  4. std::function空对象调用会抛异常,使用前最好判断是否有效。

相关推荐
小溪学编程1 小时前
Java ListIterator 接口详解:双向遍历与列表修改的利器
java·开发语言·windows
sbjdhjd1 小时前
在 8 字符、字母数字过滤与 PHP 版本差异下复盘临时文件命令执行链 | (8字符绕过)
android·java·开发语言·安全·web安全·数据挖掘·php
欧特克_Glodon1 小时前
OpenCV计算机视觉开发入门与实践<三十六>:机器学习概述
c++·opencv·机器学习·计算机视觉
小七在进步1 小时前
C++入门(3)
android·java·c++
统计学小王子1 小时前
数学建模国赛倒计时 1 天 ——《统计模型精讲(逐步回归 R 语言实战篇)》
开发语言·数学建模·r语言
码匠许师傅1 小时前
【设计模式精讲】27.模板方法模式(Template Method)
c++·设计模式·uml·模板方法模式
王老师青少年编程2 小时前
信奥赛csp初赛高频考点【排序算法】(详细解析)
c++·排序算法·csp-j·csp·高频考点·初赛·信奥赛
ttwuai2 小时前
Go 后台接入单点登录后,JWT、菜单权限和接口 403 怎么验?
开发语言·后端·golang
qeen872 小时前
【C++】C++中的错误处理机制-异常
开发语言·c++·异常