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

在这里插入图片描述

阅读导航

  • 引言
  • 一、function包装器
    • 1. 概念
    • 2. 基本使用
    • 3. 逆波兰表达式求值
      • (1)普通写法
      • (2)使用包装器以后的写法
  • 二、bind() 函数
  • 温馨提示

引言

很高兴再次与大家分享关于 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)是一种数学表达式的书写方式,它通过将操作符放在两个操作数之后来表示一个算术表达式,从而避免了使用括号。

🔴题目链接

在这里插入图片描述

(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,但这么做没什么意义)参数的新函数。同时,使用std::bind函数还可以实现参数顺序调整等操作。

  1. 头文件
    bind() 函数位于 <functional> 头文件中,因此在使用该函数之前需要包含该头文件。

  2. 函数原型
    bind() 函数的原型如下所示:

    template <class Fn, class... Args>
    bind(Fn&& fn, Args&&... args);
    
  3. 参数解释

    • Fn:表示函数类型或函数对象类型。
    • Args:表示参数类型。
    • fn:要进行绑定的函数或函数对象。
    • args:要绑定的参数。
  4. 返回值
    bind() 函数返回一个新的可调用对象,该对象可以像原始函数一样被调用,但会自动传递已绑定的参数给 fn

  5. 使用示例
    下面是一个使用 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::_1std::placeholders::_2 来表示待绑定的参数。

  1. 我们定义了一个普通的函数 Plus,它接收两个 int 类型的参数并返回它们的和。
  2. 我们定义了一个类 Sub,其中包含一个成员函数 sub,它接收两个 int 类型的参数并返回它们的差。
    • main 函数中,我们首先用 std::bind 将全局函数 Plus 绑定到 std::function 对象 func1 上,使用占位符 std::placeholders::_1std::placeholders::_2 分别表示第一个和第二个参数。这样,我们可以使用 func1 来调用 Plus 函数,并传递实际的参数。
  3. 我们使用 auto 关键字定义了一个变量 func2,将全局函数 Plus 绑定到它上面,并指定第一个参数为 1,第二个参数为 2。因为在这种情况下不需要使用占位符,所以我们直接指定了参数的值。
  4. 我们创建了一个 Sub 类的对象 s,并将其成员函数 sub 绑定到 std::function 对象 func3 上,使用占位符 std::placeholders::_1std::placeholders::_2 分别表示第一个和第二个参数。这样,我们可以通过 func3 调用 s.sub 函数,并传递实际的参数。
  5. 我们创建了另一个 std::function 对象 func4,将其绑定到 s 的成员函数 sub 上,并使用占位符 std::placeholders::_2std::placeholders::_1 分别表示第一个和第二个参数。这样,我们可以通过 func4 调用 s.sub 函数,并以不同的顺序传递实际的参数。

温馨提示

感谢您对博主文章的关注与支持!另外,我计划在未来的更新中持续探讨与本文相关的内容,会为您带来更多关于C++以及编程技术问题的深入解析、应用案例和趣味玩法等。请继续关注博主的更新,不要错过任何精彩内容!

再次感谢您的支持和关注。期待与您建立更紧密的互动,共同探索C++、算法和编程的奥秘。祝您生活愉快,排便顺畅!
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/289761.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vue前端文字效果:如何让一段文本像是手动一个一个字打出来的

效果展示 自己做的AI聊天机器人界面&#xff0c;我觉得比微信还好看 由于这个前端略微复杂&#xff0c;下文用最简单的例子来展示&#xff1a; 分析需求 对于AI聊天工具的前端&#xff0c;如果AI生成的文本像是一个一个字打出来的&#xff0c;就会让AI看起来更像真的人&…

打造炫酷粒子效果的前端利器tsParticles

前端潮流速递 &#xff1a;打造炫酷粒子效果的前端利器tsParticles 在现代前端开发中&#xff0c;动画和视觉效果是吸引用户的关键元素之一。而实现炫酷而引人入胜的粒子效果&#xff0c;常常需要耗费大量的时间和精力。然而&#xff0c;有了 tsParticles&#xff0c;这一切变…

MySQL 8.0 开关 Redo Logging

一 前言 前几天有客户测试使用云数据库的时候提出 要禁止mydumper 关闭redo log的操作 (说白了就是导入数据时保持MySQL 实例的redo logging功能)&#xff0c; 这才想起 在 MySQL 8.0.21 版本中&#xff0c;开启了一个新特性 “Redo Logging 动态开关”。 在新实例导数据的场…

搭建宠物寄养小程序流程

近日&#xff0c;一地宠物寄养需求旺盛&#xff0c;元旦满房&#xff0c;春节几近饱和&#xff0c;一窝难求。随着市场需求的增长&#xff0c;对于很多宠物行业的商家&#xff0c;可以考虑开展宠物寄养服务&#xff0c;尤其是节假日的宠物寄养需求会更高。因此&#xff0c;商家…

FastApi-快速入门1

FastAPI 是一个用于构建 API 的现代、快速&#xff08;高性能&#xff09;的 web 框架&#xff0c;使用 Python 3.8 并基于标准的 Python 类型提示。 关键特性: 快速&#xff1a;可与 NodeJS 和 Go 并肩的极高性能&#xff08;归功于 Starlette 和 Pydantic&#xff09;。最快…

算法通关村番外篇-数组实现队列

大家好我是苏麟 , 今天来用数组实现一下队列 . 数组实现队列 顺序存储结构存储的队列称为顺序队列&#xff0c;内部使用一个一维数组存储&#xff0c;用一个队头指针 front 指向队列头部节点(即使用int类型front来表示队头元素的下标)&#xff0c;用一个队尾指针rear(有的地方…

