[C++] C++11 Lambda与包装器深度解析

1. lambda

1.1 表达式语法

lambda对象是一个匿名函数对象,可以定义在函数内部,用auto或者模板参数接收。

表达式格式:

cpp 复制代码
[capture-list] (parameters)-> return type {function boby }
  1. capture-list:捕捉列表,可以捕获lambda外部的变量供其使用,不可省略
  2. parameters:参数列表,可省略
  3. return type:返回值类型,可省略(编译器推导)
  4. function boby:函数体,不可省略
cpp 复制代码
int main()
{
    //省略参数和返回值
    auto eg1 = []{cout << "hello, world" << endl;};
    eg1();
}

正常运行:

1.2 捕捉列表

分为显式,隐式和混合捕捉,传参支持传值和传引用:

显式捕捉:和传参方式类似,传值捕捉不可修改(const),传引用捕捉可以修改:

cpp 复制代码
    //显式捕捉
int main()
{
    int x = 0, y = 1, z = 2;
    auto eg2 = [&x, y, z]
    {
        ++x;
        // ++y; //不可修改
        cout << x << " " << y << " " << z << endl;
    };
    eg2();
}

++y报错信息:

隐式捕捉 :传值捕捉(=),不可修改;传引用捕捉(&)。会自动捕捉lambda中用到的外部变量:

cpp 复制代码
    //隐式捕捉
int main()
{
		int x = 1, y = 2, z = 3;
    auto eg3 = [=]
    {
        cout << x << " " << y << " " << z << endl;
    };
    eg3();

    auto eg4 = [&]
    {
        x += 10;
        y += 10;
        z += 10;
        cout << x << " " << y << " " << z << endl;
    };
    eg4();
}

混合捕捉 :显式和隐式捕捉同时使用,第一个元素必须是隐式,且不可冲突(=后无值捕捉,&后无引用捕捉):

cpp 复制代码
    //混合捕捉
int main()
{
		int x = 1, y = 2, z = 3;
    auto eg5 = [&, z]
    {
        x -= 10;
        y -= 10;
        cout << x << " " << y << " " << z << endl;
    };
    eg5();
}

补充:

  • 全局lambda的捕捉列表必须为空
  • 传值捕捉默认const,但可以加mutable关键字取消常量属性,但仅修改lambda内部临时参数,不会影响外部实参
cpp 复制代码
int main()
{
    int x = 1, y = 2, z = 3;
    //C++20中,mutable存在时省略参数列表的()是非法的
    auto eg6 = [=]() mutable
    {
        x += 1;
        y += 1;
        z += 1;
        cout << x << " " << y << " " << z << endl;
    };

    eg6();
}

输出:

1.3 原理和应用

  1. 原理:底层是仿函数对象,通过捕捉列表传入的参数构造仿函数类,再调用对应函数并传入参数列表
  2. 应用:定义可调用对象(函数) / 线程 / 智能指针...

底层模拟展示:

cpp 复制代码
int Add(int a, int b)
{
    return a + b;
}


int main()
{
    int x = 1, y = 2, z = 3;
    
    auto eg7 = [x](int a, int b)
    {
        Add(a, b);  
    };

    eg7(y, z);
}

//底层为类似的仿函数对象:
class LambdaClass_eg7 //类名按照一定规则生成
{
private:
    int _x;

public:
    LambdaClass_eg7(int x)
    :_x(x)
    {}

    void operator()(int a, int b) const //不嫩修改x
    {
        Add(a, b);
    }
};

2. 包装器

2.1 function

官方文档:

function

  • 介绍:std::function是类模板,实例出的对象可以存储其他可调用对象(函数指针/仿函数/lambda/bind等),不可为空,否则会抛出std::bad_function_call的异常
  • 头文件:<functional>
  • 优势:统一类型,方便声明/调用可调用对象

包装示例:

cpp 复制代码
//function包装器
void eg1()
{
    cout << "eg1" << endl;
}

struct Less
{
    bool operator()(int x, int y)
    {
        cout << "less()" << " ";
        return x < y;
    }
};

class Student
{
private:
    string _name;
    int _no;

public:
    Student(string name, int no)
    :_name(name)
    ,_no(no)
    {}

    void Print() const
    {
        cout << _name << " " << _no << endl;
    }

    static int add(int x, int y) const
    {
        cout << "mzh::static_add" << " ";
        return x + y;
    }
};

int main()
{
    function<void()> Func0 = eg1; //包装函数
    function<bool(int, int)> Func1 = Less(); //包装仿函数
    function<int(int, int)> Func2 = [](int x, int y){ cout << "lambda" << " "; return x - y;}; //包装lambda
    function<void(Student*)> Func3 = &Student::Print; //包装类内成员函数(内部走->*)
    function<void(Student&)> Func4 = &Student::Print; //包装类内成员函数(内部走.*)
    function<int(int, int)> Func5 = &Student::add; //包装类内静态成员函数

    int x = 1, y = 10;
    Func0();
    cout << Func1(x, y) << endl;
    cout << Func2(x, y) << endl;
    Student s1 = {"LiHua", 20250101};
    Func3(&s1);
    Func4(s1);
    cout << Func5(x, y) << endl;
    return 0;
}

