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

C++进阶(7)——包装器

 

目录

包装器

function

💡基本介绍

💡function的统一性

💡function包装器的应用

💡总结一下:

bind

💡基本介绍

💡bing包装器的使用

💡bind包装器调整传入参数的顺序

💡总结一下


包装器

function

💡基本介绍

function是一个类模板,也是一个包装器。function的实例对象可以包装存储其他的可以调用对象,包括了函数指针、仿函数、lambda和bind表达式等,存储的可调用对象被称之为function的目标。

类模板的原型如下:

template <class T> 
class function; // undefined;template <class Ret, class ...Args>
class function<Ret(Args...)>;

参数说明:

Ret:被包装存储的可调用对象的返回值类型。

Args...:被包装存储的调用对象的形参类型。

我们这里还是来举出一些栗子:

#include <iostream>
#include <functional>
using namespace std;
int f(int a, int b) {return a + b;
}struct Functor {public:int operator() (int a, int b) {return a + b;}
};class Plus {public:Plus(int n = 10) : _n(n) {}static int plusi(int a, int b) {return a + b;}double plusd(double a, double b) {return (a + b) * _n;}private:int _n;
};int main() {// 包装可调用对象function<int(int, int)> f1 = f;function<int(int, int)> f2 = Functor();function<int(int, int)> f3 = [](int a, int b) {return a + b;};cout << f1(1, 1) << endl;cout << f2(1, 1) << endl;cout << f3(1, 1) << endl;// 包装静态成员函数// 这里需要指定类域并且在前面加上&才能获取地址function<int(int, int)> f4 = &Plus::plusi;// function<int(int, int)> f4 = Plus::plusi; // 这里也可以没有&cout << f4(1, 1) << endl;// 包装普通成员函数// 普通成员函数还有一个隐含的this指针,所以绑定的时候传入对象或是对象指针都是可以的function<double(Plus*, double, double)> f5 = &Plus::plusd;Plus pd;cout << f5(&pd, 1.1, 1.1) << endl;function<double(Plus, double, double)> f6 = &Plus::plusd;cout << f6(pd, 1.1, 1.1) << endl;function<double(Plus&&, double, double)> f7 = &Plus::plusd;cout << f7(move(pd), 1.1, 1.1) << endl;cout << f7(Plus(), 1.1, 1.1) << endl;return 0;
}

测试效果:

敲黑板:

我们这里取静态成员函数的地址可以不用取地址运算符“&”,但是我们取非静态成员函数的地址就必须要使用取地址运算符了。

我们这里包装非静态成员函数的时候,要注意还有一个隐含的this指针参数,这里我们要指明第一个参数的类型,可以值对象指针,也可以是对象。

💡function的统一性

我们这里可以将我们的function包装器和我们的函数模板来进行一个对比,首先我们要有一个函数模板,这个函数模板的要求如下:

1、传入这个函数的第一个参数可以是任意的可以调用的对象,就是我们上面所提到的函数指针、仿函数、lambda表达式等。

2、这个函数模板中需要定义一个静态变量,然后每次打印这个静态变量的地址,看看是不是调用的同一个函数。

函数模板:

template<class F, class T>
T func(F f, T t) {static int temp = 1;cout << "&temp: " << &temp << endl;return f(t);
}

测试代码:

int f(int x) {return x * 2;
}struct Functor {int operator()(int x) {return x * 3;}
};int main() {// 函数指针cout << func(f, 2) << endl;cout << "****************" << endl;// 仿函数cout << func(Functor(), 2) << endl;cout << "****************" << endl;// lambda表达式cout << func([](int x){return x * 4;}, 2) << endl;return 0;
}

测试效果:

我们这里可以看到我们的三种方式调用的都不是统一个函数,也是就说我们的func函数实际上是被实例化出来的三份,但是我们直到这里根本没有必要实例化出来三份,因为我们这里的三个函数的返回值和参数类型都是相同的,这里我们就可以使用包装器对着三个可调用对象进行包装了。

示例代码:

int main() {// 函数指针function<int(int)> f1 = f;cout << func(f1, 2) << endl;cout << "****************" << endl;// 函数对象function<int(int)> f2 = Functor();cout << func(f2, 2) << endl;cout << "****************" << endl;// lambda表达式function<int(int)> f3 = [](int x) {return x * 4;};cout << func(f3, 2) << endl;cout << "****************" << endl;return 0;
}

测试效果:

我们这里可以看到我们调用的是同一个函数。

💡function包装器的应用

我们这里给出的示例是一道力扣上面的题——逆波兰表达式求值

这个题目主要考察的是对于栈的运用,我们这里直接给出实现思路:

1、定义栈,然后遍历字符床

2、遍历的过程中遇到了数字字符直接入栈,遍历到了运算符,就从栈中取出两个数字进行运算,然后将我们计算的结果返回栈中。

3、循环上面的步骤直到我们遍历结束,栈顶就是结果。

代码如下:

class Solution {
public:int evalRPN(vector<string>& tokens) {stack<int> st;for (auto& str : tokens) {if (str == "+" || str == "-" || str == "*" || str == "/") {int right = st.top();st.pop();int 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(stoi(str));}}return st.top();}
};

我们这里的实现比较的臃肿,使用的是switch_case语句来进行的判断和计算,我们这一节学习了包装器,这样的情况是可以用我们的包装器来简化代码的,我们可以将运算符和我们的计算函数之间建立映射关系,然后直接需要的时候调用即可。

示例代码:

class Solution {
public:int evalRPN(vector<string>& tokens) {stack<int> st;// function作为map的映射可调用对象的类型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; }}};for (auto& str : tokens) {if (opFuncMap.count(str)) { // 操作符int right = st.top();st.pop();int left = st.top();st.pop();int ret = opFuncMap[str](left, right);st.push(ret);} else {st.push(stoi(str));}}return st.top();}
};

我们这里使用哈希表映射了对应的函数,着不仅仅可以使代码更加简洁,还可以使代码具有更高的灵活性。

💡总结一下:

1、function包装器可以统一管理我们的可调用对象。

2、functoin包装器提高了代码的灵活性。

bind

💡基本介绍

bind是一个函数模板,同时也是一个可调用对象的包装器,可以把它看作是一个函数的适配器,对接受的可调用对象处理后返回一个可调用对象,主要是用来调整参数个数和参数顺序的。

函数模板如下:

simple(1)
template<class Fn, class ...Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);with return type(2)
template<class Ret, class Fn, class... Args
/* unspecified */ bind (Fn&& fn, Args&&... args);

参数说明:

fn:可调用对象名。

args...:要绑定的参数列表。

调用bind的一般形式

auto newCallable = bind(callable, arg_list);

参数说明:

newCallable:生成的可调用对象。

callable:传入的需要包装的可调用对象。

arg_list:逗号分隔的参数列表,对应给定的callable的参数。

这里需要说明的是:我们的arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数实际上是占位符,表示newCallable的参数,它们占据了传递给newCallable的参数的位置。数值n表示的是生成的可调用对象中参数的位置:_1表示的就是我们newCallable的第一个参数,_2就是第二个参数,以此类推即可,这些占位符放在了placeholders的一个命名空间中。

💡bing包装器的使用

普通绑定

代码示例:

#include <functional>
#include <iostream>
using namespace std;
int Sub(int a, int b) {return (a - b) * 10;
}
int main() {auto f1 = bind(Sub, placeholders::_1, placeholders::_2);cout << f1(10, 5) << endl;return 0;
}

测试效果:

这里实现的也是第一个参数传入给placeholders::_1,第二个参数传入给placeholders::_2,并生成了新的可调用对象,所以我们这里的绑定和我们的直接调用可调用对象没什么区别。

绑定固定参数

示例代码:

#include <functional>
#include <iostream>
using namespace std;
int Sub(int a, int b) {return (a - b) * 10;
}
int main() {auto f2 = bind(Sub, placeholders::_1, 2);cout << f2(10) << endl;return 0;
}

测试效果:

我们这里讲我们的第二个参数固定为了10,所以此时我们传入的时候就只需要传入一个参数即可。

