[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;
    }
}

输出:

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

相关推荐
傲世仙尊1 小时前
从磁盘硬件到Ext文件系统-Linux磁盘级文件系统学习笔记
linux·运维·服务器·开发语言·c++
小羊没烦恼!2 小时前
Memory 记忆设计讨论:为什么 Agent Memory 不能只靠向量数据库?
java·开发语言·windows·算法·c#
FlightYe2 小时前
音视频修炼之编码器(一):AVC、HEVC编码器内部
android·linux·c++·音视频
黑马程序员毕设2 小时前
基于微信小程序的节目活动报名与管理系统设计与实现
java·开发语言·spring boot·后端·电脑
羑悻的小杀马特2 小时前
数据织网者:Jsoncpp库深度解析——从C++原生JSON处理到工程级数据交互实战全攻略
c++·json·交互·jsoncpp·原生库
sunshine22 girl2 小时前
Java学习 java原理
java·开发语言·学习
果果燕2 小时前
实习笔记(七)GFP自动化插拔测试功能协议解析
c++
宁渡AI大模型2 小时前
河南宁渡科技有限公司|宁渡课堂 AI 全栈面试分享,AI 应用开发高频考点汇总
java·c++·人工智能·python·深度学习·神经网络·rag
GG-_-Bond3 小时前
9.1 kv存储内存池模拟面试问题
c++·c