1.为什么要有仿函数
我们先从一个非常简单的问题入手。假设我们现在有一个数组,数组中存有任意数量的数字,我们希望能够计数出这个数组中大于10的数字的数量,你的代码很可能是这样的:
#include <iostream> using namespace std; int RecallFunc(int *start, int *end, bool (*pf)(int)) { int count=0; for(int *i=start;i!=end+1;i++) { count = pf(*i) ? count+1 : count; } return count; } bool IsGreaterThanTen(int num) { return num>10 ? true : false; } int main() { int a[5] = {10,100,11,5,19}; int result = RecallFunc(a,a+4,IsGreaterThanTen); cout<<result<<endl; return 0; }
RecallFunc()函数的第三个参数是一个函数指针,用于外部调用,而IsGreaterThanTen()函数通常也是外部已经定义好的,它只接受一个参数的函数。如果此时希望将判定的阈值也作为一个变量传入,变为如下函数就不可行了:
bool IsGreaterThanThreshold(int num, int threshold) { return num>threshold ? true : false; }
虽然这个函数看起来比前面一个版本更具有一般性,但是它不能满足已经定义好的函数指针参数的要求,因为函数指针参数的类型是bool (*)(int),与函数bool IsGreaterThanThreshold(int num, int threshold)的类型不相符。如果一定要完成这个任务,按照以往的经验,我们可以考虑如下可能途径:
(1)阈值作为函数的局部变量。局部变量不能在函数调用中传递,故不可行;
(2)函数传参。这种方法我们已经讨论过了,多个参数不适用于已定义好的RecallFunc函数。
(3)全局变量。我们可以将阈值设置成一个全局变量。这种方法虽然可行,但是不优雅,且非常容易引入Bug,比如全局变量容易同名,造成命名空间污染。
那么有什么好的处理方法呢?仿函数应运而生。
2.仿函数的定义
仿函数(Functor)又称为函数对象(Function Object)是一个能行使函数功能的类。仿函数的语法几乎和我们普通的函数调用一样,不过作为仿函数的类,都必须重载operator()运算符。因为调用仿函数,实际上就是通过类对象调用重载后的operator()运算符。
如果编程者要将某种“操作”当做算法的参数,一般有两种方法:
(1)一个办法就是先将该“操作”设计为一个函数,再将函数指针当做算法的一个参数。上面的实例就是该做法;
(2)将该“操作”设计为一个仿函数(就语言层面而言是个class),再以该仿函数产生一个对象,并以此对象作为算法的一个参数。
很明显第二种方法会更优秀,原因也在上一小节有所阐述。正如上面的例子,在我们写代码时有时会发现有些功能代码,会不断地被使用。为了复用这些代码,实现为一个公共的函数是一个解决方法。不过函数用到的一些变量,可能是公共的全局变量。引入全局变量,容易出现同名冲突,不方便维护。
这时就可以用仿函数了,写一个简单类,除了维护类的基本成员函数外,只需要重载operator()运算符 。这样既可以免去对一些公共变量的维护,也可以使重复使用的代码独立出来,以便下次复用。而且相对于函数更优秀的性质,仿函数,还可以进行依赖、组合与继承等,这样有利于资源的管理。如果再配合模板技术和Policy编程思想,那就更是威力无穷了,大家可以慢慢体会。Policy表述了泛型函数和泛型类的一些可配置行为(通常都具有被经常使用的缺省值)。
STL中也大量涉及到仿函数,有时仿函数的使用是为了函数拥有类的性质,以达到安全传递函数指针、依据函数生成对象、甚至是让函数之间有继承关系、对函数进行运算和操作的效果。比如STL中的容器set就使用了仿函数less ,而less继承的binary_function,就可以看作是对于一类函数的总体声明了,这是函数做不到的。
//less的定义 template<typename _Tp> struct less : public binary_function<_Tp, _Tp, bool> { bool operator()(const _Tp& __x, const _Tp& __y) const { return __x < __y; } }; //set的申明 template<typename _Key, typename _Compare = std::less<_Key>, typename _Alloc = std::allocator<_Key>> class set;
仿函数中的变量可以是static的,同时仿函数还给出了static的替代方案,仿函数内的静态变量可以改成类的私有成员,这样可以明确地在析构函数中清除所用的内容,如果用到了指针,那么这个是不错的选择。有人说这样的类已经不是仿函数了,但其实,封装后从外界观察,可以明显地发现,它依然有函数的性质。
3.仿函数实例
我们先来看一个仿函数的例子:
#include <iostream> #include <string> using namespace std; class Functor { public: void operator() (const string& str) const { cout << str << endl; } }; int main() { Functor myFunctor; myFunctor("Hello world!"); return 0; }
程序输出:
Hello world!。
可以见到,仿函数提供了第四种解决方案:成员变量。成员函数可以很自然的访问成员变量,从而解决上文最开始的那个问题。
class StringAppend { public: explicit StringAppend(const string& str) : ss(str){} void operator() (const string& str) const { cout<<str<<' '<<ss<<endl; } private: const string ss; }; int main() { StringAppend myFunctor2("and world!"); myFunctor2("Hello"); return 0; }
程序输出:
Hello and world!。
这个例子应该可以让您体会到仿函数的一些作用:它既能像普通函数一样传入给定数量的参数,还能存储或者处理更多我们需要的有用信息。于是本小节开头的问题就迎刃而解了:
#include <iostream> using namespace std; class IsGreaterThanThresholdFunctor { public: explicit IsLessThanTenFunctor(int tmp_threshold) : threshold(tmp_threshold{} bool operator() (int num) const { return num>10 ? true : false; } private: const int threshold; }; int RecallFunc(int *start, int *end, IsGreaterThanThresholdFunctor myFunctor) { int count=0; for(int *i=start;i!=end+1;i++) { count = myFunctor(*i) ? count+1 : count; } return count; } int main() { int a[5] = {10,100,11,5,19}; int result = RecallFunc(a,a+4,IsLessThanTenFunctor(10)); cout<<result<<endl; return 0; }
以上就是浅析C++ 仿函数的详细内容,更多关于C++ 仿函数的资料请关注小牛知识库其它相关文章!
本文向大家介绍浅析C/C++中sort函数的用法,包括了浅析C/C++中sort函数的用法的使用技巧和注意事项,需要的朋友参考一下 sort是STL中提供的算法,头文件为#include<algorithm>以及using namespace std; 函数原型如下: 使用第一个版本是对[first,last)进行升序排序,默认操作符为"<",第二个版本使用comp函数进行排序控制,comp包含两
本文向大家介绍深入浅析C++多态性与虚函数,包括了深入浅析C++多态性与虚函数的使用技巧和注意事项,需要的朋友参考一下 派生一个类的原因并非总是为了继承或是添加新的成员,有时是为了重新定义基类的成员,使得基类成员“获得新生”。面向对象的程序设计真正的力量不仅仅是继承,而且还在于允许派生类对象像基类对象一样处理,其核心机制就是多态和动态联编。 (一)多态性 多态是指同样的消息被不同的对象接收时导
本文向大家介绍浅析C语言中strtol()函数与strtoul()函数的用法,包括了浅析C语言中strtol()函数与strtoul()函数的用法的使用技巧和注意事项,需要的朋友参考一下 C语言strtol()函数:将字符串转换成long(长整型数) 头文件: strtol() 函数用来将字符串转换为长整型数(long),其原型为: 【参数说明】str 为要转换的字符串,endstr 为第一个不能
本文向大家介绍Lua中的函数浅析,包括了Lua中的函数浅析的使用技巧和注意事项,需要的朋友参考一下 一、函数 在lua中函数的调用方式和C语言基本相同。 如print(“hello world”), z=add(x+y)。唯一的差别是,如果函数只有一个参数,并且该参数是字符串或者table构造器,那么圆括号可以省略,如print “hello world”, f{x=10,y=10}。 我对于ta
仿函数、仿函数类、函数等 无论喜欢或不喜欢,函数和类似函数的对象——仿函数——遍布STL。关联容器使用它们来使元素保持有序;find_if这样的算法使用它们来控制它们的行为;如果缺少它们,那么比如for_each和transform这样的组件就没有意义了;比如not1和bind2nd这样的适配器会积极地产生它们。 是的,在你看到的STL中的每个地方,你都可以看见仿函数和仿函数类。包括你的源代码中。
本文向大家介绍PHP filesize函数用法浅析,包括了PHP filesize函数用法浅析的使用技巧和注意事项,需要的朋友参考一下 filesize() 函数返回指定文件的大小。 若成功,则返回文件大小的字节数。若失败,则返回 false 并生成一条 E_WARNING 级的错误。 php filesize()函数 语法 作用:返回指定文件的大小。 语法: 参数: filename 必需。规定