绑定成员函数

这个是我们实现一些项目的时候比较常用的,我们这里的第二个参数需要传入对象的示例(或是对象的指针,这里和上面的function相似)。

示例代码:

#include <iostream>
#include <functional>
using namespace std;
class Foo {
public:void print(int val) const { std::cout << "Foo value: " << val << std::endl; }
};int main() {Foo obj;function<void(int)> func_binder = bind(&Foo::print, &obj, placeholders::_1);// function<void(int)> func_binder = bind(&Foo::print, obj, placeholders::_1); // 也可以传入对象func_binder(42); return 0;
}

测试效果:

绑定lambda表达式

这里其实和上面的原理一样,就不再赘述了。

示例代码:

#include <iostream>
#include <functional>int main() {using namespace std::placeholders;auto lambda_add = [](int a, int b) { return a + b; };auto lambda_reorder = std::bind(lambda_add, _2, _1);std::cout << "Reordered sum: " << lambda_reorder(10, 20) << std::endl; return 0;
}

测试效果:

💡bind包装器调整传入参数的顺序

我们这里还是给出一个栗子来理解这个过程,就比如下面的代码:

示例代码:

#include <iostream>
#include <functional>
using namespace std;
class Add {public:int add(int x, int y) {return x * 10 + y;}
};int main() {function<int(int, int)> f = bind(&Add::add, Add(), placeholders::_1, placeholders::_2);cout << f(1, 2) << endl;return 0;
}

测试效果:

这个时候我们就可以来对这里的的传参顺序进行一个调整了。

示例代码:

#include <iostream>
#include <functional>
using namespace std;
class Add {public:int add(int x, int y) {return x * 10 + y;}
};int main() {function<int(int, int)> f = bind(&Add::add, Add(), placeholders::_2, placeholders::_1);cout << f(1, 2) << endl;return 0;
}

测试效果:

我们这里可以看到我们的参数确实是变换了顺序,但是这里要说明的是这个功能其实是不常用的,了解即可。

💡总结一下

1、可以将函数的一些参数固定下来,那样我们就不用传递那些参数了。

2、可以调整我们传入参数的顺序。

http://www.dtcms.com/a/446554.html

相关文章:

  • Redis应用场景(黑马点评快速复习)
  • 泉州建站模板搭建深圳工业设计有限公司
  • 外贸出口工艺品怎么做外贸网站想自学做网站
  • 【Docker项目实战】使用Docker部署Dokuwiki个人知识库
  • 建设实验中心网站c2c网站价格
  • arp broadcast enable 概念及题目
  • 在搜狐快站上做网站怎么跳转做商品网站需要营业执照
  • 为什么多智能体系统需要记忆工程
  • C++:string 类
  • [crackme]019-CrackMe3
  • 宠物寄养网站毕业设计营销网站建设专业团队在线服务
  • C++11学习笔记
  • 搜狐快速建站郴州市做网站
  • 在Linux中重定向,复制cp,硬链接的区别,Linux底层存储数据原理。
  • 软考~系统规划与管理师考试—知识篇—V2.0—考试科目2:系统规划与管理案例分析—题型分类—第七章 IT 服务持续改进—20192021
  • NopGraphQL 的设计创新:从 API 协议到通用信息操作引擎
  • 概率论:分布与检验
  • 网站后台视频教程视频号怎么付费推广
  • 浦江网站建设微信开发wordpress 浏览计数
  • 嵌入式开发学习日志35——stm32之超声波测距
  • 山东建设厅官方网站一级建造师搜索引擎平台
  • MATLAB计算有效干旱指数(Effective drought index, EDI)
  • 网站推广如何收费现在建一个网站一年费用只要几百元
  • 如何自己做游戏网站如何建设万网网站
  • 江苏省建设厅八大员考试报名网站石家庄有哪些公司可以做网站
  • 搭建Jenkins gitlab 环境
  • 企业做的网站费入什么科目江西网站备案
  • pink老师html5+css3day05
  • 哪里找人做网站织梦淘客网站
  • 网站开发原型法装个宽带多少钱