当前位置: 首页 > news >正文

function包装器的意义

一:function包装器的概念

function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。

二:需要function包装器的场景

那么我们来看看,我们为什么需要function呢?
一个需要包装器的场景:
// 包装器
// 函数模板
template<class F, class T>
T useF(F f, T x)
{static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x);
}//一个普通函数
double f(double i)
{return i / 2;
}//一个仿函数
struct Functor
{double operator()(double d){return d / 3;}
};int main()
{// 函数名cout << useF(f, 11.11) << endl;// 函数对象cout << useF(Functor(), 11.11) << endl;// lamber表达式cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;return 0;
}

运行结果:

从这个运行结果,要看出两个点:

①:因为实例化出了三份不同的类型的类,所以conut进行++是互不影响的

②:因为实例化出了三份不同的类型的类,所以count的地址是不同的

Q:为什么这段代码需要包装器 function

A:这段代码演示了 C++ 中不同类型的可调用对象(函数指针、函数对象、Lambda)在模板实例化时实例化出不同类型的行为(模板 useF 会生成多个实例)

Q:那如果我们可能希望 count 共享,也就是说希望 useF 无论传入什么可调用对象,都共享同一个 count,那怎么办呢?

A:这就需要用到function包装器了!

三:function包装器的作用

1:function包装器的书写格式

看图不好理解,直接上例子吧:

比如我们对上面的f函数进行包装,就写成这样就行:

double f(double i)
{return i / 2;
}#include<functional>int main()
{//对f函数进行封装function<double(double)> fc1 = f;return 0;
}

语法解析:

(1) 头文件

#include<functional> // 必须包含此头文件

(2) 声明格式

std::function<返回值类型(参数类型1, 参数类型2, ...)> 变量名;

返回值类型:被包装的可调用对象的返回类型

参数类型:被包装的可调用对象的参数类型(可以有多个)

所以我们对f函数进行包装,才写作如下:

function<double(double)> fc1 = f;

2:对场景进行包装

#include<iostream>
using namespace std;// 包装器
// 函数模板
template<class F, class T>
T useF(F f, T x)
{static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x);
}double f(double i)
{return i / 2;
}struct Functor
{double operator()(double d){return d / 3;}
};#include<functional>int main()
{// 函数指针function<double(double)> fc1 = f;/*fc1(11.11);*/cout << useF(fc1, 11.11) << endl;// 函数对象function<double(double)> fc2 = Functor();//fc2(11.11);cout << useF(fc2, 11.11) << endl;// lambda表达式function<double(double)> fc3 = [](double d)->double { return d / 4; };//fc3(11.11);cout << useF(fc3, 11.11) << endl;return 0;
}

运行结果:

从这个运行结果,要看出两个点:

①:因为实例化出的是一份公共的类,所以conut进行++是互相影响的

②:因为实例化出的是一份公共的类,所以count的地址是相同的

四:用包装器解决逆波兰表达式求值

题目介绍及以前的解法:逆波兰表达式求值-CSDN博客

1:思路对比

a:原本思路:

遇到操作数时,压入栈中;遇到运算符时,弹出栈顶的两个操作数进行运算,计算得到的结果再压入栈;最终栈顶元素即为结果。

b:包装器思路:

map来 创建了一个从字符串(操作符)到对应操作函数的映射。这使得我们可以通过操作符字符串直接查找并调用相应的运算函数。

且map的v值类型是function<int(int,int)>,其用于包装接受两个整数参数并返回一个整数的操作函数;对应的操作函数呢,我们采取lambda表达式,显得简洁;

如图:

map<string, function<int(int,int)>> opFuncMap = {{"+", [](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;}}};

Q:对应的操作函数一定要用lambda表达式吗?

A:不一定,但是lambda一定是最简洁的,以下展示了v值对应的操作函数用其他方法的冗余效果

a:使用普通函数

int add(int x, int y) { return x + y; }
int sub(int x, int y) { return x - y; }
int mul(int x, int y) { return x * y; }
int divi(int x, int y) { return x / y; }map<string, function<int(int,int)>> opFuncMap = {{"+", add},{"-", sub},{"*", mul},{"/", divi}
};

b:使用仿函数

struct Add {int operator()(int x, int y) { return x + y; }
};
struct Sub {int operator()(int x, int y) { return x - y; }
};
// 同理定义Mul和Divmap<string, function<int(int,int)>> opFuncMap = {{"+", Add()},{"-", Sub()},{"*", Mul()},{"/", Div()}
};

解释:

可以看出,不管是普通函数还是仿函数,都需要额外定义函数或类,并不优秀

lambda直接在初始化映射表时完成定义,非常优秀

2:代码

class Solution {
public:int evalRPN(vector<string>& tokens) {// 定义操作符到函数的映射map<string, function<int(int,int)>> opFuncMap = {{"+", [](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;}}};stack<int> st; // 用于存储操作数的栈for(auto& str : tokens) {if(opFuncMap.count(str)) { // 如果是操作符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();}
};

3:代码解读

相比于老方法,我们的设计显得非常的细腻;而不是以前直接暴力判断字符是否为操作字符。

值得再说一句的是,map定义时候:

string是一个类型,而function<int(int,int)>也是一个类型;

string接收"+"这种字符串,而function<int(int,int)>接收[](int x, int y){return x + y;}这样一个lambda

五:包装器的几种用法的格式

1:普通函数的包装

int f(int a, int b) {return a + b;
}// 包装普通函数
function<int(int, int)> fc1 = f;

特点

