仿函数C++-创新互联

仿函数

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

成都创新互联2013年至今,先为罗平等服务建站,罗平等地企业,进行企业商务咨询服务。为罗平企业网站制作PC+手机+微官网三网同步一站式服务解决您的所有建站问题。

仿函数的优点和作用?

文章目录
  • 仿函数
    • 为什么需要仿函数
      • 场景一
      • 场景二
    • 仿函数是什么
      • 具体定义
      • 实例
          • 直接调用仿函数
          • 仿函数作为函数入参
    • 仿函数和STL
        • 算术仿函数
        • 关系运算符
        • 逻辑运算符
    • 仿函数和智能指针
        • 仿函数自定义删除器
        • 智能指针中的仿函数

为什么需要仿函数 场景一
  • 场景引入

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

#include#include#include

using namespace std;

templateinline 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#include#include

using namespace std;

templateinline 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());  //仿函数作为函数的入参,只需要传入类对象即可,这里传入的是匿名对象
    cout<< x<< endl;

    return 0;
}

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

场景二
  • 假设我们现在有一个数组,数组中存有任意数量的数字,我们希望能够统计出这个数组中大于 10 的数字的数量,你的代码很可能是这样的:
#includeusing 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<
  • 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,就可以看作是对于一类函数的总体声明,这是函数做不到的。

  • 仿函数实现
#includeusing 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!

仿函数作为函数入参
#includeusing 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内建的算术类仿函数,支持加法、减法、乘法、除法、模数(余数)、否定运算。

templateT plus    //加法仿函数
templateT minus    //减法仿函数
templateT multiplies //乘法仿函数
templateT divides   //除法仿函数
templateT modulus   //取模仿函数
templateT negate    //取反仿函数
    
    struct plus : public binary_function{T operator()(const T &x, const T &y) const return x y;
    };
    templatestruct minus : public binary_function{T operator()(const T &x, const T &y) const return x - y;
    };
    templatestruct multiplies : public binary_function{T operator()(const T &x, const T &y) const return x y;
    };
    templatestruct divides : public binary_function{T operator()(const T &x, const T &y) const return x y;
    };
    templatestruct modulus : public binary_function{T operator()(const T &x, const T &y) const return x $y;
    };
    templatestruct negate : public unary_function{T operator()(const T &x) const return -x;
    };
  • 例子
#include#includeusing namespace std;

void test1()
{negaten;
	cout<< n(50)<< endl;
}

void test2()
{plusp;
	cout<< p(10, 20)<< endl;
}

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

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

等于,不等于,大于,大于等于,小于,小于等于
templatestruct equal_to:public binary_function{bool operator()(const T&x,const T& y)const {return x==y;};
}
templatestruct not_equal_to:public binary_function{bool operator()(const T& x,const T& y)const {return x!=y;}
};
templatestruct greater:public binary_function{bool operator()(const T&x ,const T7 y)const {return x>y;}
};
templatestruct less:public binary_function{bool operator()(const T&x ,const T7 y)const {return xstruct greater_equal:public binary_function{bool operator()(const T&x ,const T7 y)const {return x>=y;}
};
templatestruct less_equal:public binary_function{bool operator()(const T&x ,const T7 y)const {return x<=y;}
};
逻辑运算符
templatebool logical_and//逻辑与
templatebool logical_or //逻辑或
templatebool logical_not//逻辑非
仿函数和智能指针 仿函数自定义删除器
//用来释放malloc出来的函数对象
templateclass FreeFunc{public:
	void operator()(T* ptr)
	{cout<< "free:"<< ptr<< endl;
		free(ptr);
	}
};

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

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

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

	DeleteArrayFuncObject2;
	shared_ptr  sp2(new int[4], Object2); 

	ClosefdFuncObject3;
	shared_ptr sp3(fopen("myfile.txt","w"), Object3);

}

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

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

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

或者使用 default_delete来做删除器

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

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

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

或者使用 default_delete来做删除器

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

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


网站栏目:仿函数C++-创新互联
新闻来源:http://scyanting.com/article/cdiece.html