【C++入门到精通】function包装器 | bind() 函数 C++11 [ C++入门 ]

简介: 【C++入门到精通】function包装器 | bind() 函数 C++11 [ C++入门 ]

引言

很高兴再次与大家分享关于 C++11 的一些知识。在上一篇文章中,我们讲解了 condition_variable 的使用方法。今天,我们将继续探讨 C++11 中的两个重要概念:function 包装器和 bind() 函数。这两个概念在 C++11 中具有非常重要的作用,它们可以帮助我们更好地管理函数指针和函数对象,并且可以大大提高代码的可读性和可维护性。在接下来的这篇文章中,我们将深入探讨这两个概念的使用方法和注意事项。

一、function包装器

1. 概念

当我们在C++中使用函数指针或函数对象时,经常会遇到一些灵活性和可复用性的问题。C++11引入了一个强大的工具——function包装器,它可以解决这些问题。

function包装器是一个通用的函数封装器可以容纳各种可调用对象(如函数指针、函数对象、成员函数指针等),并提供统一的接口来调用这些可调用对象。通过使用function包装器,我们可以将不同类型的可调用对象存储在一个容器中,方便统一管理和调用。

2. 基本使用

function包装器的使用非常简单。首先,我们需要包含头文件<functional>。然后,我们可以使用std::function模板类来定义一个function对象。例如:

#include <functional>

std::function<int(int)> func;

上述代码定义了一个名为func的function对象,它可以接受一个int类型的参数,并返回一个int类型的值。我们可以将各种可调用对象赋值给func,例如函数指针、函数对象、lambda表达式等。例如:

int myFunction(int x) {
    return x * 2;
}

struct MyFunctor {
    int operator()(int x) {
        return x + 3;
    }
};

func = myFunction; // 函数指针
int result1 = func(2); // 调用myFunction,结果为4

MyFunctor functor;
func = functor; // 函数对象
int result2 = func(2); // 调用functor,结果为5

func = [](int x) { return x * x; }; // lambda表达式
int result3 = func(2); // 调用lambda表达式,结果为4

通过使用function包装器,我们可以将不同类型的可调用对象统一存储并调用,极大地提高了代码的灵活性和可复用性。此外,function还提供了其他功能,如判空、交换等。


需要注意的是,function包装器的使用可能会引入一些性能开销,因为它需要进行动态分派。在性能要求较高的场景中,可以考虑使用函数指针或模板来代替function包装器。

3. 逆波兰表达式求值

接下来我们来讲一道题,相信通过这个OJ题目可以让大家更加了解function包装器。首先我先简单介绍一下这个题目:逆波兰表示法(Reverse Polish Notation, RPN)是一种数学表达式的书写方式,它通过将操作符放在两个操作数之后来表示一个算术表达式,从而避免了使用括号。

接下来我们来讲一道题,相信通过这个OJ题目可以让大家更加了解function包装器。首先我先简单介绍一下这个题目:逆波兰表示法(Reverse Polish Notation, RPN)是一种数学表达式的书写方式,它通过将操作符放在两个操作数之后来表示一个算术表达式,从而避免了使用括号。

(1)普通写法

  • 如果当前元素是操作符(‘+’、‘-’、‘*’ 或 ‘/’):
  • 从栈中弹出两个操作数,分别记为 num2 和 num1。
  • 根据当前操作符进行计算,并将结果压入栈中。
  • 如果当前元素是操作数(整数):
  • 将其转换为整数并压入栈中。
  • 遍历结束后,栈中只会剩下一个元素,即为最终的计算结果。
class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<long long> st; // 创建一个存储操作数的栈,使用 long long 类型以避免溢出问题
        for(auto& str : tokens) // 遍历表达式中的每个元素
        {
            if(str == "+" || str == "-" || str == "*" || str == "/") // 当前元素为操作符
            {
                long long right = st.top(); // 取出栈顶的操作数作为右操作数
                st.pop(); // 弹出栈顶元素
                long long left = st.top(); // 取出新的栈顶元素作为左操作数
                st.pop(); // 弹出栈顶元素

                // 根据操作符进行计算,并将结果压入栈中
                switch(str[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(stoll(str)); // 将字符串转换为 long long 类型并压入栈中
            }
        }
        return st.top(); // 返回栈顶元素,即最终的计算结果
    }
};