  • 直接使用函数名 f 赋值给 function

  • 调用方式与原函数完全相同

  • 最简单直接的包装方式

2:静态成员函数的包装

class Plus {
public:static int plusi(int a, int b) {return a + b;}
};// 包装静态成员函数
function<int(int, int)> fc2 = &Plus::plusi;

特点

  • 需要使用 &类名::函数名 的语法获取函数指针(&一定得要,语法规定)

  • 调用方式与普通函数相同

3:非静态成员函数的包装

静态成员函数代码:

class Plus
{
public:double plusd(double a, double b){return a + b;}
};//对其中的plusd函数进行包装

方式一:使用对象值

使用对象值去包装非静态函数的两种写法:写法1:
function<double(Plus, double, double)> fc3 = &Plus::plusd;
//使用的时候,第一个参数匿名构造一个对象
cout << fc3(Plus(), 1, 1) << endl;或者写法2:
Plus plus;
function<double(Plus, double, double)> fc4 = &Plus::plusd;
cout << fc4(plus, 1, 1) << endl;

特点

  • 包装器的第一个参数的类型是类类型(类这个类型),所以是Plus;因为成员函数的第一个参数是this 指针,所以要用类类型Plus去对应

  • 调用时,第一个参数要么是之前实例化的对象(如plus);要么就是匿名构造一个对象(Plus())

方式二:使用对象指针

    //一定要实例化Plus plus;function<double(Plus*, double, double)> fc5 = &Plus::plusd;//第一个参数是堆对象取地址cout << fc5(&plus, 1, 1) << endl;

特点

  • function第一个参数的类型必须是类指针 Plus*

  • 一定需要先创建对象实例 plus

  • 调用时,第一个参数需要传入对象地址 &plus

六:bind绑定对于包装器的优化

bind叫作绑定,有两个作用:

①:更改参数的顺序(不重要)

②:更改参数的个数(重要)

1:更改参数的顺序

int Sub(int a, int b)
{return a - b;
}int main()
{int x = 10, y = 20;cout << Sub(x, y) << endl;  // 正常调用,输出 -10auto f1 = bind(Sub, placeholders::_2, placeholders::_1);//用bind更改参数的顺序后cout << f1(x, y) << endl;   // 输出 10return 0;
}

运行结果:

解释:

  • placeholders是标准库定义的命名空间,位于 <functional> 头文件中。

  • bind(Sub, placeholders::_2, placeholders::_1) 将第二个参数作为第一个参数,第一个参数作为第二个参数()

  • 原本 Sub(10,20) 是 10-20=-10

  • 调整后变为 Sub(20,10) 是 20-10=10

2:更改参数的个数

我们之前五中的例子,对于非静态成员函数的包装后,使用的时候,第一个参数需要传我们不想手动传的东西,如:

cout << fc3(Plus(), 1, 1) << endl;cout << fc4(plus, 1, 1) << endl;cout << fc5(&plus, 1, 1) << endl;

那我们现在就可以用bind,去更改参数的个数,让我们传的时候只传后面的1,1,如下

cout << fc3(1, 1) << endl;
cout << fc4(1, 1) << endl;
cout << fc5(1, 1) << endl;

更改参数个数的代码:

class Plus
{
public:double plusd(double a, double b){return a + b;}
};int main()
{//1:使用对象值绑定 (plus)//原先调用的第一个参数是plus  被省略了Plus plus;function<double(double, double)> fc4 = bind(&Plus::plusd, plus, placeholders::_1, placeholders::_2);cout << fc4(1, 1) << endl;//2:使用临时对象值绑定 (Plus())//原先调用的第一个参数是Plus()  被省略了function<double(double, double)> fc3 = bind(&Plus::plusd, Plus(), placeholders::_1, placeholders::_2);cout << fc3(1, 1) << endl;//3:使用对象指针绑定 (&plus)//原先调用的第一个参数是&plus  被省略了function<double(double, double)> fc5 = bind(&Plus::plusd, &plus, placeholders::_1, placeholders::_2);cout << fc5(1, 1) << endl;return 0;
}

运行结果:

相关文章:

  • 解决 Builroot 系统编译 perl 编译报错问题
  • 正态分布习题集 · 答案与解析篇
  • 过采样处理
  • P3469 [POI 2008] BLO-Blockade
  • 【PyTorch完全指南】从深度学习原理到工业级实践
  • 5个重要的财务指标讲解
  • 22:一维码与二维码区别
  • Android之Button、ImageButton、ChipGroup用法
  • Spring AI 实战:第十章、Spring AI RAG之博学多才
  • PiscTrace针对YOLO深度适配:从v8到v12
  • Spring MVC @CookieValue 注解怎么用?
  • 每日算法-250504
  • 即梦AI视频3.0模型提示词创作设定
  • 【C++重载操作符与转换】下标操作符
  • n8n工作流自动化平台的实操:生成统计图的两种方式
  • QT数据库实验
  • AVL树(2):
  • 性能优化实践:渲染性能优化
  • Python|Pyppeteer实现自动登录小红书(32)
  • 蓝桥杯15届国赛 合法密码
  • 谢承祥已任自然资源部总工程师
  • “五一”假期第四天,全社会跨区域人员流动量预计超2.7亿人次
  • 长三角铁路今日预计发送390万人次,昨日客发量同比增长10.5%
  • 首次面向上海、江苏招收本科生,西湖大学接连发布招生简章
  • 韩代总统李周浩履职
  • 武汉大学新闻与传播学院已由“80后”副院长吴世文主持工作