C++中function包装器的应用实例详解

 更新时间:2024年12月31日 08:55:00   作者:藤椒味的火腿肠真不错  
这篇文章主要介绍了C++中function包装器的相关资料,std::function是C++11引入的一个模板类,用于封装任何可调用对象,使得函数能够像对象一样传递、存储和调用,文中通过代码介绍的非常详细,需要的朋友可以参考下

前言:

在现代 C++ 中,std::function 是一个非常有用的工具,它使得函数能够像对象一样传递、存储和调用。随着 C++11 的到来,std::function 被引入到标准库中,成为函数式编程和回调机制的核心组件之一。在这篇博客中,我们将深入探讨 std::function 的工作原理、应用场景及其优缺点。

 1.什么是 std::function?

std::function 是 C++11 引入的一个模板类,用于封装任何可调用对象如普通函数、Lambda 表达式、函数指针、成员函数指针或函数对象等)。它允许你存储一个可调用对象,并在需要时调用它。这使得我们可以更加灵活地编写代码,特别是在需要传递回调函数或异步任务时,std::function 显得尤为重要。

std::function 是通过类型擦除实现的,它可以在运行时动态地将不同类型的可调用对象转化为统一的接口。简单来说,它允许你用一个通用的对象来代替不同类型的函数或函数指针

2. function 包装器的原型

std::function在头文件<functional>

// 类模板原型如下

template <class T> function; // undefined

template <class Ret, class... Args>

class function<Ret(Args...)>;

模板参数说明:

Ret: 被调用函数的返回类型Args…:被调用函数的形参。

 3.使用 function 封装不同类型的函数对象

#include <iostream>
#include <functional>
#include <string>

using namespace std;

// 普通函数
void func(int n)
{
    cout << "普通函数: " << n << endl;
}

// 仿函数
struct Func
{
    void operator()(int n)
    {
        cout << "仿函数: " << n << endl;
    }
};

// Lambda 表达式
auto lambda = [](int n) { cout << "Lambda 表达式: " << n << endl; };

int main()
{
    // 使用 std::function 封装不同类型的函数
    function<void(int)> f;

    f = func;           // 包装普通函数
    f(10);

    f = Func();         // 包装仿函数
    f(20);

    f = lambda;         // 包装 Lambda 表达式
    f(30);

    return 0;
}

代码分析

我们定义了三个不同类型的函数:一个普通函数 func、一个仿函数 Func 和一个 Lambda 表达式 lambda

然后,使用 std::function<void(int)> 来封装这三种不同类型的函数对象。

通过调用包装后的 f,我们可以统一的方式执行这些不同的函数对象。(适配器)

这种方式使得我们能够将多种类型的可调用对象统一为一个接口,方便管理和使用。

4.实际应用:

 150. 逆波兰表达式求值 - 力扣(LeetCode)

 解法一:

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
      stack<int> st;
      for(auto& to:tokens)
      {
        if(to=="+"||to=="-"||to=="*"||to=="/")
        {
            int right=st.top();
            st.pop();

            int left=st.top();
            st.pop();
           switch(to[0])
           {
            case '+':
            st.push(left+right);
            break;
            
            case '-':
            st.push(left-right);
            break;

            case '*':
            st.push(left*right);
            break;
           
            case '/':
            st.push(left/right);
            break;
           }
        }
        else
        {
            st.push(stoi(to));
        }
      }
    return st.top();
    }
};

解法二: 利用function包装器:

class Solution 
{
public:
    int evalRPN(vector<string>& tokens) 
    {
        // 解题思路:操作数入栈,遇到操作符,取两个数计算后,入栈

        // 建立映射关系
        unordered_map<string, function<int(int, int)>> hash = 
        {
            {"+", [](int x, int y)->int { return x + y; } },
            {"-", [](int x, int y)->int { return x - y; } },
            {"*", [](int x, int y)->int { return x * y; } },
            {"/", [](int x, int y)->int { return x / y; } },
        };

        stack<int> s;

        for(auto str : tokens)
        {
            if(str != "+" && str != "-" && str != "*" && str != "/")
                s.push(stoi(str));
            else
            {
                // 注意:先获取 y,再获取 x
                int y = s.top();
                s.pop();
                int x = s.top();
                s.pop();

                s.push(hash[str](x, y));
            }
        }

        return s.top();
    }
};

function作为C++11的一个知识,还是非常好用的。😊

 5. bind 绑定:修改参数传递顺序和数量

bind 是 C++ 标准库中的一个函数模板,它允许我们对函数参数进行预先绑定或重新排列,从而生成一个新的可调用对象。bind 的强大之处在于,它不仅能够指定某些参数的固定值,还能改变参数传递的顺序,极大地提高了灵活性。

函数原型:

template <class Fn, class... Args>
bind (Fn&& fn, Args&&... args);

 fn 是传递的 函数对象args 是传给函数的 可变参数包,这里使用了 万能引用(引用折叠),使其在进行模板类型推导时,既能引用左值,也能引用右值。

2.1 使用 bind 绑定修改参数传递顺序

#include <iostream>
#include <functional>

using namespace std;

void Func(int a, int b)
{
    cout << "Func: " << a << " " << b << endl;
}

int main()
{
    // 正常调用
    Func(10, 20);

    // 使用 bind 改变参数顺序
    auto RFunc = bind(Func, std::placeholders::_2, std::placeholders::_1);
    RFunc(10, 20);  // 输出: Func: 20 10

    return 0;
}

代码分析

