【c++】c++11新特性(function包装器,bind包装器)
小编个人主页详情<—请点击
小编个人gitee代码仓库<—请点击
c++系列专栏<—请点击
倘若命中无此运,孤身亦可登昆仑,送给屏幕面前的读者朋友们和小编自己!
目录
-
- 前言
- 一、function包装器
-
- 铺垫
- function
- 应用场景
- 二、bind包装器
-
- 概念
- 使用
-
- 普通函数
- 静态成员函数
- 成员函数
- 总结
前言
【c++】c++11新特性(lambda表达式,可变参数模板,emplace系列接口测试,新增的默认成员函数)——书接上文 详情请点击<——
本文由小编为大家介绍——【c++】c++11新特性(function包装器,bind包装器)
一、function包装器
function包装器也叫做适配器,c++中的function本质是类模板,也是一个包装器
铺垫
- 那么下面小编带领大家探索一下,为什么需要function
- 目前我们已知的可调用对象有三种,分别是函数指针(其实就是函数名,函数名的本质是函数指针,函数名可以进行调用,函数指针也可以进行调用,void(* ptr)(int a)),仿函数(重载了operator()的类,其对象可以像函数一样使用),lambda(匿名函数对象,可以直接定义使用)
- 下面代码中的函数模板useF其中的第一个模板参数接收的是一个可调用对象,有可能是函数指针,也有可能是仿函数,也有可能是lambda表达式
- 那么我们知道模板的参数不同,其实例化对象的类型也不同,当我们传不同的可调用对象的时候是否下面的函数模板useF也会实例化出三份呢?
- 验证如下,useF中有静态成员变量count,实例化不同的局部函数中会有独自的静态成员变量count,情况一:如果实例化了三份useF函数,那么会有三个count,count的值都为1,并且取出的count的地址也都不同,情况二:如果只实例化出了一份useF函数,那么只会有一个静态成员变量count,并且经过三次调用之后count的值会变为3,而且count的地址也都相同,那么下面我们就调用验证一下
#include 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;}};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;}
运行结果如下
- 结果为情况一,这里分别传入三份不同的调用对象,那么函数模板useF会接收到三份不同的参数,就会去实例化出了三份构成函数重载的useF
- 仅仅是传入的调用对象不同,其它都一样,却实例化出三份useF,那么会对效率会有一定程度的损失,可不可以将这个三个调用对象包装一下,让useF实例化出一份函数呢?
- 当然是可以的,这就会用到我们的包装器function
function
- function是包装器,其可以对可调用对象进行包装,可以对函数指针,仿函数,lambda进行包装,实际上解决的是可调用对象的类型问题,我们如果想要将可调用对象存储起来,那么对象函数指针,其类型的书写比较繁琐,对于仿函数,仿函数可以知道类型,对于lambda,由于lambda是一个匿名函数对象,我们虽然可以使用typeid().name将lambda的类型进行打印,但是只能看不能用,对于lambda的类型我们无法获取,而且每次编译lambda生成的类型都不同,例如vs19上就使用了uuid参与并去生成lambda的类型,只有编译器知道lambda的类型,我们无法获取并使用lambda的类型
- 有了function之后,就可以对可调用对象的类型进行统一的包装了,便于我们进行存储和调用
- 要使用function需要包头文件#include
template<class Ret, class ...Args>class function<Ret(Args...)>;//Ret:可调用对象的返回值//Args:可调用对象的形参
- 那么我们就可以使用function将可调用对象存储在容器中,再遍历进行传参对象实例化出一份函数,进行调用对象
#include #include #include 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;}};int main(){vector<function<double(double)>> v = { f, Functor(),[](double d)->double { return d / 4; } };for (const auto& fun : v){cout << useF(fun, 11.11) << endl << endl;}return 0;}
运行结果如下
- 那么此时传入函数模板useF的第一个参数就是一个包装器类型了,此时useF就只会实例化出一份了,count最终为3,并且三次count的地址都相同,此时就减少了模板实例化的消耗
- 如果可调用对象的形参或者返回值类型不同,那么使用function进行包装的时候可以使用一个类或者容器去存储可调用对象的类型,这里只需要了解即可,不需要深究
应用场景
- 小编拿一个题目来讲解function的应用场景,这个应用场景其实就是指令和对应的动作(函数调用)的场景
逆波兰表达式求值详情请点击<——
同时小编已经有关于这道题目的第一种解法的讲解,不了解的读者友友可以点击后方学习一下,小编会基于第一种解法的基础上进行讲解如何使用这里的functon求解题目详情请点击<——
class Solution {public: int evalRPN(vector<string>& tokens) { unordered_map<string, function<int(int, int)>> hash = { { \"+\", [](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; } } };//使用哈希表将操作符和lambda一一映射//这里适合使用lambda,其轻巧,编写简介,适用于语句较少的情况//这里去编写仿函数有点不太适合,太重了,去编写一个类实现没必要//函数指针即编写函数的话,一般语句较多才编写函数或仿函数 stack<int> st; for(const auto& str : tokens) { if(hash.count(str))//可以使用哈希表判断操作符是否存在,存在则取出运算 { int right = st.top(); st.pop(); int left = st.top(); st.pop(); st.push(hash[str](left, right));//使用[]取出lambda,传参调用 } //调用完成将结果入栈 else { st.push(stoi(str));//不是操作符,则是操作数,操作数则直接入栈 } } return st.top();//返回栈顶元素 }};
二、bind包装器
概念
bind是一个函数模板,它像是一个函数包装器(适配器),通过接收一个可调用对象,生成一个新的可调用对象去“适应”原对象的参数列表
一般而言,我们可以使用bind将一个接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个参数的新函数(M小于N),同时bind还可以实现函数的参数顺序调整的操作
template<class Fn, class... Args>bind(Fn&& fn, Args&&... args);
- bind包含在#include这个头文件中,使用bind时不要忘记包头文件
- 调用bind的一般形式:auto newcallable = bind(callable, arg_list);
- newcallable本身是一个可调用对象,arg_list是一个使用逗号分隔的参数列表,我们可以给定callable的参数。当我们调用newcallable的时候,newcallable会调用callable,并传给它arg_list中的参数
- arg_list会包含形如_n的标识符,以_1, _2, _3 …依次类推,_1对应newcallable这个可调用对象的第一个实参,_2对应newcallable的第二个实参,_3对应newcallable的第三个实参,以此类推
- 但是使用标识符的时候要去placeholders这个命名空间中去找,使用::去声明命名空间域才能找到_1, _2, _3 …这一类的标识符
使用
普通函数
- 我们先学习一下bind的参数的对应关系之后再做相应的调整参数的学习,下面小编并没有使用bind调整sub的传参顺序,而是一一对应,便于进行学习使用
int sub(int x, int y){return x - y;}int main(){auto newsub = bind(sub, placeholders::_1, placeholders::_2);cout << newsub(3, 6) << endl;return 0;}
运行结果如下
- 那么接下来小编就使用bind调整一下sub函数的传参顺序
int sub(int x, int y){return x - y;}int main(){auto newsub = bind(sub, placeholders::_2, placeholders::_1);cout << newsub(3, 6) << endl;return 0;}
运行结果如下,调整参数顺序成功
- 下面小编再讲解一下如何绑定参数,那么就是直接bind中在需要进行绑定参数的位置,显示给出这个参数需要绑定的值即可,那么这样的话原本fun需要传参三个参数,经过小编使用bind绑定了rate为11.11之后就不再需要传参rate了,相对于在fun中的rete形参给出缺省值,这个缺省值只能固定一个值,但是如果像下面需要计算三种rate的场景就不适用了,此时使用bind进行绑定参数最为合适
double fun(int x, int y, double rate){return (x + y) * rate;}int main(){auto newfun1 = bind(fun, placeholders::_1, placeholders::_2, 11.11);auto newfun2 = bind(fun, placeholders::_1, placeholders::_2, 22.22);auto newfun3 = bind(fun, placeholders::_1, placeholders::_2, 33.33);cout << newfun1(3, 6) << endl;cout << newfun2(3, 6) << endl;cout << newfun3(3, 6) << endl;return 0;}
运行结果如下
静态成员函数
- 当我们bind进行操作的可调用对象是静态成员函数的时候,要使用::声明类域才能进行相应的绑定或调整参数
class fun{public:static int sub(int x, int y){return x - y;}};int main(){auto newsub = bind(fun::sub, placeholders::_1, placeholders::_2);cout << newsub(1, 2);return 0;}
运行结果如下
成员函数
- 当bind操作的可调用对象是成员函数的时候,语法规定:取出成员函数的地址的时候要加入&,并且要传入类的对象(类的有名对象和匿名对象都可以)或类的指针
class fun{public:int sub(int x, int y){return x - y;}};int main(){fun f;auto newsub1 = bind(&fun::sub, &f, placeholders::_1, placeholders::_2);cout << newsub1(1, 2) << endl;//&f类的指针auto newsub2 = bind(&fun::sub, f, placeholders::_1, placeholders::_2);cout << newsub2(1, 2) << endl;//f类的对象auto newsub3 = bind(&fun::sub, fun(), placeholders::_1, placeholders::_2);cout << newsub3(1, 2) << endl;//fun()类的匿名对象return 0;}
运行结果如下
- 下面小编带领大家探索一下为什么需要传入类的对象或类的指针,带领大家看一下汇编,主要看一下call的这一行,由于有点长,所以小编分两次进行截图查看
- 我们可以看到调用newsub1()的时候,实际上是去调用了operator(),也就是说bind的底层还是仿函数,当bind操作的对象是类的成员函数sub的时候,bind操作后的新对象newsub编译器是将其转化为了仿函数,进行调用新对象newsub1(),实际上是去调用operator(),在operator()内是使用类的对象或指针去进行调用类的成员函数,所以当bind操作的对象是类的成员函数的时候,需要传类的对象和指针给operator()进行使用
总结
以上就是今天的博客内容啦,希望对读者朋友们有帮助
水滴石穿,坚持就是胜利,读者朋友们可以点个关注
点赞收藏加关注,找到小编不迷路!