仿函数C++

25 篇文章 1 订阅

仿函数

为什么我们需要使用仿函数(仿函数解决了什么痛点)?

仿函数的优点和作用?

为什么需要仿函数

场景一

  • 场景引入

  • 计算a数组里面全部的和,放在x变量里面。

#include<iostream>
#include<string>
#include<algorithm>

using namespace std;

template <typename InputIterator, typename T >
inline T accumulate(InputIterator first, InputIterator last, T init, T (*ptrA)(T, T)) {//函数指针
    while (first != last) {
        init = (*ptrA)(init, *first);
        ++first;
    }

    return init;
}

int funcA(int x, int y)
{
    return x + y;
}

int main(void) 
{
    int a[5] = {2, 5, 7, 9, 11};

    random_shuffle(&a[0], &a[5]);
    int x = ::accumulate(&a[0], &a[5], 0, funcA);
    cout << x << endl;

    return 0;
}
  • 这里就是计算a数组里面全部的和,放在x变量里面。
  • ​ 首先,这个函数的原型是T (*ptrA)(T x, T y);如果我想提升我处理数据的速度,让时间少浪费由于函数值传递引起的拷贝上,于是我需要这样写函数 T funcA(const int& x, const int& y)。这样写的话,我们原来的函数T (*ptrA)(T x, T y)就不能匹配这个函数,于是我们就要重载函数。还有就是效率问题,函数指针的调用,我们的电脑需要做很多工作,比如说保存当前的寄存器值,传递参数,返回值,返回到函数调用地方继续执行等。
  • 幸运的是,这一切问题都可以通过仿函数来解决,如下:
#include<iostream>
#include<string>
#include<algorithm>

using namespace std;

template <typename InputIterator, typename T, typename FunObject>
inline T accumulate(InputIterator first, InputIterator last, T init, FunObject object) {//函数对象
    while (first != last) {
        init = object(init, *first);
        ++first;
    }

    return init;
}

template < typename T>
class Test {
    public:
    T operator()(const T& x, const T& y) {
        return x + y;
    }
};

int main(void) 
{
    int a[5] = {2, 5, 7, 9, 11};

    random_shuffle(&a[0], &a[5]);
    int x = ::accumulate(&a[0], &a[5], 0, Test<int>());  //仿函数作为函数的入参,只需要传入类对象即可,这里传入的是匿名对象
    cout << x << endl;

    return 0;
}

这样就解决了效率和函数重载的问题了。

场景二

  • 假设我们现在有一个数组,数组中存有任意数量的数字,我们希望能够统计出这个数组中大于 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)阈值作为函数的局部变量。局部变量不能在函数调用中传递,故不可行;

bool IsGreaterThanThreshold(int num) {
int threshold; // 这里的threhold 没法获得外部的传参
	return num>threshold ? true : false;
}

(2)全局变量。我们可以将阈值设置成一个全局变量。这种方法虽然可行,但不优雅,且容易引入 Bug,比如全局变量容易同名,造成命名空间污染

int threshold=10; // 定义全局变量
bool IsGreaterThanThreshold(int num) {

	return num>threshold ? true : false;
}

(3)函数传参。这种方法我们已经讨论过了,多个参数不适用于已定义好的 RecallFunc() 函数。(除非你重写函数指针)

假设你设计的传参函数是这样的:

bool IsGreaterThanThreshold(int num, int threshold) {
	return num>threshold ? true : false;
}

在此基础上,你必须重写 RecallFunc() 函数 。

int RecallFunc(int *start, int *end, bool (*pf)(int,int),int threshold) {  这里就需要引入新参数,来指threshold。同时需要重写函数指针,以使其符合IsGreaterThanThreshold 。
    int count=0;
    for(int *i = start; i != end+1; i++) {
    	count = pf(*i,threshold) ? count+1 : count;
    }
    return count;
}
  1. 这种方法扩展性较差,当函数参数有所变化,则无法兼容旧的代码,具体在第一小节已经阐述。正如上面的例子,在我们写代码时有时会发现有些功能代码,会不断地被使用。为了复用这些代码,实现为一个公共的函数是一个解决方法。不过函数用到的一些变量,可能是公共的全局变量。引入全局变量,容易出现同名冲突,不方便维护。

  2. 这时就可以使用仿函数了,写一个简单类,除了维护类的基本成员函数外,只需要重载 operator() 运算符 。这样既可以免去对一些公共变量的维护,也可以使重复使用的代码独立出来,以便下次复用。而且相对于函数更优秀的性质,仿函数还可以进行依赖、组合与继承等,这样有利于资源的管理。如果再配合模板技术和 Policy 编程思想,则更加威力无穷,大家可以慢慢体会。Policy 表述了泛型函数和泛型类的一些可配置行为(通常都具有被经常使用的缺省值)。

  3. STL 中也大量涉及到仿函数,有时仿函数的使用是为了函数拥有类的性质,以达到安全传递函数指针、依据函数生成对象、甚至是让函数之间有继承关系、对函数进行运算和操作的效果。比如 STL 中的容器 set 就使用了仿函数 less ,而 less 继承的 binary_function,就可以看作是对于一类函数的总体声明,这是函数做不到的。

  • 仿函数实现