输出:

统一包装可调用对象的例子(用function包装器代替if-else):

逆波兰表达式求值 - ⼒扣(LeetCode)

cpp 复制代码
class Solution {
public:
    int evalRPN(vector<string>& tokens)
    {
        stack<int> st;

        map<string, function<int(int, int)>> FuncStr = 
        {
            {"+", [](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& s : tokens)
        {
            if(FuncStr.count(s))
            {
                int right = st.top();
                st.pop();
                int left = st.top();
                st.pop();

                st.push(FuncStr[s](left, right));
            }
            else
            {
                st.push(stoi(s));
            }
        }

        return st.top();
    }
};

2.2 bind

  • 介绍:bind是一个函数模板(函数适配器),可以在包装函数的同时调整参数个数和顺序
  • 头文件:<functional>

调用bind:

cpp 复制代码
auto newCallable = bind(callable,arg_list);
  1. newCallable:包装callable的可调用对象
  2. callable:可调用对象(在调用newCallable时调用)
  3. arg_list:参数列表,可能包含placeholders::_n,用来指代newcallable中对应位置的参数(eg:第一个参数_1 / 第二个_2 / ...),剩余参数死绑定
  4. 参数在bind所在行发生值拷贝,后续修改参数不会作用于newCallable

使用示例 / 应用(复利计算):

cpp 复制代码
bind绑定
namespace bind
{
    using placeholders::_1;
    using placeholders::_2;
    using placeholders::_3;

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

    class Culculate
    {
    private:
        int _x = 5;
        int _y = 2;

    public:
        int Plus1()
        {
            return _x + _y;
        }

        int Plus2(int x, int y)
        {
            return x + y + _x + _y;
        }
    };


    int main()
    {
        auto sub1 = bind(Sub, _1, _2); //绑定函数
        cout << sub1(10, 2) << endl; //8

        auto sub2 = bind(Sub, _2, _1); //修改参数顺序
        cout << sub2(10, 2) << endl; //-8
        
        auto sub3 = bind(Sub, _1, 100); //绑死固定参数(常用)
        cout << sub3(20) << endl; //-80

        Culculate c1;

        function<int()> func1 = bind(Culculate::Plus1, c1); //固定对象
        function<int(int, int)> func2 = bind(Culculate::Plus2, Culculate(), _1, _2); //固定对象
        cout << func1() << endl; //7
        cout << func2(10, 20) << endl; //37


        //应用:和function搭配,复利计算(通过绑死参数实现计算不同金额在指定期限和利率下的利润)
        auto CpInterest = [](double rate, double money, double year)->double
        {
            double ret = money;
            for(int i=0; i<year; ++i)
            {
                ret += ret * rate;
            }

            return ret - money;
        };

        function<double(double)> f1 = bind(CpInterest, 0.015, _1, 3);
        function<double(double)> f2 = bind(CpInterest, 0.015, _1, 5);
        function<double(double)> f3 = bind(CpInterest, 0.025, _1, 10);
        function<double(double)> f4 = bind(CpInterest, 0.035, _1, 30);

        cout << f1(1000000) << endl;
        cout << f2(1000000) << endl;
        cout << f3(1000000) << endl;
        cout << f4(1000000) << endl;

        return 0;
    }
}

输出:

感谢阅读,我们下篇文章见。

相关推荐
sunshine22 girl22 分钟前
Java学习六 Java常见API-1-Radom随机数
java·开发语言·学习
可乐鸡翅yeah_26 分钟前
hls.js 手动自定义 http 请求 loader,修改请求头实战
开发语言·前端·javascript·网络协议·http·ecmascript·m3u8在线
潼心1412o42 分钟前
C++初阶(长期更新)第6讲:类和对象(下)
开发语言·c++·算法
夜晚回家1 小时前
Python 零基础入门 | 输入的数字为什么不能直接算
开发语言·python
时间的拾荒人1 小时前
Qt 事件机制全解析:从事件处理到事件过滤器
开发语言·qt·面试
-森屿安年-1 小时前
按身高排序
c++·算法
于指尖飞舞1 小时前
java面试题总结
java·开发语言
(Charon)2 小时前
【C++面试】vector底层原理:扩容、拷贝移动与迭代器失效
c++·面试
zhangzeyuaaa3 小时前
深入理解 Ruby 运算符:本质、分类、坑点与重载实战
开发语言·前端·ruby
一木 之林3 小时前
DeepSeek Agent 开发(一)
开发语言·前端·javascript