《C++进阶之C++11》【lambda表达式 + 包装器】

【lambda表达式 + 包装器】目录

  • 前言:
  • [------------ lambda表达式 ------------](#------------ lambda表达式 ------------)
    • [1. 什么是lambda表达式?](#1. 什么是lambda表达式?)
    • [2. lambda表达式的基本语法形式?](#2. lambda表达式的基本语法形式?)
    • [3. lambda表达式怎么使用?](#3. lambda表达式怎么使用?)
    • [4. 特殊的捕获限制有哪些?](#4. 特殊的捕获限制有哪些?)
    • [5. lambda表达式的价值是什么?](#5. lambda表达式的价值是什么?)
    • [6. lambda表达式的应用有哪些?](#6. lambda表达式的应用有哪些?)
    • [7. lambda表达式的原理是什么?](#7. lambda表达式的原理是什么?)
  • [------------ 包装器 ------------](#------------ 包装器 ------------)

往期《C++初阶》回顾:

《C++初阶》目录导航


往期《C++进阶》回顾:

/------------ 继承多态 ------------/
【普通类/模板类的继承 + 父类&子类的转换 + 继承的作用域 + 子类的默认成员函数】
【final + 继承与友元 + 继承与静态成员 + 继承模型 + 继承和组合】
【多态:概念 + 实现 + 拓展 + 原理】

/------------ STL ------------/
【二叉搜索树】
【AVL树】
【红黑树】
【set/map 使用介绍】
【set/map 模拟实现】
【哈希表】
【unordered_set/unordered_map 使用介绍】
【unordered_set/unordered_map 模拟实现】

/------------ C++11 ------------/
【列表初始化 + 右值引用】
【移动语义 + 完美转发】
【可变参数模板 + emplace接口 + 新的类功能】

前言:

hi ~,小伙伴们大家好啊,今天是......嗯~o ( ̄▽ ̄) o 对,今天是国庆节!是不是没料到距离上次更新才过去两天,鼠鼠就又来啦?

哈哈,偷偷告诉你们一个小秘密:每月的一号鼠鼠都会准时更新哦~~(ノ>ω<)ノ你要是不信,那就罚你等到 11 月 1 号再来验证,到时候可别忘啦来!(≧▽≦)♪
今天我们要学习的新特性是:【lambda表达式 + 包装器】 。╰(▔∀▔)╯

【lambda 表达式 + 包装器】就像给 "临时工具" 配 "专属收纳盒":→ (っ•̀ᴗ•́)っ✧

  • lambda 是 C++ 里随写随用的 "临时函数",不用单独定义函数名,写在遍历排序、过滤等场景里很轻便,但类型隐蔽,没法直接传参或复用 ╮(╯▽╰)╭
  • 包装器(比如 function)就是 "收纳盒",能把 lambda "装起来" 赋予明确类型,不管传参还是存起来反复用都能搞定,就像临时扳手放进工具盒,好拿好存不麻烦╰(✧∇✧)╯

简单说,lambda 负责 "快速造工具",包装器负责 "好好存工具",两者搭在一起,既能享受 lambda 的便捷,又能解决它的使用局限,写代码时灵活度和实用性直接拉满~ദ്ദി˶>ω<)✧

------------ lambda表达式 ------------

1. 什么是lambda表达式?

lambda表达式:是一种匿名函数,也就是没有名字的函数。

  • 它是一种简洁的函数定义方式,不需要显式命名函数

  • 它可以方便地定义简短的可调用对象,用于各种需要函数对象的场景

  • 可定义在函数内部(普通函数只能定义在全局命名空间中)

  • 语法层面无显式类型,需通过 auto模板参数接收其匿名类型


主要特点:

  1. 匿名性:没有函数名
  2. 简洁性:通常用于简单操作,可以在一行内完成
  3. 临时性:常用于一次性使用或作为参数传递给高阶函数

2. lambda表达式的基本语法形式?

基本语法形式:

cpp 复制代码
[捕获列表](参数列表) -> 返回类型 
{ 
    	函数体 
}


[capture-list] (parameters) -> return-type 
{ 
    	function-body 
}
  • 捕获列表capture-list):用于指定在 lambda 表达式中可以访问的外部变量,是 lambda 能够访问所在作用域中变量的关键。
    • 必须出现在 lambda 最开头,编译器通过 [] 识别 lambda 开始
    • 它可以为空,表示不捕获任何外部变量,但是[] 不能省略(空捕获列表是语法要求)
  • 参数列表parameters):和普通函数的参数列表类似,用于指定 lambda 函数的参数。
    • 它可以为空
    • 若无需参数传递,可连同 () 一起省略 (如:无参 lambda 可写 [] { ... }
  • 返回值类型return-type):指定 lambda 函数的返回值类型。
    • 无返回值时(如:void),可直接省略
    • 返回值类型明确时(如:单一 return 语句),编译器可自动推导,也可省略
  • 函数体function-body):定义 lambda 函数的具体操作和逻辑,也就是函数执行时要做的事情。
    • 除参数外,可直接使用捕获列表中的变量
    • 即使函数体为空(如:[] {}),{}不能省略

通过上面的关于lambda的语法的介绍,现在我们可以明白空lambda的形式了:

cpp 复制代码
// 空lambda(所有可省略部分均省略)
auto emptyLambda = [] {};  // 合法(但无实际逻辑)

2.1:捕获列表

lambda表达式默认仅能访问自身函数体、参数列表里的变量。若需使用外层作用域(如:包含 lambda 的函数作用域)的变量,需通过 捕获列表 显式声明。


捕获列表决定了 lambda 表达式对外部变量的访问方式,常见的捕获方式有以下几种:

显示捕获 :在捕获列表中明确写出变量名,用逗号分隔多个变量,同时通过符号区分捕获方式:

  • 值捕获:直接写变量名(如:[x, y]

  • 引用捕获:变量名前加 &(如:[&z]


隐式捕获:无需逐个写变量名,用符号批量指定捕获规则,编译器会自动识别 lambda 内用到的外层变量并捕获:

  • 按值隐式捕获:捕获列表写 [=] ,lambda 内用到的外层变量全部传值捕获(创建副本)
  • 按引用隐式捕获:捕获列表写 [&] ,lambda 内用到的外层变量全部传引用捕获(使用引用)

混合捕获 :同时用隐式捕获符号=&)和显式变量


值捕获 :在 lambda 内部对捕获变量的修改不会影响外部的原始变量。

  • 例如[a]表示按值捕获变量 a
cpp 复制代码
#include <iostream>
using namespace std;

int main()
{
    //1.定义一个局部变量,初始值为10
    int num = 10;

    //2.定义一个lambda表达式,通过值捕获方式获取外部变量num
    auto lambda = [num]()mutable
        {
            //2.1:尝试修改捕获的副本(注意:修改的是副本,不是原始变量)
            num = 20; //注意:此操作不会影响外部的num变量

            //2.2:输出捕获副本的值(此时副本已被修改为20)
            cout << num << endl;  // 输出: 20
        };

    /* 注意事项:默认情况下,按值捕获的变量在lambda表达式内部是不可修改的(const)
    *    
    *      1. 当你使用值捕获 [num] 时,lambda 内部得到的是 num 的一个副本,
    *      2. 但这个副本默认是 const 的,不能修改。
    *      3. 因此当你尝试在 lambda 内部修改 num 时,编译器会报错。
    *      4. 要使按值捕获的变量可以在 lambda 内部修改,需要使用 mutable 关键字
    */

    //3.调用lambda表达式,执行内部逻辑
    lambda();

    //4.输出外部num的值(未被lambda内部修改)
    cout << num << endl;  // 输出: 10
    return 0;
}

引用捕获 :在 lambda 内部对捕获变量的修改会直接影响外部的原始变量。

  • 例如[&a],表示按引用捕获变量 a
cpp 复制代码
#include <iostream>

int main()
{
    //1.定义一个局部变量 num
    int num = 10;

    //2.定义一个lambda表达式,使用引用捕获的方式捕获外部变量 num
    auto lambda = [&num]()
        {
            //2.1:由于是引用捕获,这里对 num 的修改会直接反映到外部的 num 变量上
            num = 20;

            //2.2:输出修改后的 num 的值,此时会输出 20
            std::cout << num << std::endl;
        };

    /* 注意事项:
    *      1. [&num] 表示以引用的方式捕获变量 num,
    *      2. 这意味着 lambda 表达式内部对 num 的操作会直接作用于外部定义的这个 num 变量
    *      3. 因为它们共享同一块内存空间
    */

    //3.调用定义好的lambda表达式,执行其内部的代码逻辑
    lambda();

    //4.再次输出 num 的值
    std::cout << num << std::endl; //因为 lambda 表达式中通过引用捕获修改了 num,所以这里输出 20
    return 0; 
}

隐式捕获 :可以让编译器自动推导捕获列表。 分为:

  • 按值隐式捕获[=]
  • 按引用隐式捕获[&]
cpp 复制代码
#include <iostream>
int main()
{
    //1.定义两个局部变量
    int num1 = 10;
    int num2 = 20;

    //2.lambda1:按值隐式捕获所有用到的外部变量
    auto lambda1 = [=]()
        {
            //2.1:输出捕获的副本值
            std::cout << num1 << " " << num2 << std::endl;  // 输出: 10 20
        };

    /* 注意事项:
    *   [=]:表示以值捕获方式捕获所有外部变量
    *   lambda内部使用的是外部变量的副本,无法修改原始变量
    */

    //3.lambda2:按引用隐式捕获所有用到的外部变量
    auto lambda2 = [&]()
        {
            //3.1:直接修改外部的num1变量
            num1 = 30;  

            //3.2:输出修改后的外部变量值
            std::cout << num1 << " " << num2 << std::endl;  // 输出: 30 20
        };

    /* 注意事项:
    *   [&]:表示以引用捕获方式捕获所有外部变量
    *   lambda内部使用的是外部变量的引用,可以直接修改原始变量
    */

    //4.调用lambda1:输出捕获时的值副本
    lambda1();  // 输出: 10 20

    //5.调用lambda2:修改外部变量并输出
    lambda2();  // 输出: 30 20

    //6.验证外部变量确实被修改
    std::cout << "外部num1: " << num1 << std::endl;  // 输出: 30

    return 0;
}

混合捕获 :可以同时使用值捕获和引用捕获。

  • 若开头用 [=](隐式传值),后续显式变量必须传引用
    • [=, &x](其他变量隐式传值,x 显式传引用)
  • 若开头用 [&](隐式传引用),后续显式变量必须传值
    • [&, x](其他变量隐式传引用,x 显式传值)

所以[=, &a] 表示按值捕获除 a 以外的所有用到的外部变量,而 a 按引用捕获。


总结:捕获列表的常见形式

捕获列表写法 含义 示例 (外部变量 int a=10; double b=3.14;
[] 不捕获任何外部变量 lambda 内无法使用 ab
[a] 传值捕获 a (修改 lambda 内的 a 不影响外部) lambda 内 a10 的副本
[&b] 传引用捕获 b (修改 lambda 内的 b 会影响外部) lambda 内 b 是外部 b 的引用
[=] 传值捕获所有用到的外部变量 lambda 内 a 是副本,b 是副本 (修改不影响外部)
[&] 传引用捕获所有用到的外部变量 lambda 内 ab 都是引用 (修改会影响外部)
[a, &b] 混合捕获a 传值,b 传引用) lambda 内 a 是副本,b 是引用

2.2:参数列表与返回值

参数列表:和普通函数一样,可以有不同类型的参数,也可以没有参数。

  • []() { std::cout << "Hello Lambda!" << std::endl; } 没有参数,只是输出一条信息
  • [](int a, int b) { return a + b; } 接收两个 int 类型的参数并返回它们的和

返回值

  • 如果函数体只有一条返回语句,返回值类型可以省略,编译器会自动推导。

    cpp 复制代码
    [](int a, int b)   //编译器会自动推导返回值类型为:int
    { 
        return a + b; 
    }
  • 如果函数体有多条语句需要明确返回值类型,就必须显式指定。

    cpp 复制代码
    [](int a, int b) -> bool  //注意:必须显式指定返回类型:bool
    { 
        if (a > b) return true; 
        else return false;
    }

3. lambda表达式怎么使用?

代码案例1:lambda表达式的使用

cpp 复制代码
#include <iostream>
using namespace std;

int main()
{
    /*------------------- 完整语法示例(显式声明所有部分)-------------------*/
    auto add1 = [](int a, int b) -> int
        {
            return a + b;
        };

    /* 定义一个完整语法的lambda表达式:
    *               1. []          空捕获列表(不捕获任何外部变量)
    *               2. (int a, int b) 参数列表,接收两个int类型参数
    *               3. -> int      显式指定返回值类型为int
    *               4. { return a + b; } 函数体,实现两数相加
    */

    cout << "add1(3,4) = " << add1(3, 4) << endl;

    /*------------------- 省略返回值类型(编译器自动推导)-------------------*/
    auto add2 = [](int a, int b)
        {
            return a + b;  // 返回值类型被自动推导为int
        };

    /* 省略返回值类型,编译器根据return语句自动推导返回类型为int
    *  其他部分与add1完全相同
    */

    cout << "add2(5,6) = " << add2(5, 6) << endl;


    /*------------------- 省略参数列表(无参场景)-------------------*/ 
    auto greet = []
        {
            std::cout << "Hello Lambda" << endl;
        };

    /* 省略参数列表的写法:当lambda不需要参数时,()可以连同参数一起省略
    *  但[]和{}不能省略
    */
    greet();


    /*------------------- 省略捕获列表和参数列表(最简形式)-------------------*/
    auto emptyLambda = [] {};

    /* 最简lambda形式:空捕获列表+空参数列表
    *  可以理解为一个无输入、无输出的函数
    */
    emptyLambda();  //调用空lambda(无任何效果,但语法合法)
    return 0;
}

代码案例2:lambda表达式的使用

cpp 复制代码
#include <iostream>
using namespace std;

//定义全局变量 x
int x = 0;

int main()
{
    // ====================== 1. "全局变量"与"空捕获列表" ======================
    auto func1 = []() //注意:全局变量无需捕获即可使用,lambda 捕获列表必须为空
        {
            x++;  
        };


    // ====================== 2. 显式捕获(传值 + 传引用) ======================

    //1.定义局部变量 a,b,c,d
    int a = 0, b = 1, c = 2, d = 3;

    //2.显式捕获:a 传值(const 只读),b 传引用(可修改)
    auto func2 = [a, &b]()
        {
            // a++;  // 错误:传值捕获的变量默认 const,无法修改(除非加 mutable)
            b++;     // 正确:传引用捕获,可直接修改外部变量 b

            int ret = a + b;  // a=0(原值), b=2(已自增)
            return ret;      
        };
    cout << func2() << endl;  


    // ====================== 3. 隐式传值捕获([=]) ======================
    //3.隐式传值捕获所有用到的变量(a, b, c)
    auto func3 = [=]()
        {
            // a++;  // 错误:传值捕获默认 const,无法修改
            int ret = a + b + c;  // a=0, b=2, c=2
            return ret;      
        };
    cout << func3() << endl; 


    // ====================== 4. 隐式传引用捕获([&]) ======================
    //4.隐式传引用捕获所有用到的变量(a, c, d)
    auto func4 = [&]()
        {
            a++;  // 正确:传引用,修改外部 a → a=1
            c++;  // 正确:传引用,修改外部 c → c=3
            d++;  // 正确:传引用,修改外部 d → d=4
        };
    func4();
    cout << a << " " << b << " " << c << " " << d << endl;


    // ====================== 5. 混合捕获(隐式传引用 + 显式传值) ======================
    //5. 隐式传引用(&) + 显式传值(a, b)
    //规则:隐式符号(&)在前,显式变量必须传值
    auto func5 = [&, a, b]()
        {
            // a++;  // 错误:a 是显式传值,默认 const
            // b++;  // 错误:b 是显式传值,默认 const
            c++;  // 正确:隐式传引用,修改外部 c → c=4
            d++;  // 正确:隐式传引用,修改外部 d → d=5

            return a + b + c + d;  // 0 + 1 + 4 + 5 = 10
        };
    func5();

    cout << a << " " << b << " " << c << " " << d << endl;


    // ====================== 6. 混合捕获(隐式传值 + 显式传引用) ======================
    //6.隐式传值(=) + 显式传引用(&a, &b)
    //规则:隐式符号(=)在前,显式变量必须传引用
    auto func6 = [=, &a, &b]()
        {
            a++;  // 正确:显式传引用,修改外部 a → a=2
            b++;  // 正确:显式传引用,修改外部 b → b=3
            // c++;  // 错误:c 是隐式传值,默认 const
            // d++;  // 错误:d 是隐式传值,默认 const

            return a + b + c + d;  // 2 + 3 + 4 + 5 = 14
        };
    func6();

    cout << a << " " << b << " " << c << " " << d << endl;


    // ====================== 7. "静态变量"与"全局变量"的访问 ======================
    //静态局部变量:无需捕获即可使用
    static int m = 0;

    auto func7 = []()
        {
            int ret = x + m;  // 全局变量 x=0,静态变量 m=0
            return ret;       // 返回 0 + 0 = 0
        };
    cout << func7() << endl;


    // ====================== 8. mutable 修饰符(突破传值捕获的 const 限制) ======================
    //隐式传值捕获([=]) + mutable:允许修改传值副本(不影响外部变量)
    auto func8 = [=]() mutable
        {
            a++;  // 修改传值副本 a → 副本 a=3(外部 a 仍为 2)
            b++;  // 修改传值副本 b → 副本 b=4(外部 b 仍为 3)
            c++;  // 修改传值副本 c → 副本 c=5(外部 c 仍为 4)
            d++;  // 修改传值副本 d → 副本 d=6(外部 d 仍为 5)

            return a + b + c + d;  // 3 + 4 + 5 + 6 = 18
        };
    //输出内部变量的和
    cout << func8() << endl;  // 输出: 18

    //输出外部变量
    cout << a << " " << b << " " << c << " " << d << endl; //a=2, b=3, c=4, d=5(未被修改)

    return 0;
}

4. 特殊的捕获限制有哪些?

lambda表达式定义的位置:

若lambda 若定义在函数局部作用域(如:main 函数内 ):

  • 仅能捕获 lambda 定义位置之前 的局部变量
  • 静态局部变量 (static int var; )和 全局变量 无需捕获,可直接在 lambda 内使用(因为:它们的作用域不受函数栈限制 )

若 lambda 定义在全局作用域(函数外 ):

  • 捕获列表必须为空(无外层局部变量可捕获 )

mutable修饰符:突破传值捕获的只读限制:

  • 默认情况下,传值捕获 的变量在 lambda 内是 const 只读的

  • 若需修改传值捕获的副本,可在参数列表后加 mutable,此时参数列表不能省略 (即使无参数,也要写 ()

5. lambda表达式的价值是什么?

lambda表达式通过 简洁语法 实现了匿名函数对象,核心价值在于:

  1. 支持函数内部定义,灵活捕获局部变量
  2. 语法各部分可按需省略,适配不同场景(如:无参、无返回值等)
  3. 配合 auto 使用,无需显式定义函数类型,让代码更简洁

掌握 lambda 的语法结构和捕获规则,是高效使用 C++ 现代特性(如:算法库、异步编程)的基础。

6. lambda表达式的应用有哪些?

在学习 lambda 表达式之前,我们所使用的可调用对象只有 函数指针仿函数对象

  • 函数指针的类型定义较为繁琐
  • 仿函数需要定义一个类,操作相对麻烦

而使用 lambda 来定义可调用对象,简单又便捷。


lambda 在很多场景中也十分好用:

  • 在线程里定义线程的执行函数逻辑
  • 在智能指针中定制删除器等

lambda 的应用场景广泛,后续我们会不断接触到相关用法。


代码案例:lambda表达式应用的优越性(与传统的仿函数进行对比)

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
using namespace std;

/*----------------------"商品结构体"定义----------------------*/
struct Goods
{
    /*------------成员变量------------*/
    string _name;   // 商品名称
    double _price;  // 商品价格
    int _evaluate;  // 商品评价

    /*------------成员函数------------*/
    // 1.构造函数:初始化商品信息
    Goods(const char* str, double price, int evaluate)
        : _name(str)
        , _price(price)
        , _evaluate(evaluate)
    {
    }
};

/*----------------------"打印商品信息的函数对象"----------------------*/
struct PrintGoods
{
    void operator()(const Goods& goods) const
    {
        cout << "名称:" << goods._name
            << ",价格:" << goods._price
            << ",评价:" << goods._evaluate 
            << endl;
    }
};

/*----------------------仿函数:价格升序比较(less)----------------------*/
struct ComparePriceLess
{
    bool operator()(const Goods& gl, const Goods& gr) const
    {
        return gl._price < gr._price; // 价格低的排前面
    }
};

/*----------------------仿函数:价格降序比较(greater)----------------------*/
struct ComparePriceGreater
{
    bool operator()(const Goods& gl, const Goods& gr) const
    {
        return gl._price > gr._price; // 价格高的排前面
    }
};

int main()
{
    // 1.初始化商品列表
    vector<Goods> v =
    {
        {"苹果", 2.1, 5},   // 商品1
        {"香蕉", 3, 4},     // 商品2
        {"橙子", 2.2, 3},   // 商品3
        {"菠萝", 1.5, 4}    // 商品4
    };

    //2.先输出初始商品列表
    cout << "初始商品列表:" << endl;
    for_each(v.begin(), v.end(), PrintGoods());
    cout << "------------------------" << endl;



    // -------------------- "仿函数"排序 --------------------
    // 1. 按价格升序排序
    sort(v.begin(), v.end(), ComparePriceLess());

    cout << "按价格升序排序(仿函数)结果:" << endl;
    for_each(v.begin(), v.end(), PrintGoods());
    cout << "------------------------" << endl;

    // 2. 按价格降序排序
    sort(v.begin(), v.end(), ComparePriceGreater());

    cout << "按价格降序排序(仿函数)结果:" << endl;
    for_each(v.begin(), v.end(), PrintGoods());
    cout << "------------------------" << endl;

    // -------------------- "lambda表达式"排序 --------------------
    // 优势:无需定义额外类,直接在排序处写比较逻辑

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

    cout << "按价格升序排序(lambda)结果:" << endl;
    for_each(v.begin(), v.end(), PrintGoods());
    cout << "------------------------" << endl;

    // 2. 按价格降序排序
    sort(v.begin(), v.end(),
        [](const Goods& g1, const Goods& g2)  // lambda 替代 ComparePriceGreater
        {
            return g1._price > g2._price;
        });

    cout << "按价格降序排序(lambda)结果:" << endl;
    for_each(v.begin(), v.end(), PrintGoods());
    cout << "------------------------" << endl;



    // 3. 按评价升序排序(新需求,仿函数需额外定义类,lambda 直接写)
    sort(v.begin(), v.end(),
        [](const Goods& g1, const Goods& g2)
        {
            return g1._evaluate < g2._evaluate;
        });

    cout << "按评价升序排序(lambda)结果:" << endl;
    for_each(v.begin(), v.end(), PrintGoods());
    cout << "------------------------" << endl;

    // 4. 按评价降序排序(新需求,lambda 直接写)
    sort(v.begin(), v.end(),
        [](const Goods& g1, const Goods& g2)
        {
            return g1._evaluate > g2._evaluate;
        });

    cout << "按评价降序排序(lambda)结果:" << endl;
    for_each(v.begin(), v.end(), PrintGoods());
    cout << "------------------------" << endl;

    return 0;
}

7. lambda表达式的原理是什么?

lambda的原理和范围for类似,从编译后的汇编指令层面来看,不存在 lambda 和范围for 这些语法结构。

范围for 底层基于迭代器实现,而 lambda 底层实则是仿函数对象 ,即我们编写一个 lambda 后,编译器会自动生成一个对应的仿函数类。

  • 仿函数的类名由编译器按特定规则生成,确保不同 lambda 生成的类名互不相同
  • lambda 的参数 ---> 对应仿函数 operator()的参数
  • lambda 的返回类型与函数体 ---> 对应仿函数返回类型与函数体
  • lambda 的捕获列表 ---> 本质是生成的仿函数类的成员变量

也就是说,捕获列表里的变量会作为 lambda 类构造函数的实参,对于隐式捕获,编译器会依据实际使用的变量来传递相应对象。

关于上述原理,我们可通过汇编层进一步了解,下方的汇编代码可印证这些原理。


代码案例:根据lambda表达式和仿函数的关系阐述lambda表达式的本质

cpp 复制代码
#include <iostream>
using namespace std;

// ====================== 仿函数类:Rate ======================
class Rate
{
private:
	double _rate;  // 存储利率的成员变量

public:
	//1.实现:"构造函数"
	Rate(double rate)
		: _rate(rate)  // 用传入的利率初始化成员变量
	{ }

	//2.实现:"重载 operator()"
	double operator()(double money, int year) //目的:实现计算逻辑
	{
		//功能:计算利息 = 本金 * 利率 * 年限
		return money * _rate * year;
	}
};

int main()
{
	//1.定义利率(后续用于仿函数和 lambda)
	double rate = 0.49;

	// ====================== lambda 表达式 ======================
	auto func1 = [rate](double money, int year)
		{
			return money * rate * year;
		};

	/*---------------------- 调用"仿函数对象"----------------------*/
	//1.首先用"仿函数类rate"初始化"仿函数对象func2"
	Rate func2(rate);

	//2.然后调用仿函数
	cout << func2(10000, 2) << endl; //计算 10000 本金,2 年的利息

	/*---------------------- 调用"lambda对象"----------------------*/
	//1.调用lambda对象:
	cout << func1(10000, 2) << endl; //计算 10000 本金,2 年的利息

	return 0; 
}

lambda 的底层是仿函数类:

"匿名仿函数类" 与 "显示仿函数类Rate" 的对比:

cpp 复制代码
// 手动定义的仿函数类
class Rate { ... };  

// 编译器为 lambda 生成的匿名类(逻辑等价)
class __lambda_xxxx 
{ 
private:
    double _rate;  // 对应捕获的 rate
public:
    //1.实现:"构造函数"
    __lambda_xxxx(double rate) 
        : _rate(rate) 
        {}  
    
    //2.实现:"重载 operator()"
    double operator()(double money, int year) 
    { 
        return money * _rate * year; 
    }
};

总结:"lambda" 与 "仿函数" 的关系

特性 手动定义的仿函数(Rate) 编译器生成的 lambda 类
类名 显式定义(Rate) 匿名(由编译器生成唯一名称)
成员变量 手动声明(_rate) 自动捕获外部变量
operator () 逻辑 手动实现 由 lambda 函数体自动生成
调用方式 对象调用(func2 (...) 同仿函数(func1 (...)

lambda表达式的本质:

  • lambda 是 "语法糖",底层完全基于仿函数实现

  • 编写 lambda 时,编译器会自动生成等价的仿函数类,简化了手动定义仿函数的繁琐过程

------------ 包装器 ------------

1. 什么是包装器?

包装器(Wrapper) :是一种能对其他对象、函数、数据等进行封装的机制,目的是简化使用、统一接口或添加额外功能

  • 它就像给原本的东西套了一层 "壳",让操作更方便
  • 常见的包装器有 std::functionstd::bind 以及智能指针(某种意义上也算资源包装器 )

包装器的核心作用:

  • 统一接口 :把不同类型的可调用对象(函数指针、仿函数、lambda 等)"包装" 成相同类型,方便存储、传递。
    • 比如 :lambda 是匿名类型,直接用很难统一管理,但 std::function 能把它们包装成一致的类型
  • 简化使用:隐藏底层复杂细节,调用时不用关心被包装对象的具体类型,按统一方式调用即可。
  • 扩展功能 :可以在包装器里添加额外逻辑(比如:日志、异常处理 ),被包装的对象无需修改。

2. 常见的包装器有哪些?

常见的包装器有:

  • 函数包装器std::function
  • 绑定器std::bind
  • 智能指针std::unique_ptrstd::shared_ptr

2.1:函数包装器(std::function)

C++ 中通过类模板实现 std::function,核心原型简化如下:(标准库简化)

cpp 复制代码
// 前向声明基础模板(未定义具体逻辑)
template <class T>  
class function;  

// 特化模板:用于匹配"返回值为 Ret、参数为 Args..."的可调用对象  
template <class Ret, class... Args>  
class function<Ret(Args...)>;  

std::function :是 C++ 标准库 <functional> 里的 类模板 ,专门用来包装可调用对象函数指针仿函数lambdastd::bind结果等 ),使它们具有统一的调用方式,从而方便存储、传递和管理。


std::function 是可调用对象的 "包装器",关键特性:

  1. 支持可调用对象
    能包裹函数指针、仿函数(函数对象)、lambda 表达式、std::bind 绑定的表达式等
  2. "空" 状态与异常
    std::function 未绑定任何可调用对象(空状态 ),调用时会抛出 std::bad_function_call 异常
  3. 统一类型抽象
    无论原始可调用对象类型多复杂( :lambda 是匿名类型 ),都能被 std::function 包装为相同类型,便于声明和使用

std::function的类型声明:

cpp 复制代码
function<int(int, int)>
  • 这里 std::function<int(int, int)> 就是 "包装器类型"
  • 模板参数 <int(int, int)> 表示:返回值为 int,接受两个 int 参数的可调用对象
  • 可简化理解为:function<返回类型(参数类型列表)>

意义:不管底层是函数指针、仿函数还是 lambda,都被包装成相同类型,调用方式完全一致。


代码示例1 :用 std::function 统一包装不同可调用对象

cpp 复制代码
#include <iostream>   
#include <functional>  
using namespace std;   

//1.实现:两数相加的"普通函数"
int add(int a, int b) 
{
    return a + b;
}


//2.实现:两数相乘的"仿函数"(函数对象)
struct Multiplier 
{
    //重载函数调用运算符,使对象可像函数一样调用
    int operator()(int a, int b) const 
    {
        return a * b;
    }
};

int main() 
{
    // ==================== std::function 包装不同类型的可调用对象 ====================
    //1.包装普通函数:将函数 add 包装为 std::function 类型
    function<int(int, int)> func1 = add;

    //2.包装仿函数:实例化 Multiplier 对象并包装
    function<int(int, int)> func2 = Multiplier();

    //3.包装lambda表达式:将匿名lambda函数包装为 std::function 类型
    function<int(int, int)> func3 = [](int a, int b) 
        {
            return a - b;
        };

    // ==================== 统一调用方式,无需关心底层实现 ====================
    //1.调用"普通函数包装器"
    cout << "func1(3, 4) = " << func1(3, 4) << endl;  // 输出 7(3+4)

    //2.调用"仿函数包装器"
    cout << "func2(3, 4) = " << func2(3, 4) << endl;  // 输出 12(3*4)

    //3.调用"lambda包装器"
    cout << "func3(3, 4) = " << func3(3, 4) << endl;  // 输出 -1(3-4)
    return 0;
}

不同可调用对象的不同包装方式:

  • 普通函数 :直接使用函数名(函数指针)

  • 仿函数 :需要先实例化对象Multiplier()),再进行包装

  • lambda表达式 :直接使用匿名lambda函数

cpp 复制代码
// ==================== std::function 包装不同类型的可调用对象 ====================
//1.包装普通函数:将函数 add 包装为 std::function 类型
function<int(int, int)> func1 = add;

//2.包装仿函数:实例化 Multiplier 对象并包装
function<int(int, int)> func2 = Multiplier();

//3.包装lambda表达式:将匿名lambda函数包装为 std::function 类型
function<int(int, int)> func3 = [](int a, int b)
    {
        return a - b;
    };

不同可调用对象的统一调用语法:

cpp 复制代码
func1(3, 4);  // 无论底层是函数、仿函数还是 lambda,调用方式完全一致

意义 :体现了 std::function 的核心价值:类型擦除,统一接口

  • std::function 内部通过类型擦除技术,封装了不同类型的可调用对象,对外提供统一的调用接口
  • std::function 存在一定的性能开销(虚函数调用),对于性能敏感场景,可考虑使用函数指针或模板

代码示例2 :用 std::function 包装"静态成员函数"和"普通成员函数"

cpp 复制代码
#include<iostream>
#include<functional>
using namespace std;


//实现:包含成员函数的类 Plus
class Plus
{
private:
    /*-----------------成员变量-----------------*/
    int _n;  // 成员变量,影响 plusd 的计算结果

public:
    //1.实现:"构造函数"
    Plus(int n = 10)
        : _n(n)
    { }

    //2.实现:"静态成员函数"
    static int staticFunc(int a, int b)
    {
        return a + b; //(无依赖对象状态)
    }

    //3.实现:"普通成员函数"
    double Func(double a, double b)
    {
        return (a + b) * _n; //(依赖对象的 _n 值)
    }
};



int main()
{
    // ====================== 1. 包装"静态成员函数" ====================== 

    /********************* 静态成员函数无隐含 this 指针,直接取地址即可 *********************/
    function<int(int, int)> f4 = &Plus::staticFunc;
    cout << f4(1, 1) << endl;  // 等价于调用 Plus::staticFunc(1,1),结果 2

    // ====================== 2. 包装"普通成员函数" ======================

    /********************* 普通成员函数有隐含 this 指针,需指定"对象"或"对象指针"调用 *********************/
    Plus pd;  // 创建 Plus 对象

    //1.用对象指针绑定(this 指针为 &pd)
    function<double(Plus*, double, double)> f5 = &Plus::Func;
    cout << f5(&pd, 1.1, 1.1) << endl;  // 调用:pd.Func(1.1, 1.1) → (1.1+1.1)*10(_n 默认为 10)
    

    //2.用对象值绑定(this 指针为 pd 的拷贝)
    function<double(Plus, double, double)> f6 = &Plus::Func;
    cout << f6(pd, 1.1, 1.1) << endl;  //调用:pd.Func(1.1, 1.1) → 同样 (2.2)*10

    //3.用右值引用绑定(this 指针为临时对象)
    function<double(Plus&&, double, double)> f7 = &Plus::Func;

    //3.1:move(pd)将 pd 转为右值,调用临时对象的 Func
    cout << f7(move(pd), 1.1, 1.1) << endl;

    //3.2:直接构造临时对象 Plus(),调用其 Func
    cout << f7(Plus(), 1.1, 1.1) << endl;

    return 0;
}

2.2:绑定器(std::bind)

std::bind有两种典型声明形式:(标准库简化)

cpp 复制代码
// 形式 1:自动推导返回类型
template <class Fn, class... Args>  
auto bind(Fn&& fn, Args&&... args);  

// 形式 2:显式指定返回类型(较少直接使用)
template <class Ret, class Fn, class... Args>  
auto bind(Fn&& fn, Args&&... args);  

std::bind :是定义在<functional>头文件中的 函数模板 ,它的主要作用是对函数可调用对象的参数进行绑定重新排列 ,生成一个新的可调用对象。


std::bind 像一个可调用对象的 "函数适配器",支持:

  1. 固定参数:把可调用对象的某些参数 "固定" 为具体值,减少调用时传递的参数。
  2. 调整参数顺序:重新排列可调用对象的参数顺序。
  3. 参数占位 :用占位符(_1_2 )预留参数位置,让新的可调用对象能接收动态参数。

std::bind的类型声明:

cpp 复制代码
// 调用格式:
auto newCallable = bind(callable, arg_list);  


// 实际运用:
auto newFunc = bind(multiply, 2, placeholders::_1, placeholders::_2);
  • 第一个参数:****callable :必须是被包装的可调用对象(multiply
  • 后续参数:****arg_list :参数列表,支持两种元素
    • 具体值 :(2)会固定 callable 对应参数的位置(这里固定 a=2,不清楚的话可以先看一下下面的代码)
    • 占位符 :(_1_2)(来自 std::placeholders 命名空间 ),表示新可调用对象 newCallable 的参数位置
  • 赋值符左侧的:****newCallable :返回的新可调用对象,调用它时,会触发原始 callable 执行,并传递 arg_list 中配置的参数(包括占位符对应的动态参数 )

总结std::bind的第一个参数是要绑定的可调用对象,后续参数可以是具体的值用于绑定原始函数的参数,也可以是占位符std::placeholders::_n,占位符表示新函数的参数位置。


代码案例 :使用 std::bind 绑定器生成一个新的可调用对象

cpp 复制代码
#include <iostream> 
#include <functional>  
using namespace std;  

//实现:三数相乘的"原始函数"
int multiply(int a, int b, int c) 
{
    return a * b * c;
}

int main() 
{
    // ==================== 使用 std::bind 绑定参数 ====================
    auto newFunc = bind(multiply, 2, placeholders::_1, placeholders::_2);
    /* bind 函数的作用:创建一个新的可调用对象,固定原函数的某些参数
    *  语法:std::bind(原函数, 参数1, 参数2, ...)
    *  这里:
    *      1. 将 multiply 的第一个参数固定为 2
    *      2. 使用占位符 _1 和 _2 表示新函数的第1和第2个参数
    *      3. 占位符来自命名空间 std::placeholders,因 using 语句可直接使用
    */

    // ==================== 调用绑定后的函数 ====================
    cout << "newFunc(3, 4) = " << newFunc(3, 4) << endl;  // 输出 2*3*4=24
    /* 调用 newFunc(3, 4) 时:
    *      3会被传递给占位符 _1,对应 multiply 的第二个参数
    *      4会被传递给占位符 _2,对应 multiply 的第三个参数
    *      等价于调用 multiply(2, 3, 4)
    */

    return 0;
}

占位符的作用:

占位符 std::placeholders::_n :表示新函数的参数位置。(n表示参数位置)

  • placeholders::_1, placeholders::_2, ...:是 std::placeholders 命名空间中的对象
  • placeholders::_1, placeholders::_2, ... :用于指定新函数的参数如何映射到原函数的参数位置
    • 例如_1 表示新函数的第一个参数,在调用时会被传递给原函数对应的位置

新函数的调用逻辑:

cpp 复制代码
newFunc(3, 4);
  • 绑定时a=2(固定值),bc 由占位符指定
  • 调用时34 分别填充到占位符位置,最终执行 2*3*4

调用 newFunc(3, 4);时,参数传递路径为:

cpp 复制代码
3 → _1 → multiply 的第二个参数
4 → _2 → multiply 的第三个参数

调用newFunc(3, 4);等价于调用 multiply(2, 3, 4)


std::bind扩展说明:

  • 多参数绑定

    cpp 复制代码
    // 固定 multiply 的第二个参数为 3
    auto func = bind(multiply, placeholders::_1, 3, placeholders::_2);
    
    func(2, 4);  // 等价于 multiply(2, 3, 4)

  • 参数重排序

    cpp 复制代码
    // 交换原函数的第二个和第三个参数
    auto swapped = bind(multiply, placeholders::_1, placeholders::_3, placeholders::_2); //注:swapped是bind包装multiply后返回的新可调用对象
    
    swapped(2, 3, 4);  // 等价于 multiply(2, 4, 3)

  • 绑定成员函数

    cpp 复制代码
    #include <iostream> 
    #include <functional>  
    using namespace std;
    
    //定义一个包含成员函数的类
    struct Calculator
    {
        //实现:计算两个整数的和的"成员函数"
        int add(int a, int b)
        {
            return a + b;
        }
    };
    
    int main()
    {
        //1.创建 Calculator 类的实例
        Calculator calc;
        // ==================== 使用 std::bind 绑定成员函数 ====================
        //2.绑定成员函数
        auto boundAdd = bind(&Calculator::add, &calc, placeholders::_1, placeholders::_2);
    
        /* 绑定语法:
        *  std::bind(成员函数指针, 对象指针, 参数1, 参数2, ...)
        *
        *  这里:
        *      1. &Calculator::add:成员函数的地址(必须使用 & 取地址)
        *      2. &calc:对象的指针(绑定到哪个对象实例)
        *      3. placeholders::_1, placeholders::_2:新函数的第1和第2个参数
        */
    
        // ==================== 调用绑定后的函数对象 ====================
        //3.调用绑定后的函数对象
        cout << "boundAdd(3, 4) = " << boundAdd(3, 4) << endl;  // 输出 7
        /* 调用 boundAdd(3, 4) 时:
        *          3 和 4 分别填充到占位符 _1 和 _2 的位置
        *          等价于调用 calc.add(3, 4)
        */
        return 0;
    }

成员函数绑定的特殊要求:

cpp 复制代码
bind(&Calculator::add, &calc, ...)
  • 必须使用&取成员函数地址&Calculator::add 是正确写法
  • 必须传递对象指针&calc 指定成员函数要作用于哪个对象实例
  • 若 Calculator::add 是 const 成员函数,则需传递 const Calculator*

绑定成员函数与普通函数的区别:

特性 普通函数绑定 成员函数绑定
第一个参数 函数名(自动转换为函数指针) 必须显式使用 &类名::成员函数名
是否需要对象指针 必须(指定调用该成员函数的对象)
示例 bind(func, _1, _2) bind(&Class::method, &obj, _1)

代码示例:绑定器 bind 的使用大总结

cpp 复制代码
#include <iostream>  
#include <functional>   
#include <utility>    // 用于支持 move 语义
using namespace std; 


// 引入占位符 _1、_2、_3,简化 bind 中参数占位的写法
using placeholders::_1;
using placeholders::_2;
using placeholders::_3;

//1.实现:两参数减法运算,结果放大 10 倍
int Sub(int a, int b)
{
	return (a - b) * 10;
}

//2.实现:三参数减法运算,结果放大 10 倍
int SubX(int a, int b, int c)
{
	return (a - b - c) * 10;
}

//3.类 Plus,包含"静态成员函数"和"普通成员函数"
class Plus
{
public:
	//3.1:静态成员函数:两整数的加法(静态函数属于类,无需对象即可调用)
	static int plusi(int a, int b)
	{
		return a + b;
	}

	//3.2:普通成员函数:两浮点数的加法(需要通过对象调用,隐含 this 指针)
	double plusd(double a, double b)
	{
		return a + b;
	}
};


int main()
{
	// ========== 1. 基础用法:绑定函数参数,保持参数顺序 ==========
	auto sub1 = bind(Sub, _1, _2);
	cout << sub1(10, 5) << endl;  //等价于直接调用 Sub(10, 5),计算 (10 - 5) * 10 = 50



	// ========== 2. 调整参数顺序(展示 bind 灵活调整参数的能力) ==========
	auto sub2 = bind(Sub, _2, _1);
	cout << sub2(10, 5) << endl;  //等价于 Sub(5, 10),计算 (5 - 10) * 10 = -50



	// ========== 3. 固定部分参数(常用场景:简化调用,减少重复传参) ==========
	//1. 固定 Sub 的第一个参数为 100
	auto sub3 = bind(Sub, 100, _1);
	cout << sub3(5) << endl;  //等价于 Sub(100, 5),计算 (100 - 5) * 10 = 950

	//2. 固定 Sub 的第二个参数为 100
	auto sub4 = bind(Sub, _1, 100);
	cout << sub4(5) << endl; //等价于 Sub(5, 100),计算 (5 - 100) * 10 = -950


	// ========== 4. 绑定类成员函数(区分静态和普通成员函数) ==========
	// 方式 1:使用"对象"绑定类Plus的"普通成员函数"plusd
	//1.创建类Plus的对象实例
	Plus pd;

	//2.使用"对象"绑定类Plus的"普通成员函数"
	function<double(Plus, double, double)> f6 = &Plus::plusd;  //绑定"普通成员函数",需传入对象实例才能调用(因为依赖 this 指针)

	//3.通过"对象"调用
	cout << f6(pd, 1.1, 1.1) << endl; //通过 pd 对象调用 plusd(1.1, 1.1),结果 1.1 + 1.1 = 2.2
	//4.通过"临时对象"调用
	cout << f6(move(Plus()), 1.1, 1.1) << endl;  //用临时对象调用,move 优化(也可直接传 Plus(),效果一样,这里展示 move 用法)



	// 方式 2:使用 bind 绑定成员函数plusd + 固定对象实例Plus()
    
	function<double(double, double)> f7 = bind(&Plus::plusd, Plus(), _1, _2); //绑定到临时 Plus 对象,调用时传 _1、_2 对应 plusd 的两个参数
	cout << f7(1.1, 1.1) << endl;  // 等价于 Plus().plusd(1.1, 1.1),结果 2.2

	return 0;
}

类成员函数绑定(f6、f7)

  • 绑定普通成员函数时,必须传入对象实例(pd 或临时对象 Plus()),因为成员函数隐含 this 指针
  • 用 bind 可以 "固定对象实例",让后续调用更简洁( :f7 直接传两个 double 即可)

3. 包装器怎么应用?

3.1:std::function的应用

1. 可调用对象的 "映射表"

  • std::map 建立 "字符串 → 可调用对象" 的映射,通过 std::function 统一类型。

std::function 的作用:统一包装不同的 lambda 表达式(比如:加法、减法等)等,使它们能存储在 map 中,实现 "运算符 → 逻辑" 的映射。

cpp 复制代码
#include <iostream>  
#include <functional>  
#include <map>         
#include <string>  
using namespace std;

int main()
{
    //1.定义一个映射表(map),用于存储"名称→函数逻辑"的对应关系
    /* 说明:
    *     1. 键(key)类型:string(函数的名称标识)
    *     2. 值(value)类型:function<int(int)>(可调用对象包装器,接受int参数并返回int)
    */ 
    map<string, function<int(int)>> funcMap =
    {
        //1.1:向映射表中添加第一个键值对:
        {"double", [](int x) { return x * 2; }},  //键为"double",值为一个lambda表达式(匿名函数)

        //1.2:向映射表中添加第二个键值对:
        {"addFive", [](int x) { return x + 5; }}, //键为"addFive",值为另一个lambda表达式
    };

    //2.调用映射表中的函数逻辑:
    //2.1:通过键"double"从映射表中获取对应的函数,传入参数3并执行
    auto result1 = funcMap["double"](3);
    cout << "调用 double(3) 的结果: " << result1 << endl;

    //2.2:通过键"addFive"从映射表中获取对应的函数,传入参数3并执行
    auto result2 = funcMap["addFive"](3);
    cout << "调用 addFive(3) 的结果: " << result2 << endl;

    return 0;
}

实践案例1150. 逆波兰表达式求值

***方法一:***方传统方式的实现

cpp 复制代码
/*-------------------------- 传统方式的实现 --------------------------*/
class Solution
{
public:
    int evalRPN(vector<string>& tokens)
    {
        //1.定义一个栈,用于存储操作数
        stack<int> stk;

        //2.遍历 tokens 中的每个元素
        for (auto& str : tokens)
        {
            //2.1:如果当前遍历到的字符是"运算符"
            if (str == "+" || str == "-" || str == "*" || str == "/")
            {
                //第一步:先栈顶取出第二个操作数(后入栈的操作数,即右操作数)
                int right = stk.top();
                stk.pop();

                //第二步;再栈顶取出第一个操作数(先入栈的操作数,即左操作数)
                int left = stk.top();
                stk.pop();

                //第三步:根据运算符类型,执行对应的计算
                switch (str[0])
                {
                case '+':
                    stk.push(left + right);
                    break;
                case '-':
                    stk.push(left - right);
                    break;
                case '*':
                    stk.push(left * right);
                    break;
                case '/':
                    stk.push(left / right);
                    break;
                }
            }

            //2.2:如果当前遍历到的字符是"操作数"
            else
            {
                //1.将其转换为 int 后入栈
                stk.push(stoi(str));  // stoi(str):将字符串形式的操作数转换为 int 类型,方便入栈计算
            }
        }

        //3.栈顶元素即为最终结果
        return stk.top();
    }
};

***方法二:***map映射实现

cpp 复制代码
/*-------------------------- 使用 map 映射 string 和 function 的方式实现 --------------------------*/
class Solution 
{
public:
    int evalRPN(vector<string>& tokens) 
    {
        //1.定义一个栈,用于存储操作数
        stack<int> stk;

        //2.定义一个 map,用于映射运算符和对应的计算函数
        /* 说明:
        *    1. key: 运算符(如 "+"、"-"、"*"、"/")
        *    2. value: function<int(int, int)> 类型的可调用对象,接收两个 int 参数,返回一个 int 结果
        */
        map<string, function<int(int, int)>> opFuncMap = 
        {
            //2.1:加法运算
            {"+", [](int x, int y) { return x + y; }},

            //2.2:减法运算
            {"-", [](int x, int y) { return x - y; }},

            //2.3:乘法运算
            {"*", [](int x, int y) { return x * y; }},

            //2.4:除法运算
            {"/", [](int x, int y) { return x / y; }}
        }; 

        //3.遍历 tokens 中的每个元素
        for (auto& str : tokens) 
        {
            //3.1:如果当前遍历到的字符是"运算符"
            if (opFuncMap.count(str)) 
            {
                //第一步:先栈顶取出第二个操作数(后入栈的操作数,即右操作数)
                int right = stk.top();
                stk.pop();

                //第二步;后栈顶取出第一个操作数(先入栈的操作数,即左操作数)
                int left = stk.top();
                stk.pop();

                //第三步:根据运算符,调用对应的计算函数,并将结果入栈
                int ret = opFuncMap[str](left, right);
                stk.push(ret);
            }
            //3.2:如果当前遍历到的字符是"操作数"
            else 
            {
                //1.将其转换为 int 后入栈
                stk.push(stoi(str));  // stoi(str):将字符串形式的操作数转换为 int 类型,方便入栈计算
            }
        }

        //4.栈顶元素即为最终结果
        return stk.top();
    }
};

传统方式实现 VS map映射实现:

传统方式实现:

  • 核心逻辑
    使用 stack 存储操作数,遍历 tokens 时,遇到运算符则从栈中弹出两个操作数,根据运算符类型(switch-case 判断)执行计算,结果重新入栈;遇到操作数则转换为 int 后入栈。
  • 优缺点
    • 优点逻辑直观,适合简单的四则运算场景
    • 缺点:新增运算符时,需要修改 switch-case 分支,扩展性较差

map映射实现:

  • 核心逻辑
    利用 std::map 建立运算符 → 计算函数 的映射关系,遍历 tokens 时,遇到运算符则从 map 中获取对应的计算函数,执行运算;遇到操作数则转换为 int 后入栈。
  • 优缺点
    • 优点扩展性强,新增运算符时只需在 map 中添加映射关系,无需修改核心逻辑;代码更简洁,利用 std::function 和 lambda 表达式简化函数调用
    • 缺点map 的查找效率为 O(log n)n 为运算符数量),理论上略低于 switch-case 的 O ( 1 ) O(1) O(1),但实际场景中差异可忽略

2. 与 lambda/仿函数的配合

  • 因为 std::function 能包裹 lambda,所以可简化代码中 "函数对象类型不明确" 的问题
cpp 复制代码
#include <iostream> 
#include <functional>  
using namespace std;   

int main()
{
    //1.将lambda表达式赋值给std::function对象
    function<void()> printHello = []  
        {
            cout << "Hello, std::function!" << endl;
        };
    /* 说明:
    *    1. function<void()> 表示:包装一个无参数、无返回值的可调用对象
    *    2. lambda表达式 []{} :无捕获列表,无参数,函数体为输出一句话
    */

    //2.调用function对象,本质是执行包装的lambda表达式
    printHello(); 

    return 0;  
}

综上
std::function 是 C++ 中管理可调用对象的 "胶水层",通过统一类型包装,让函数指针、lambda 等能更方便地协同工作,尤其在复杂场景(如:回调管理、策略模式 )中优势显著。

3.2:std::bind的应用

1. 与 lambda/仿函数的配合

cpp 复制代码
#include <iostream>
#include <functional> 
using namespace std;
using namespace std::placeholders; //引入占位符命名空间,让 _1、_2 等直接可用

int main()
{
    // ========== 结合 bind + lambda,实现复杂逻辑(复利计算场景) ==========
    //1.定义lanbda表达式:计算复利利息
	auto func1 = [](double rate, double money, int year) -> double 
		{
			//1.定义变量记录总金额
			double ret = money;

			//2.复利公式:每年利息加入本金,循环 year 次
			for (int i = 0; i < year; ++i)
			{
				ret += ret * rate;
			}

			//3.返回总利息(最终金额 - 本金)
			return ret - money;
		};

	//2.固定利率、年数,只让本金作为可变参数,生成专用函数

	// 场景 1:利率 3.5%,3 年,不同本金 → 调用时只需传本金
	function<double(double)> func3_3_5 = bind(func1, 0.035, _1, 3);
	// 场景 2:利率 1.5%,5 年
	function<double(double)> func5_1_5 = bind(func1, 0.015, _1, 5);
	// 场景 3:利率 2.5%,10 年
	function<double(double)> func10_2_5 = bind(func1, 0.025, _1, 10);
	// 场景 4:利率 3.5%,30 年
	function<double(double)> func30_3_5 = bind(func1, 0.035, _1, 30);

	//3.测试:本金 1000000,计算各场景利息
	cout << "场景 1(3.5%利率,3年)利息:" << func3_3_5(1000000) << endl;
	cout << "场景 2(1.5%利率,5年)利息:" << func5_1_5(1000000) << endl;
	cout << "场景 3(2.5%利率,10年)利息:" << func10_2_5(1000000) << endl;
	cout << "场景 4(3.5%利率,30年)利息:" << func30_3_5(1000000) << endl;

	return 0;
}

std::bind 的价值总结:

  • 解耦参数传递:无需修改原始函数,就能调整参数的传递逻辑。
  • 适配复杂调用:在回调函数、策略模式中,灵活调整参数,让接口更通用。
  • 简化重复调用:固定常用参数,减少重复传参的冗余代码。

简单说std::bind 是 C++ 中灵活处理 "可调用对象参数" 的工具,通过包装和适配,让函数调用更灵活、更贴合复杂场景~

相关推荐
Hello_Embed2 小时前
STM32 智能垃圾桶项目笔记(四):PWM 回顾与舵机(SG90)控制实现
笔记·stm32·单片机·学习·嵌入式软件
QL.ql2 小时前
MOS管简单入门笔记(主讲NMOS,PMOS不常用)
笔记
想唱rap3 小时前
归并排序、计数排序以及各种排序稳定性总结
c语言·数据结构·笔记·算法·新浪微博
零一iTEM3 小时前
NS4168输出音频通过ESP32C3测试
c++·单片机·嵌入式硬件·mcu·音视频·智能家居
charlie1145141913 小时前
精读C++20设计模式:结构型设计模式:装饰器模式
笔记·学习·设计模式·程序设计·c++20·装饰器模式
charlie1145141913 小时前
精读C++20设计模式——行为型设计模式:解释器模式
c++·学习·设计模式·解释器模式·c++20
zhangxuyu11183 小时前
flex布局学习记录
前端·css·学习
郭源潮13 小时前
《Muduo网络库:实现Channel通道以及Poller抽象基类》
服务器·c++·网络库