#include <iostream>
using namespace std;
 
class IsGreaterThanThresholdFunctor {
public:
	explicit IsGreaterThanThresholdFunctor(int t):threshold(t){}
	bool operator() (int num) const {
		return num > threshold ? 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, IsGreaterThanThresholdFunctor(10));//仿函数作为函数的入参,只需要传入类对象即可,这里传入的是匿名对象
	cout << result << endl;
}
  • 这个例子应该可以让您体会到仿函数的一些作用:它既能像普通函数一样传入给定数量的参数,还能存储或者处理更多我们需要的有用信息。于是仿函数提供了第四种解决方案:成员变量。成员函数可以很自然地访问成员变量,从而可以解决第一节“1.为什么要有仿函数”中提到的问题:计算出数组中大于指定阈值的数字数量。

仿函数是什么

具体定义

  • 仿函数(Functor)又称为函数对象(Function Object)是一个能行使函数功能的类。

  • 仿函数的语法几乎和我们普通的函数调用一样,不过作为仿函数的类,都必须重载 operator() 运算符。因为调用仿函数,实际上就是通过类对象调用重载后的 operator() 运算符。

实例

我们先来看一个仿函数的例子。

直接调用仿函数
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");// 隐式写法
  //   myFunctor2.operator()("Hello"); //显式写法
    

编译运行输出:

Hello and world!

仿函数作为函数入参
#include <iostream>
using namespace std;
 
class IsGreaterThanThresholdFunctor {
public:
	explicit IsGreaterThanThresholdFunctor(int t):threshold(t){}
	bool operator() (int num) const {
		return num > threshold ? 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, IsGreaterThanThresholdFunctor(10));//仿函数作为函数的入参,只需要传入类对象即可,这里传入的是匿名对象
	cout << result << endl;
}

仿函数和STL

  • 通过仿函数建立与stl沟通的桥梁,只为算法服务,当我需要对算法提出一些要求的时候,例如排序默认为从小到大,但是我需要由大到小进行排序,就需要用一般函数的形式或仿函数的形式告诉算法,实现第二个版本的算法。

  • 为什么要把加减,比大小等等功能定义成一个函数或仿函数,因为需要将这些信息传入算法中。算法拿到这些东西之后才会做出相对应的改变。

算术仿函数

STL内建的算术类仿函数,支持加法、减法、乘法、除法、模数(余数)、否定运算。

template<class T> T plus<T>    //加法仿函数
template<class T> T minus<T>    //减法仿函数
template<class T> T multiplies<T>//乘法仿函数
template<class T> T divides<T>   //除法仿函数
template<class T>T modulus<T>   //取模仿函数
template<class T> T negate<T>    //取反仿函数
    
    struct plus : public binary_function<T, T, T>
    {
        T operator()(const T &x, const T &y) const return x y;
    };
    template <class T>
    struct minus : public binary_function<T, T, T>
    {
        T operator()(const T &x, const T &y) const return x - y;
    };
    template <class T>

    struct multiplies : public binary_function<T, T, T>
    {
        T operator()(const T &x, const T &y) const return x y;
    };
    template <class T>
    struct divides : public binary_function<T, T, T>
    {
        T operator()(const T &x, const T &y) const return x y;
    };
    template <class T>
    struct modulus : public binary_function<T, T, T>
    {
        T operator()(const T &x, const T &y) const return x $y;
    };
    template <class T>
    struct negate : public unary_function<T, T>
    {
        T operator()(const T &x) const return -x;
    };
  • 例子
#include <iostream>
#include <functional>
using namespace std;

void test1()
{
	negate<int>n;
	cout << n(50) << endl;
}

void test2()
{
	plus<int>p;
	cout << p(10, 20) << endl;
}

int main()
{
	test1();
	test2();
	std::cout << "Hello World!\n";
}
// -50
// 30
关系运算符