(2)使用包装器以后的写法

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> st; // 创建一个存储操作数的栈
        map<string, function<int(int, int)>> opFuncMap = // 创建一个从操作符到对应计算函数的映射表
        {
            { "+", [](int i, int j){return i + j; } }, // lambda 函数实现加法计算
            { "-", [](int i, int j){return i - j; } }, // lambda 函数实现减法计算
            { "*", [](int i, int j){return i * j; } }, // lambda 函数实现乘法计算
            { "/", [](int i, int j){return i / j; } }  // lambda 函数实现除法计算
        };

        for(auto& str : tokens) // 遍历表达式中的每个元素
        {
            if(opFuncMap.find(str) != opFuncMap.end()) // 当前元素为操作符
            {
                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(); // 返回栈顶元素,即最终的计算结果
    }
};

二、bind() 函数

std::bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。一般而言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M可以大于N,

  1. 头文件
    bind() 函数位于 <functional> 头文件中,因此在使用该函数之前需要包含该头文件。
  2. 函数原型
    bind() 函数的原型如下所示:
template <class Fn, class... Args>
bind(Fn&& fn, Args&&... args);
  1. 参数解释
  • Fn:表示函数类型或函数对象类型。
  • Args:表示参数类型。
  • fn:要进行绑定的函数或函数对象。
  • args:要绑定的参数。
  1. 返回值
    bind() 函数返回一个新的可调用对象,该对象可以像原始函数一样被调用,但会自动传递已绑定的参数给 fn
  2. 使用示例
    下面是一个使用 bind() 函数的示例代码:
#include <functional>
#include <iostream>
using namespace std;

// 定义一个普通函数 Plus,接收两个 int 类型参数,返回它们的和
int Plus(int a, int b)
{
    return a + b;
}

// 定义一个类 Sub,包含一个成员函数 sub,接收两个 int 类型参数,返回它们的差
class Sub
{
public:
    int sub(int a, int b)
    {
        return a - b;
    }
};

int main()
{
    // 绑定全局函数 Plus 到一个 std::function 对象 func1 上,并使用 placeholders 占位符表示待绑定的参数。
    // 将第一个和第二个参数分别绑定到调用 func1 时传递的第一个和第二个参数上。
    std::function<int(int, int)> func1 = std::bind(Plus, placeholders::_1, placeholders::_2);

    // 使用 auto 定义变量 func2,将全局函数 Plus 绑定到它上面,并将第一个参数和第二个参数分别指定为 1 和 2。
    // 因为 Plus 函数已经被绑定,这里不需要再使用 placeholders 占位符了。
    auto func2 = std::bind(Plus, 1, 2);

    // 创建一个 Sub 对象 s,将它的成员函数 sub 绑定到 std::function 对象 func3 上。
    // 使用 placeholders 占位符表示待绑定的参数。将第一个和第二个参数分别绑定到调用 func3 时传递的第一个和第二个参数上。
    Sub s;
    std::function<int(int, int)> func3 = std::bind(&Sub::sub, s, placeholders::_1, placeholders::_2);

    // 创建另一个 std::function 对象 func4,将它绑定到 s 的成员函数 sub 上。
    // 使用 placeholders 占位符表示待绑定的参数。这里将第一个参数和第二个参数分别绑定到调用 func4 时传递的第二个和第一个参数上。
    std::function<int(int, int)> func4 = std::bind(&Sub::sub, s, placeholders::_2, placeholders::_1);

    // 分别输出各个函数对象的返回值
    cout << func1(1, 2) << endl;  // 输出 3,即 Plus(1, 2) 的结果
    cout << func2() << endl;      // 输出 3,即 Plus(1, 2) 的结果
    cout << func3(1, 2) << endl;  // 输出 -1,即 s.sub(1, 2) 的结果
    cout << func4(1, 2) << endl;  // 输出 1,即 s.sub(2, 1) 的结果

    return 0;
}

这段代码演示了如何使用 std::bind 函数将函数对象和成员函数绑定到 std::function 对象上,并使用占位符 std::placeholders::_1 和 std::placeholders::_2 来表示待绑定的参数。


  1. 我们定义了一个普通的函数 Plus,它接收两个 int 类型的参数并返回它们的和。
  2. 我们定义了一个类 Sub,其中包含一个成员函数 sub,它接收两个 int 类型的参数并返回它们的差。

在 main 函数中,我们首先用 std::bind 将全局函数 Plus 绑定到 std::function 对象 func1 上,使用占位符 std::placeholders::_1 和 std::placeholders::_2 分别表示第一个和第二个参数。这样,我们可以使用 func1 来调用 Plus 函数,并传递实际的参数。

