【C++11/高级语法】bind绑定器和function函数对象

目录

  • bind1st和bind2nd什么时候会用到
  • bind1st和bind2nd的底层实现原理
  • function函数对象类型的应用示例
  • lambda表达式的应用实践

橙色

绑定器和函数对象operator()

函数对象就是对象拥有()运算符重载函数,这个对象使用起来就跟函数调用特别相似。

1.C++ STL中的绑定器
bind1st:operator ()的第一个形参变量绑定成一个确定的值
bind2nd:operator ()的第二个形参变量绑定成一个确定的值

2.C++11从Boost库中引入了bind绑定器和function函数对象机制

3.lambda表达式,底层依赖函数对象的机制实现的

bind1st和bind2nd什么时候会用到

bind1st和bind2nd这两个函数在functional库中。这两个函数都是用在二元函数对象身上的

注意代码第36行,find_if的第三个参数是需要一个一元的函数对象,因为它需要一次从容器中取出一个元素与70进行比较。因此没法直接使用algorithm库里的greater()等函数对象,因为它们都是二元的。

所以就引入了bind1st和bind2nd

绑定器+二元函数对象=一元函数对象

下面的例子中,通过find_if找到了小于70的第一个数,并把70插在了它的前面。greater()函数对象的逻辑是a>b,通过bind1st(greater(), 70)其函数对象的逻辑就变成了70>b,依此来寻找小于70的第一个元素。

同样也可以用less()来实现寻找小于70的第一个数的功能,因为该函数对象的逻辑是a<b,所以bind2nd(greater(), 70),其逻辑就变成了a<70。同样是寻找小于70的第一个元素

#include <iostream>
#include <functional>
#include<vector>
#include<algorithm>
#include<ctime>
using namespace std;template<typename Container>
void showContainer(Container &con)
{typename Container::iterator it = con.begin();for (; it != con.end();it++){cout << *it << " ";}cout << endl;
}int main(){vector<int> vec;srand(time(nullptr));for (int i = 0; i < 20;i++){vec.push_back(rand()% 100 + 1);}showContainer(vec);sort(vec.begin(), vec.end()); //默认从小到大showContainer(vec);//greater 二元函数对象sort(vec.begin(), vec.end(), greater<int>());showContainer(vec);/*把70按顺序插入到vec容器当中  找第一个小于70的数字*/auto it1 = find_if(vec.begin(), vec.end(), bind1st(greater<int>(), 70));cout << *it1 << endl;if (it1 != vec.end()) {vec.insert(it1, 70);}showContainer(vec);return 0;
}

在这里插入图片描述

bind1st和bind2nd的底层实现原理

这段代码是很精妙的,下面有详细的解释,可以好好看看