STL支持6种关系运算,每一种都是二元运算

等于,不等于,大于,大于等于,小于,小于等于
template<class T>
struct equal_to:public binary_function<T,T,bool>
{
	bool operator()(const T&x,const T& y)const {return x==y;};
}
template<class T>
struct not_equal_to:public binary_function<T,T,bool>
{
	bool operator()(const T& x,const T& y)const {return x!=y;}
};
template<class T>
struct greater:public binary_function<T,T,bool>
{
	bool operator()(const T&x ,const T7 y)const {return x>y;}
};
template<class T>
struct less:public binary_function<T,T,bool>
{
	bool operator()(const T&x ,const T7 y)const {return x<y;}
};
template<class T>
struct greater_equal:public binary_function<T,T,bool>
{
	bool operator()(const T&x ,const T7 y)const {return x>=y;}
};
template<class T>
struct less_equal:public binary_function<T,T,bool>
{
	bool operator()(const T&x ,const T7 y)const {return x<=y;}
};
逻辑运算符
template<class T> bool logical_and<T>    //逻辑与
template<class T> bool logical_or<T>     //逻辑或
template<class T> bool logical_not<T>    //逻辑非

仿函数和智能指针

仿函数自定义删除器
//用来释放malloc出来的函数对象
template<class T>
class FreeFunc{
public:
	void operator()(T* ptr)
	{
		cout << "free:" << ptr << endl;
		free(ptr);
	}
};

//用来释放new[]出来的函数对象
template<class T>
class DeleteArrayFunc {
public:
	void operator()(T* ptr)
	{
		cout << "delete[]" << ptr << endl;
		delete[] ptr;
	}
};
//用来释放new 出来的函数对象
template<class T>
class DeleteArrayFunc {
public:
	void operator()(T* ptr)
	{
		cout << "delete " << ptr << endl;
		delete ptr;
	}
};

//用来释放文件描述符的函数对象
template<class T>
class ClosefdFunc{
public:
	void operator()(T* fd)
	{
		cout << "close fd" << fd << endl;
		fclose(fd);
	}
};

void test06(){

	FreeFunc<int>        Object1;
	shared_ptr<int>      sp1((int*)malloc(sizeof(int)*4), Object1);         // 回调函数是可调用对象,可以是普通的函数名或者函数对象或者lambda表达式

	DeleteArrayFunc<int> Object2;
	shared_ptr<int>      sp2(new int[4], Object2); 

	ClosefdFunc<FILE>    Object3;
	shared_ptr<FILE>     sp3(fopen("myfile.txt","w"), Object3);

}

	int main()
{
    test06();
    return 0;
}
  • 输出结果:

close fd0x7ff94b4bfa90
delete[]0x220c21d1ae0 
free:0x220c21d1770   
智能指针中的仿函数
//智能指针的删除器:
template<class _Ty>
struct default_delete{
//......
	
//default deleter for unique_ptr,其中_Ptr是智能指针底层资源的指针
void operator()(_Ty *_Ptr) const _NOEXCEPT
	{	// delete a pointer
		delete _Ptr; 
		//默认删除器仅仅只做一件事,只调用delete进行资源的释放
	}
	
//......
};

改进,针对这种特殊的情况,添加自定义的一个删除器保证资源释放完全:

template<typename Ty>
class Deleter{
public:
	void operator()(Ty *ptr)const{
		cout<<"Call a custom method !!!!! "<<endl;
		delete []ptr;
	}
};
int main(){
	std::unique_ptr<int,Deleter<int>> ptr(new int[100]);//delete []ptr
	return 0;
}

或者使用 default_delete来做删除器

// 可用default_delete来做删除器,default_.delete是标准库里的模板类。
    void fun4()
    {
        cout << "detail5:: func4()" << endl;
        shared_ptr<int> pi5(new int[100](), std::default_delete<int[]>());
    }

改进,争对这种特殊的情况,添加自定义的一个删除器保证资源释放完全:

template<typename Ty>
class Deleter{
public:
	void operator()(Ty *ptr)const{
		cout<<"Call a custom method !!!!! "<<endl;
		delete []ptr;
	}
};
int main(){
	std::unique_ptr<int,Deleter<int>> ptr(new int[100]);//delete []ptr
	return 0;
}

或者使用 default_delete来做删除器

// 可用default_delete来做删除器,default_.delete是标准库里的模板类。
    void fun4()
    {
        cout << "detail5:: func4()" << endl;
        shared_ptr<int> pi5(new int[100](), std::default_delete<int[]>());
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

丁金金

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值