我们使用 auto 关键字定义了一个变量 func2,将全局函数 Plus 绑定到它上面,并指定第一个参数为 1,第二个参数为 2。因为在这种情况下不需要使用占位符,所以我们直接指定了参数的值。

我们创建了一个 Sub 类的对象 s,并将其成员函数 sub 绑定到 std::function 对象 func3 上,使用占位符 std::placeholders::_1 和 std::placeholders::_2 分别表示第一个和第二个参数。这样,我们可以通过 func3 调用 s.sub 函数,并传递实际的参数。

我们创建了另一个 std::function 对象 func4,将其绑定到 s 的成员函数 sub 上,并使用占位符 std::placeholders::_2 和 std::placeholders::_1 分别表示第一个和第二个参数。这样,我们可以通过 func4 调用 s.sub 函数,并以不同的顺序传递实际的参数。

温馨提示

感谢您对博主文章的关注与支持!另外,我计划在未来的更新中持续探讨与本文相关的内容,会为您带来更多关于C++以及编程技术问题的深入解析、应用案例和趣味玩法等。请继续关注博主的更新,不要错过任何精彩内容!再次感谢您的支持和关注。期待与您建立更紧密的互动,共同探索C++、算法和编程的奥秘。祝您生活愉快,排便顺畅!

目录
相关文章
|
7月前
|
人工智能 Python
083_类_对象_成员方法_method_函数_function_isinstance
本内容主要讲解Python中的数据类型与面向对象基础。回顾了变量类型(如字符串`str`和整型`int`)及其相互转换,探讨了加法在不同类型中的表现。通过超市商品分类比喻,引出“类型”概念,并深入解析类(class)与对象(object)的关系,例如具体橘子是橘子类的实例。还介绍了`isinstance`函数判断类型、`type`与`help`探索类型属性,以及`str`和`int`的不同方法。最终总结类是抽象类型,对象是其实例,不同类型的对象有独特运算和方法,为后续学习埋下伏笔。
147 7
083_类_对象_成员方法_method_函数_function_isinstance
|
7月前
|
Python
[oeasy]python086方法_method_函数_function_区别
本文详细解析了Python中方法(method)与函数(function)的区别。通过回顾列表操作如`append`,以及随机模块的使用,介绍了方法作为类的成员需要通过实例调用的特点。对比内建函数如`print`和`input`,它们无需对象即可直接调用。总结指出方法需基于对象调用且包含`self`参数,而函数独立存在无需`self`。最后提供了学习资源链接,方便进一步探索。
169 17
|
5月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
146 0
|
7月前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
277 12
|
7月前
|
人工智能 Python
[oeasy]python083_类_对象_成员方法_method_函数_function_isinstance
本文介绍了Python中类、对象、成员方法及函数的概念。通过超市商品分类的例子,形象地解释了“类型”的概念,如整型(int)和字符串(str)是两种不同的数据类型。整型对象支持数字求和,字符串对象支持拼接。使用`isinstance`函数可以判断对象是否属于特定类型,例如判断变量是否为整型。此外,还探讨了面向对象编程(OOP)与面向过程编程的区别,并简要介绍了`type`和`help`函数的用法。最后总结指出,不同类型的对象有不同的运算和方法,如字符串有`find`和`index`方法,而整型没有。更多内容可参考文末提供的蓝桥、GitHub和Gitee链接。
179 11
|
8月前
|
安全 C++
【c++】继承(继承的定义格式、赋值兼容转换、多继承、派生类默认成员函数规则、继承与友元、继承与静态成员)
本文深入探讨了C++中的继承机制,作为面向对象编程(OOP)的核心特性之一。继承通过允许派生类扩展基类的属性和方法,极大促进了代码复用,增强了代码的可维护性和可扩展性。文章详细介绍了继承的基本概念、定义格式、继承方式(public、protected、private)、赋值兼容转换、作用域问题、默认成员函数规则、继承与友元、静态成员、多继承及菱形继承问题,并对比了继承与组合的优缺点。最后总结指出,虽然继承提高了代码灵活性和复用率,但也带来了耦合度高的问题,建议在“has-a”和“is-a”关系同时存在时优先使用组合。
445 6
|
9月前
|
存储 机器学习/深度学习 编译器
【C++终极篇】C++11:编程新纪元的神秘力量揭秘
【C++终极篇】C++11:编程新纪元的神秘力量揭秘
|
9月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
5月前
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
227 0

热门文章

最新文章