3dmax灯光缓存参数应该怎么设置?

细分&#xff1a;用来决定灯光缓存的样本数量&#xff0c;样本数量以此数值的平方来计算。数值越高&#xff0c;效果越好&#xff0c;速度越慢。 一般出图建议1000到1800之间已经足够了 采样大小&#xff1a;用来控制灯光缓存的样本尺寸大小&#xff0c;较小的数值意味着较小的…

Vue 模板编译原理解析

Vue 模板编译原理解析 模板编译整体流程 首先我们看一下什么是编译&#xff1f; 所谓编译&#xff08;Compile&#xff09;&#xff0c;指的是将语言 A 翻译成语言 B&#xff0c;语言 A 就被称之为源码&#xff08;source code&#xff09;&#xff0c;语言 B 就被称之为目标…

清风数学建模笔记-主成分分析

内容&#xff1a;主成分分析 介绍&#xff1a; 主成分分析是一种降维算法&#xff0c;它通过旋转和变换将多个指标转化为少数几个主成分&#xff0c;这些主成分是原变量的线性组合&#xff0c;且互不相关&#xff0c;其能反映出原始数据的大部分信息。 例如解决多重共线性问题…

Vue+ElementUI笔记(1)

一、表格 1.上移、下移和移除功能 需求&#xff1a;有时我们会面对类似这样的表格 图中的上移&#xff0c;下移功能需求明显要求我们改变两行数据的顺序。在实际开发中这种功能一般由后台来做&#xff0c;因为列表数据一般从后台获取刷新。即是我们点击”上移“&#xff0c;向…

K8Spod组件

一个pod能包含几个容器 一个pause容器(基础容器/父容器/根容器&#xff09; 一个或者多个应用容器(业务容器) 通常一个Pod最好只包含一个应用容器&#xff0c;一个应用容器最好也只运行一个业务进程。 同一个Pod里的容器都是运行在同一个node节点上的&#xff0c;并且共享 net、…

20、Finetuning

微调是指调整大型语言模型&#xff08;LLM&#xff09;的参数以适应特定任务的过程&#xff0c;用于改进预训练模型的性能。这是通过在与任务相关的数据集上训练模型来完成的。所需的微调量取决于任务的复杂性和数据集的大小。 PEFT&#xff08;Parameter-Efficient Fine-Tunin…

前端发开的性能优化 请求级:请求前(资源预加载和预读取)

预加载 预加载&#xff1a;是优化网页性能的重要技术&#xff0c;其目的就是在页面加载过程中先提前请求和获取相关的资源信息&#xff0c;减少用户的等待时间&#xff0c;提高用户的体验性。预加载的操作可以尝试去解决一些类似于减少首次内容渲染的时间&#xff0c;提升关键资…

逻辑回归(LR)----机器学习

基本原理 逻辑回归&#xff08;Logistic Regression&#xff0c;LR&#xff09;也称为"对数几率回归"&#xff0c;又称为"逻辑斯谛"回归。 logistic回归又称logistic 回归分析 &#xff0c;是一种广义的线性回归分析模型&#xff0c;常用于数据挖掘&#…

基于Rangenet Lib的自动驾驶LiDAR点云语义分割与可视化

这段代码是一个C程序&#xff0c;用于处理来自KITTI数据集的激光雷达&#xff08;LiDAR&#xff09;扫描数据。程序主要实现以下功能&#xff1a; 1. **读取和解析命令行参数**&#xff1a;使用Boost库中的program_options模块来定义和解析命令行参数。这包括扫描文件路径、模型…

李沐机器学习系列2--- mlp

1 Introduction LP中有一个很强的假设&#xff0c;输入和输出是线性关系&#xff0c;这一般是不符合事实的。 通过几何的方式去对信息进行理解和压缩是比较高效的&#xff0c;MLP可以表示成下面的形式。 1.1 从线性到非线性 X ∈ R n d X \in R^{n \times d} X∈Rnd表示输入…

深信服技术认证“SCCA-C”划重点:云计算关键技术

为帮助大家更加系统化地学习云计算知识&#xff0c;高效通过云计算工程师认证&#xff0c;深信服特推出“SCCA-C认证备考秘笈”&#xff0c;共十期内容。“考试重点”内容框架&#xff0c;帮助大家快速get重点知识。 划重点来啦 *点击图片放大展示 深信服云计算认证&#xff08…

神经网络:经典模型热门模型

在这里插入代码片【一】目标检测中IOU的相关概念与计算 IoU&#xff08;Intersection over Union&#xff09;即交并比&#xff0c;是目标检测任务中一个重要的模块&#xff0c;其是GT bbox与pred bbox交集的面积 / 二者并集的面积。 下面我们用坐标&#xff08;top&#xff0…

电动汽车BMS PCB制板的技术分析与可制造性设计

随着电动汽车行业的迅猛发展&#xff0c;各大厂商纷纷投入巨资进行技术研发和创新。电动汽车的核心之一在于其电池管理系统&#xff08;Battery Management System, BMS&#xff09;&#xff0c;而BMS的心脏则是其印刷电路板&#xff08;PCB&#xff09;。通过这篇文章探讨电动…

Application layer

title: 应用层 date: 2023-12-20 21:03:48 tags: 知识总结 categories: 计算机网络 应用层&#xff1a;负责最直观的应用请求的封装、发起 一、域名系统DNS 连接在互联网上的主机不仅有IP地址&#xff0c;还有便于用户记忆的主机名字。域名系统DNS能够把互联网上的主机的名字…