bind(Func, std::placeholders::_2, std::placeholders::_1) 通过 placeholders::_1 和 placeholders::_2 指定了新的参数顺序,即将原本的第二个参数和第一个参数交换。

当我们调用 RFunc(10, 20) 时,实际上是将 20 作为第一个参数,10 作为第二个参数传递给 Func。

这种参数顺序的改变,在一些特定的应用场景下非常有用,特别是在函数签名不一致时,可以方便地进行适配。

2.2. bind 绑定:指定特定参数

bind 还可以用于指定函数的某些参数为固定值,从而减少后续调用时需要传递的参数个数。

示例代码:使用 bind 绑定指定特定参数

#include <iostream>
#include <functional>

using namespace std;

void Func(int a, int b)
{
    cout << "Func: " << a << " " << b << endl;
}

int main()
{
    // 使用 bind 绑定第一个参数
    auto RFunc = bind(Func, 100, std::placeholders::_1);
    RFunc(20);  // 输出: Func: 100 20

    return 0;
}

代码分析

我们通过 bind(Func, 100, std::placeholders::_1) 将第一个参数绑定为固定值 100

后续调用时,我们只需要传递第二个参数 20bind 会自动将 100 作为第一个参数传递给 Func

2.3. bind 绑定与类成员函数

bind 还可以用于绑定类成员函数。对于普通函数,绑定非常简单,但对于成员函数,我们需要额外注意如何传递类的对象或指针。

示例代码:使用 bind 绑定静态成员函数

#include <iostream>
#include <functional>

using namespace std;

class Test
{
public:
    static void funcA(int val)
    {
        cout << "静态成员函数 funcA: " << val << endl;
    }
};

int main()
{
    // 使用 bind 绑定静态成员函数
    auto RFunc = bind(&Test::funcA, std::placeholders::_1);
    RFunc(10);  // 输出: 静态成员函数 funcA: 10

    return 0;
}

代码分析

对于静态成员函数,我们可以直接使用 &Test::funcA 来绑定。

bind 会自动处理函数的绑定,并返回一个新的可调用对象 RFunc我们可以使用它来调用函数。

 示例代码:使用 bind 绑定非静态成员函数

#include <iostream>
#include <functional>

using namespace std;

class Test
{
public:
    Test(int n) : _n(n) {}

    void funcB(int val)
    {
        cout << "非静态成员函数 funcB: " << val * _n << endl;
    }

private:
    int _n;
};

int main()
{
    Test t(10);
    // 使用 bind 绑定非静态成员函数
    auto RFunc = bind(&Test::funcB, t, std::placeholders::_1);
    RFunc(5);  // 输出: 非静态成员函数 funcB: 50

    return 0;
}

 代码分析

对于非静态成员函数,我们需要提供类的对象 t 作为参数来绑定。

bind 会将 t 与 &Test::funcB 结合,并生成一个新的可调用对象。

总结:

通过 std::function 和 bind,C++ 提供了强大的函数包装和绑定功能,使得我们能够在不同类型的函数之间进行无缝切换、修改参数传递顺序以及绑定特定参数。这些工具极大地增强了代码的灵活性和可重用性,特别是在需要对多个不同函数进行统一管理时,它们提供了非常便捷的解决方案。在实际开发中,这些技巧不仅能帮助我们提升编程效率,还能让代码更加简洁和优雅。

到此这篇关于C++中function包装器应用的文章就介绍到这了,更多相关C++ function包装器应用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C++程序内存栈区与堆区模型案例分析

    C++程序内存栈区与堆区模型案例分析

    一直以来总是对这个问题的认识比较朦胧,我相信很多朋友也是这样的,总是听到内存一会在栈上分配,一会又在堆上分配,那么它们之间到底是怎么的区别呢,让我们一起来看看
    2022-03-03
  • EasyC++模板重载

    EasyC++模板重载

    这篇文章主要介绍了C++模板重载,重载的模板的函数特征,也就是入参的数量和类型必须有所不同,下面我们讲举例说明此内容,具有一定的参考价值,需要的小伙伴可以参考一下
    2021-12-12
  • C++20中的协程(Coroutine)的实现

    C++20中的协程(Coroutine)的实现

    这篇文章主要介绍了C++20中的协程(Coroutine)的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03
  • 判断本机office安装版本的方法分享

    判断本机office安装版本的方法分享

    这篇文章主要介绍了判断本机office安装版本的方法分享,需要的朋友可以参考下
    2014-01-01
  • C语言实现个税计算器

    C语言实现个税计算器

    这篇文章主要为大家详细介绍了C语言实现个税计算器,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-10-10
  • C++带头双向循环链表超详细解析

    C++带头双向循环链表超详细解析

    带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单
    2022-03-03
  • Qt图形图像开发曲线图表模块QChart库基本用法、各个类之间的关系说明

    Qt图形图像开发曲线图表模块QChart库基本用法、各个类之间的关系说明

    这篇文章主要介绍了Qt图形图像开发曲线图表模块QChart库基本用法、各个类之间的关系说明,需要的朋友可以参考下
    2020-03-03
  • C++中的数字转字符串to_string

    C++中的数字转字符串to_string

    这篇文章主要介绍了C++中的数字转字符串to_string,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-11-11
  • Qt MQTT开发环境搭建的实现示例

    Qt MQTT开发环境搭建的实现示例

    本文主要介绍了Qt MQTT开发环境搭建的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-06-06
  • 一篇文章带你了解C++特殊类的设计

    一篇文章带你了解C++特殊类的设计

    这篇文章主要为大家详细介绍了C++特殊类的设计,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-02-02

最新评论