C++ 仿函数和适配器
本文从不断复杂的应用场景入手,来说明C++设计仿函数和适配器的原因,并深入源码来介绍仿函数和适配器的使用方法。
仿函数
现有一个vector,需要统计大于8的元素个数。
使用std::count_if
,代码可能长这个样子。
bool greaterThan8(int n){
return n > 8;
}
int count = count_if(vec.begin(), vec.end(), greaterThan8);
count_if第三个参数接收的是一个函数指针,并且这个函数指针只接收一个参数,返回值是bool。
count_if是将参数1和参数2之间的每一项都会作为参数去调用第三个参数,然后返回第三个参数每一次true的次数。
这里是将8直接写在这里,并不符合编程规则,也不容易维护。
或者新加个参数
bool greaterThanN(int n, int x){
return n > x;
}
看起来可以解决问题。
但是上文说了,count_if的第三个参数函数指针,只能接收一个参数。
如何取消这个参数呢?
1、全局变量
int minValue = 8;
bool greaterThanN(int n){
return n > minValue ;
}
能解决问题,但是并不优雅。
1、容易出错
为什么这么说呢,我们必须先初始化maxLength的值,才能继续接下来的工作,如果我们忘了,则结果未定义。
此外,变量maxLength和函数LengthIsLessThan之间是没有必然联系的,编译器无法确定在调用该函数前是否将变量初始化。
需要使用者自己控制,则增加维护成本和出错可能性。
2、没有可扩展性
如果我们每遇到一个类似的问题就新建一个全局变量,尤其是多人合作写代码时,很容易引起命名空间污染(namespace polution)的问题。
3、全局变量的问题
全局变量花销很大,一般是编程过程中极力避免的
- 宏
\#define VALUE 8
bool greaterThanN(int n){
return n > VALUE;
}
换成宏是节省了全局变量的代价。但是依然不够优雅,容易出错且可扩展性差。
3.成员变量
我们既想要它既能想普通函数一样传入给定数量的参数,还能存储或者处理更多我们需要的有用信息。
如果存成成员变量,那成员函数调用不就是很正常的事了吗?
对,为了实现上面的内容,C++语言引入了仿函数的概念。
仿函数(functor),其实是像函数一样使用的对象,也被称为函数对象(function object)。仿函数里面重载了"()"操作符,是我们可以像调用函数一样调用它。
仿函数的基本使用
class functor {
public:
functor(int x) { value =x; }
void operator()(const int& x) { cout << x+value << endl;}
private:
int value;
};
functor fun(10);
fun(5); //15
为了满足文初的应用场景,使用仿函数的代码,大概长这样
template<typename T>
class greater{
public:
greater(const T&x) { info = x; }
bool operator()(const T& x) { return info > x; }
private:
T info;
};
调用代码
greater<int> ge(20);
int count = count_if(vec.begin(), vec.end(), ge);
或者直接使用临时变量
int count = count_if(vec.begin(), vec.end(), greater<int>(20));
当然,这里可以不用模板,但是为了扩展和方便,建议还是养成用模板的习惯。
另外,不用普通函数指针是因为函数指针的局限:
- 不能满足STL的抽象要求
参数,返回值如何设置 - 无法和STL其他组件交互
template<typename _Kty, typename _Pr = less<_Kty>,...>
class set{...};
class Person{...};
std::set<Person, std::less<Person> > set1, set2; //operator<做排序行为
std::set<Person, std::greater<Person> >set3,set4;//operator>做排序行为
set1 = set2; //正确,相同的型别
set1 = sete3; //错误,不同的型别!
STL中也预先实现了一些仿函数,需要包头文件#include<functional>
另外在c++11里面可以通过lambda表达式解决上述问题:
#include <vector>
#include <iostream>
#include <algorithm>
#include <functional>
using namespace std;
int main()
{
std::vector<int> c{ 1, 5, 3, 4, 5, 6, 7 };
int x = 5;
int k=std::count_if(c.begin(), c.end(), [x](int n){return x == n; });
cout << k << endl;
return 0;
}
适配器
bind1st/bind2nd
vector<int> vec{0,0,0,10};
怎么找到第一个非0元素?
通过std::not_equal_to
,代码应该是下面这样
std::vector<int>::iterator it = std::find_if(vec.begin(), vec.end(),
std::not_equal_to<int>(0));
无法通过编译。为什么?
not_equal_to
结构没有一个参数的构造函数not_equal_to
的operator()
需要接受两个参数
template<class _Ty = void>
struct not_equal_to
{ // functor for operator!=
typedef _Ty first_argument_type;
typedef _Ty second_argument_type;
typedef bool result_type;
_CONST_FUN bool operator()(const _Ty& _Left, const _Ty& _Right) const
{ // apply operator!= to operands
return (_Left != _Right);
}
};
现在,这里需要一个插座,也就是适配器模式里的适配器。
typename std::not_equal_to<int>::first_argument_type nonZeroItem(0);
std::not_equal_to<int> f;
std::vector<int>::iterator iter = find_if(vec.begin(), vec.end(),
std::binder1st<std::not_equal_to<int> >(f, nonZeroItem));
具体调用如下
使用f, nonZeroItem
来构造std::not_equal_to<int>
类型的bind1st
。operator()
实际上调用的是std::not_equal_to<int>
的operator()
。
为了方便使用,std使用bind1st
进行了封装
template<class _Fn2,
class _Ty> inline
binder1st<_Fn2> bind1st(const _Fn2& _Func, const _Ty& _Left)
{ // return a binder1st functor adapter
typename _Fn2::first_argument_type _Val(_Left);
return (binder1st<_Fn2>(_Func, _Val));
}
那我们的代码应该会变成这个样子
std::vector<int>::iterator iter = find_if(vec.begin(), vec.end(),
std::bind1st(std::not_equal_to<int>(), 0));
binder2nd
和binder1st
类似。区别只在于_Func
操作的是左值还是右值。
也就是说以下两句代码的同义的
std::vector<int>::iterator iter = find_if(vec.begin(), vec.end(),
std::bind1st(std::less<int>(), 0)); //std::bind2nd(std::greater<int>(), 0)
std::vector<int>::iterator iter = find_if(vec.begin(), vec.end(),
std::bind2nd(std::greater<int>(), 0));
bind1st操作的是左值,找到第一个X,使满足0 < X
bind2nd操作的是右值,找到第一个X,使满足X > 0
mem_fun/mem_fun_ref
设计和上面类似,是用于适配对象的函数
需求:打印vector中所有Person类的成员
std::vector<Person*> vec;
vec.push_back(new Person("I",1));
vec.push_back(new Person("love", 2));
vec.push_back(new Person("u", 3));
vec.push_back(new Person("so much", 4));
std::for_each(vec.begin(), vec.end(), &Person::Print); //编译不通过
如何能在函数中调用对象的成员函数呢?
首先,有对象obj,和函数fun,调用方法有如下几种:
1. fun(obj) //fun是全局函数,非成员函数
2. obj.fun() //obj非指针,fun是成员函数
3. obj->fun() //obj指针对象,fun是成员函数
for_each
会调用_For_each
只接收fun(obj)
的调用
所以代码应该是下面这个样子
std::for_each(vec.begin(), vec.end(), std::mem_fun(&Person::Print));
具体调用
mem_fun_ref
类似,但是用于容器中存放的不是指针的情况
释放内存
方法一
for (vector<Person*>::iterator it = vec.begin(); it != vec.end(); ++it)
delete (*it);
vec.clear();
方法二
struct DeleteItem {
template<typename _Ty>
void operator()(const _Ty p) const
{
delete (p);
}
};
std::for_each(vec.begin(), vec.end(), DeleteItem());
vec.clear();
注意
1、有继承的情况下,容器存放指针而不是对象
- 对象拷贝比指针拷贝对象大得多
- 父对象的容器存放子对象时,数据会Slice
2、使用指针记得释放内存
3、尽量用算法代替手写循环
代码更优雅,据说速度更快,但是还没测试过
C++ 仿函数和适配器的更多相关文章
- stl仿函数和适配器
所谓的适配器就是底层利用仿函数,然后修改仿函数的接口,达到自己的目的: 例如:template<class operation> class binder1st的适配器,其本质是一个类,它 ...
- C++标准库分析总结(八)——<仿函数、适配器、istream_iterator、ostream_iterator、bind>
一.仿函数定义 仿函数是STL中最简单的部分,存在的本质就是为STL算法部分服务的,一般不单独使用.仿函数(functors)又称为函数对象(function objects),虽然函数指针虽然也可以 ...
- STL标准库-仿函数与仿函数适配器
技术在于交流.沟通,本文为博主原创文章转载请注明出处并保持作品的完整性 概要: 1.仿函数 2.bind2nd() 3.not1() 4.bind() 仿函数的实现:声明一个类,重载它的operato ...
- STL六大组件之——适配器代表大会
适配器也是一种常用的设计模式: 将一个类的接口转换为另一个类的接口,使得原本因接口不兼容而不能合作的两个类可以一起运作.STL提供三种适配器:改变容器接口的容器适配器.改变迭代器接口的迭代器适配器以及 ...
- [GeekBand] STL 仿函数入门详解
本文参考文献::GeekBand课堂内容,授课老师:张文杰 :C++ Primer 11 中文版(第五版) page 37 :网络资料: 叶卡同学的部落格 http://www.leavesite. ...
- 【转载】C++知识库内容精选 尽览所有核心技术点
原文:C++知识库内容精选 尽览所有核心技术点 C++知识库全新发布. 该知识库由C++领域专家.CSDN知名博客专家.资深程序员和项目经理安晓辉(@foruok)绘制C++知识图谱,@wangshu ...
- C++ STL小知识
五种迭代器: 在STL中,迭代器主要分为5类,分别是:输入迭代器.输出迭代器.前向迭代器.双向迭代器和随机访问迭代器. 输入迭代器 :只读,支持++.==.!=: 输出迭代器 :只写,支持++: 前向 ...
- C++ 进阶必备
C++ 进阶要点(原理+熟练使用) 持续更新中 虚函数 虚继承 多继承 构造函数,拷贝构造函数,赋值构造函数,友元类,浅拷贝,深拷贝,运算符重载 class 类的基本使用,iostream获取屏幕输入 ...
- 一步一步的理解C++STL迭代器
一步一步的理解C++STL迭代器 "指针"对全部C/C++的程序猿来说,一点都不陌生. 在接触到C语言中的malloc函数和C++中的new函数后.我们也知道这两个函数返回的都是一 ...
随机推荐
- 以太坊再爆高危漏洞!黑客增发ATN 1100万枚token事件始末
事情发生在5月中旬,ATN技术人员发现Token合约由于存在漏洞受到攻击.不过ATN基金会随后透露,将销毁1100万个ATN,并恢复ATN总量,同时将在主链上线映射时对黑客地址内的资产予以剔除,确保原 ...
- 移动端测试之APP安全测试
现在APP测试已经是测试行业的一个重要分支,对APP测试技能和经验的要求也越来越高,看到一篇关于APP安全测试的总结,分享给需要的朋友.1.软件权限1)扣费风险:包括发送短信.拨打电话.连接网络等2) ...
- 计算机概论 64bit和32bit的CPU的不同
32位(WOW32).64位(WOW64,x64)指的是两种不同的CPU架构. 32位的CPU能够在每个时钟周期传输32位数据,而64位的CPU能够在每个时钟周期传输64位数据. 32位系统可以访问2 ...
- zookeeper客户端KeeperErrorCode = ConnectionLoss异常问题排查历险记
经过线报,说前方应用有异常,导致了可用性变差.咦!讨厌的异常,抛异常是程序猿最讨厌的事情之一. 经过收集异常信息如下 2019-06-24 10:57:41.806 ERROR [hades- ...
- Unity上线google商店 用IL2Cpp打包64位版本和Android APP Bundle优化 及产生的bug
ios刚上线,这边着手改成android版本,我开始使用的是unity2017.4.1版本 上传谷歌商店是出现这两个警告: 要支持64位,但是在2017版本上没有找到64位的打包选项,猜测应该是版本的 ...
- 一个jenkins的bug
部署在redhat上docker中的jenkins运行了很久了,最新发现一个站点不是最新的,于是去查看发现git仓库都连不上,我换成外网github的仓库就可以连非得是https吗???? 提示是域名 ...
- HDR10 中的名词解释
1. EOTF ( Electro-Optical Transfer Function ),电->光 转换函数.由电信号,转换成光信号时的规则.确定显示终端(电视机.投影仪等),如何合理地响应输 ...
- java web开发跨域问题
分布式环境,前后端分离背景下跨域问题 1.1 设置页面document.domain去把2个页面之间的跨域交互统一 一级域名相同的情况下 调用者和页面提供者进行一个协调 页面提供者要在document ...
- Java攻城狮面试题录:笔试篇(1)
1.作用域public,private,protected,以及不写时的区别答:区别如下:不写时默认为friendly 2.ArrayList和Vector的区别,HashMap和Hashtable的 ...
- shiro系列三、定义Realm
自定义realm: /** * 认证和授权 */ @Component public class UserRealm extends AuthorizingRealm { @Autowired pri ...