#include <iostream>
#include <functional>
#include<vector>
#include<algorithm>
#include<ctime>
using namespace std;template<typename Container>
void showContainer(Container &con)
{typename Container::iterator it = con.begin();for (; it != con.end();it++){cout << *it << " ";}cout << endl;
}template<typename Iterator,typename Compare>
Iterator my_find_if(Iterator first,Iterator last,Compare comp)
{for (; first != last;++first){if(comp(*first))//comp.operator()(*first){return first;}} return last;
}template<typename Compare,typename T>
class _mybind1st
{
public:_mybind1st(Compare comp,T val):_comp(comp),_val(val){}bool operator()(const T&second){return _comp(_val, second);}
private:Compare _comp;T _val;
};template<typename Compare,typename T>
_mybind1st<Compare,T> mybind1st(Compare comp,const T &val)
{return _mybind1st<Compare, T>(comp, val);
}int main(){vector<int> vec;srand(time(nullptr));for (int i = 0; i < 20;i++){vec.push_back(rand()% 100 + 1);}//greater 二元函数对象sort(vec.begin(), vec.end(), greater<int>());showContainer(vec);/*把70按顺序插入到vec容器当中  找第一个小于70的数字*/auto it1 = my_find_if(vec.begin(), vec.end(), mybind1st(greater<int>(), 70));cout << *it1 << endl;if (it1 != vec.end()) {vec.insert(it1, 70);}showContainer(vec);return 0;
}

在这里插入图片描述
上面这段代码是对bind绑定器函数的一个实现。我的目标是在已经按降序排列的容器中把70按顺序插入到容器中,其实也就是找到小于70的第一个数字。my_find_if函数的形参有三个,分别是初始的迭代器,末尾的迭代器和一个二元函数对象。这个二元函数对象形参实际上传入的参数是mybind1st(greater(), 70),也就是传入的是mybind1st函数的返回值。

mybind1st函数返回的是一个二元函数对象_mybind1st<Compare, T>(comp, val),看本文最开始函数对象的定义,其实返回的就是一个有()运算符重载的类对象,因为_mybind1st<Compare, T>(comp, val)已经算是调用了构造函数对这个类对象进行初始化了。其类成员变量_comp为greater(),_val为val

所以呢,my_find_if中的第3个形参cmp在本代码中实际上就是_mybind1st<Compare, T>(comp, val)。my_find_if函数内的comp(*first)其实就是调用了_mybind1st类对象的()运算符重载。也就是下面这段代码:

bool operator()(const T&second){return _comp(_val, second);}

也就是挨个把_val(为70)和迭代器所指向的元素一起传入到_comp函数对象(也就是greater())中,看70是否大于该迭代器所指向的元素,是的话就返回该迭代器(也就是寻找到小于70的第一个元素,代码中容器初始顺序是从大到小)。

function函数对象类型的应用示例

使用functioal函数对象类型需要头文件< functional >.

下面是几个简单的应用:

#include <iostream>
#include <functional>
#include<string>
using namespace std;/*
function :绑定器,函数对象,lambda表达式它们只能使用在一条语句中
*/
void hello1()
{cout << "hello world!" << endl;
}void hello2(string str)
{cout << str << endl;
} int sum(int a,int b)
{return a + b;
}
int main()
{//从function的类模板定义处,看到希望用一个函数类型实例化functionfunction<void()> func1 = hello1;func1();    //func1.operator()()=>hello1()function<void(string)> func2 = hello2;func2("hello hello2");    //func2.operator()(string str)=>hello2(str)function<int(int, int)> func3 = sum;cout << func3(20, 30)<<endl;function<int(int,int)>func4=[](int a,int b)->int {return a+b;};cout << func4(20, 30) << endl;return 0;
}

在这里插入图片描述
上面这些例子都是通过function函数对象指向函数的,但function函数对象不仅可以指向函数,也可以指向类中的成员函数,但就没办法直接指向了,而是必须依赖于一个对象,参考下面的代码:

#include <iostream>
#include <functional>
#include<string>
using namespace std;class Test
{
public://必须依赖一个对象void hello(string str) { cout << str << endl; }
};
int main()
{//因为类对象自带一个this指针,所以参数是两个function<void(Test *, string)> func5 = &Test::hello;Test a;func5(&a, "call Test::hello!");return 0;
}

看完上面这些内容,那function到底强大在哪里呢?直接用函数不也可以吗?看下面这个例子:

#include <functional>
#include <iostream>
#include <map>
using namespace std;void doShowAllBooks() { cout << "查看所有书籍信息" << endl; }
void doBorrow() { cout << "借书" << endl; }
void doBack() { cout << "还书" << endl; }
void doQueryBooks() { cout << "查询书籍" << endl; }
void doLoginOut() { cout << "注销" << endl; }
int main()
{int choice = 0;map<int, function<void()>> actionMap;actionMap.insert({1, doShowAllBooks});actionMap.insert({2, doBorrow});actionMap.insert({3, doBack});actionMap.insert({4, doQueryBooks});actionMap.insert({5, doLoginOut});for (;;){cout << "---------------------" << endl;cout << "1、查看所有书籍信息" << endl;cout << "2、借书" << endl;cout << "3、还书" << endl;cout << "4、查询书籍" << endl;cout << "5、注销" << endl;cout << "---------------------" << endl;cout << "请选择:";cin >> choice;  auto it=actionMap.find(choice);if(it==actionMap.end()){cout << "输入数字无效,重新选择" << endl;}else{it->second();}}return 0;
}

假如直接在主函数中通过switch来实现也可以,但就不符合写代码的开闭原则;而且后面想加减功能就必须在主函数中改动。但像我们这么写,清晰明了,无论后续对功能作何种改动,都可以直接在相应函数上该,而不需要对主函数进行大的改动。

lambda表达式的应用实践

lambda表达式的语法:
在这里插入图片描述

举个例子

#include <functional>
#include <iostream>
#include<vector>
#include <algorithm>
using namespace std;int main()
{vector<int> vec = {9, 7, 4, 2};//寻找第一个小于5的数字,并按照降序插入其中auto it = find_if(vec.begin(), vec.end(), [=](int a) -> bool{ return a < 5; });if(it!=vec.end()){vec.insert(it, 5);}for(int val:vec){cout << val << " ";}cout <<endl;//找vec中的偶数并输出for_each(vec.begin(), vec.end(), [](int a){if(a%2==0)cout << a << " "; });cout << endl;return 0;
}

在这里插入图片描述

既然 lambda 表达式只能使用在语句当中,如果想跨语句使用之前定义好的lambda表达式,怎么办?用什么类型来表示lambda表达式?
当然是用function类型来表示函数对象的类型了

下面是一个函数对象方面应用的例子

#include <functional>
#include <iostream>
#include<vector>
#include <algorithm>
#include<map>
using namespace std;//lambda表达式=》函数对象int main()
{map<int, function<int(int, int)>> caculateMap;caculateMap[1]=[](int a,int b)->int{return a+b;};caculateMap[2]=[](int a,int b)->int{return a-b;};caculateMap[3]=[](int a,int b)->int{return a*b;};caculateMap[4]=[](int a,int b)->int{return a/b;};cout << "选择:";int choice;cin >> choice;cout << "10+15:" << caculateMap[choice](10, 15) << endl;return 0;return 0;
}

再举一个智能指针自定义删除器方面的例子:

#include <functional>
#include <iostream>
#include<vector>
#include <algorithm>
#include<memory>
using namespace std;int main()
{unique_ptr<FILE, function<void(FILE *)>> ptr1(fopen("data.txt", "w"), [](FILE *pf){ fclose(pf); });return 0;
}

再举一个优先队列的例子:
下面的代码是会报错误的,因为优先队列默认的情况下维护的是一个大堆,即权值以从高到低排列。但此时传入的是一个自定义变量Data,所以就不知道该怎么排了

#include <functional>
#include <iostream>
#include<vector>
#include<queue>
using namespace std;class Data
{
public:Data(int val1=10,int val2=10):ma(val1),mb(val2){}int ma;int mb;
};int main()
{priority_queue<Data> queue;queue.push(Data(10, 20));queue.push(Data(15, 15));queue.push(Data(20, 10));return 0;
}

我们可以通过lambda匿名函数这么写:

#include <functional>
#include <iostream>
#include<vector>
#include<queue>
using namespace std;class Data
{
public:Data(int val1=10,int val2=10):ma(val1),mb(val2){}int ma;int mb;
};int main()
{using FUNC = function<bool(Data &, Data &)>;priority_queue<Data, vector<Data>, FUNC>maxHeap([](Data &d1, Data &d2) -> bool{ return d1.ma > d2.ma;});maxHeap.push(Data(10, 20));maxHeap.push(Data(15, 15));maxHeap.push(Data(20, 10));return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端学习笔记--面试题系列总结

event loop它的执行顺序&#xff1a; 一开始整个脚本作为一个宏任务执行执行过程中同步代码直接执行&#xff0c;宏任务进入宏任务队列&#xff0c;微任务进入微任务队列当前宏任务执行完出队&#xff0c;检查微任务列表&#xff0c;有则依次执行&#xff0c;直到全部执行完执…

Hadoop入门——数据分析基本步骤

文章目录 1.概述2.分析步骤2.1第一步 明确分析目的和思路2.2第二步 数据收集2.3第三步 数据处理2.4第四步 数据分析2.5第五步 数据展现2.6第六步 报告撰写 3.总结 1.概述 2.分析步骤 2.1第一步 明确分析目的和思路 2.2第二步 数据收集 2.3第三步 数据处理 2.4第四步 数据分析 …

如何使用Docker搭建Drupal内容管理系统并远程访问

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525;个人专栏:《Linux深造日志》《C干货基地》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 前言1. Docker安装Drupal2. 本地局域网访问3 . Linux 安装cpolar4. 配置Drupal公网访问地址5. 公网远程访问Drupal…

Kakaotalk如何注册?常见问题解答

kakaoTalk是一款韩国即时通讯软件&#xff0c;使用程度类似于国内的微信&#xff0c;他还包含叫车服务、食品外送、餐厅预订、支付和游戏等多种功能&#xff0c;几乎每个韩国人都在使用KakaoTalk。 因此&#xff0c;对于要发展韩国市场的独立站人&#xff0c;这款软件必定是营…

人大金仓物理备份异机恢复

概述 KingbaseES V8支持使用RMAN物理备份在异机环境恢复&#xff0c;通过重新克隆方式完扩展主备集群。 原集群环境&#xff1a;演示用例&#xff0c;仅供参考 查看原集群备份和物理备份路径 异机恢复 前置条件 *获取原集群物理备份文件&#xff0c;包括全量备份、增量备份…

Java项目maven打包,打jar包中不包含项目引用第三方jar包,以及打war包不能将其放到lib的问题

在使用maven进行打包项目中&#xff0c;想要将第三方的jar包放入&#xff0c;有两种方法&#xff1a;一种将jar包上传到maven库中&#xff0c;第二种再pom.xml中进行配置&#xff0c;第三种 情况是需要打包成war包放入tomcat中&#xff1b;具体如下&#xff1a; 第一种&#x…

nacos集群配置(超完整)

win配置与linux一样&#xff0c;换端口或者换ip&#xff0c;文章采用的 linux不同IP&#xff0c;同一端口 节点ipportnacos1192.168.253.168848nacos2192.168.253.178848nacos3192.168.253.188848 单IP多个端口 1.复制两个&#xff0c;重命名 2.修改 conf目录下的 application…

Ubuntu 20.04编译Chrome浏览器

本文记录chrome浏览器编译过程&#xff0c;帮助大家避坑qaq 官网文档&#xff1a;https://chromium.googlesource.com/chromium/src//main/docs/linux/build_instructions.md 一.系统要求 一台64位的英特尔机器&#xff0c;至少需要8GB的RAM。强烈推荐超过16GB。至少需要100…

淘宝京东优惠券信息API接口系列

获取淘宝优惠券信息接口需要使用淘宝开放平台提供的API接口。以下是获取优惠券信息的步骤&#xff1a; 进入淘宝开放平台&#xff0c;注册并登录账号。在开放平台页面中&#xff0c;找到“优惠券”或“营销工具”等相关的API接口&#xff0c;根据需要进行选择。根据接口文档&a…

Java 设计模式——组合模式

目录 1.概述2.结构3.实现3.1.抽象构件3.2.容器构件3.3.叶子节点3.4.测试 4.分类5.使用场景6.优点 1.概述 &#xff08;1&#xff09;大家对于上面这个图片肯定非常熟悉&#xff0c;上图我们可以看做是一个文件系统&#xff0c;对于这样的结构我们称之为树形结构。在树形结构中可…

通讯协议学习之路(实践部分):SPI开发实践

通讯协议之路主要分为两部分&#xff0c;第一部分从理论上面讲解各类协议的通讯原理以及通讯格式&#xff0c;第二部分从具体运用上讲解各类通讯协议的具体应用方法。 后续文章会同时发表在个人博客(jason1016.club)、CSDN&#xff1b;视频会发布在bilibili(UID:399951374) 本文…

【MySQL】MySQL中的锁

全局锁 全局锁是对整个数据库实例加锁&#xff0c;整个库处于只读状态。 flush tables with read lock 适用场景 全局锁适用于做全库逻辑备份&#xff0c;但是整个库处于只读状态&#xff0c;在备份期间&#xff0c;所有的更新操作、DDL将会被阻塞&#xff0c;会对业务产生影…