C++STL中的函数对象

版权声明:实不相瞒,我也想成为大佬 https://blog.csdn.net/CV_Jason/article/details/83899253

前言

  所谓函数对象,即 Function Object ,或者称之为仿函数(functors)。顾名思义,就是函数的一种对象,我们可以把函数对象看作是一个函数与对象的结合,一方面,它本质上是一个对象,但主要功能是使用其成员函数(主要是operator())在不同的容器和函数中传值;另一方面,它相比于普通函数作为函数参数,具有更强大的数据传递能力。函数对象,像函数而比函数强大,是对象,而没有对象复杂。
  C++标准库中实现了许多高级数据结构和算法,函数对象就是打开标准库之门的一把钥匙。熟练使用函数对象在许多实用场景下具有无与伦比的优势。

Function Object(函数对象)概念

  所谓function object,是定义了一个operator()对象语法如下:

// 这里定义一个函数对象
class FunctionObjectType{
public:
	void operator()(){
		// TO-DO 这个函数体是函数对象的函数体
	}
}	

  定义了之后,我们这样使用——

	FunctionObejctType fo;
	fo();// 函数对象的使用

  本质上,我们只是重载了一个类的括号运算符,形式上,该类对象在使用括号时,与一般函数无异,但是普通函数在调用结束后,自己所拥有的数据便会回收,只能通过函数传参和返回值与外界交互,函数对象作为一个对象,可以通过对象的数据成员保持数据的生命周期,更加灵活。
  这种定义看似复杂,但是有三大优点——
  1. Function Object比一般函数更灵巧,因为它可以拥有多个状态,一个类可以对应多个对象,每个对象在不同的阶段可以保持不同的数据,相比于函数传值,函数对象免去了值传递的开销,灵活而强大;
  2. 每个function object都具有其类型,因此你可以将function object类型作为template参数传递,然后通过operator()函数体定义具体行为。这一点相当重要,因为C++标准库中提供了大量的template参数,函数对象能在这些template中来去自如;
  3. 说起来你可能不信,在执行速度上function object比function pointer更快

几种使用函数对象的场景

  下面通过几个例子来介绍函数对象的用武之地。

Function Object拥有多种状态

  下面展示function object如何行为像个函数,又拥有多个状态——

#include<iostream>
#include<list>
#include<algorithm>
#include<iterator>

using namespace std;

class IntSequence {
private:
	int value;
public:
	IntSequence(int initialValue) :value(initialValue) {

	}

	int operator()() {
		return ++value;
	}
};

int main(){
	list<int> coll;

	generate_n(
		back_inserter(coll), 		// 使用back_inserter迭代器插入元素
		9,							// 插入9个元素
		IntSequence(1));			// 插入值由IntSequence产生的临时对象指定

	for (auto elem : coll) {
		cout << elem << " ";
	}
	cout << endl;

	generate_n(
		back_inserter(coll),
		6,
		IntSequence(94));

	for (auto elem : coll) {
		cout << elem << " ";
	}
	cout << endl;
	return 0;
}

在这里插入图片描述
  这里,第一次使用generate_n函数的时候,通过IntSequence(1)指定了从1开始增加值,在generate_n执行期间,IntSequence(1)所产生的临时对象始终有效,因此产生了2 3 4 5序列,而在第二个generate_n执行时,第一个IntSequence(1)所产生的函数对象已不再有效,产生作用的是IntSequence(94)。因此,每一次产生临时对象,其在generate_n执行期间,保持了内部状态。如果希望函数对象保持一种外部状态,即其对象能与外部进行交互,那么只需要扩展对象生命周期到期望的位置即可。

#include<iostream>
#include<list>
#include<algorithm>
#include<iterator>

using namespace std;

class IntSequence {
private:
	int value;
public:
	IntSequence(int initialValue) :value(initialValue) {

	}

	int operator()() {
		return ++value;
	}
};

int main(){
	list<int> coll;
	// 这里的generate_n的模板参数不太一样
	generate_n<back_insert_iterator<list<int>>,int,IntSequence&>(
		back_inserter(coll), 
		5,
		fo);						// 【1】这里使用的是fo对象的引用

	for (auto elem : coll) {
		cout << elem << " ";
	}
	cout << endl;

	generate_n(
		back_inserter(coll),
		5,
		IntSequence(94)); 			// 【2】这里使用的是临时对象
	for (auto elem : coll) {
		cout << elem << " ";
	}
	cout << endl;

	generate_n(
		back_inserter(coll),
		5,
		fo);						// 【3】使用fo对象,但是这里是按值传递

	for (auto elem : coll) {
		cout << elem << " ";
	}
	cout << endl;

	generate_n(
		back_inserter(coll),
		5,
		fo);						// 【4】继续使用fo对象,作为对比

	for (auto elem : coll) {
		cout << elem << " ";
	}
	cout << endl;
	return 0;
}

在这里插入图片描述
  从程序运行结果不难看出,我们可以通过引用把函数对象的状态传递出来。
  【1】处使用的是引用传值,因此,generate_n函数内部无论发生什么,都会被fo对象记录,此所谓外部状态
  【2】处使用的是IntSequence产生的临时对象,因此和fo对象没有关系,只和临时对象的初值有关;
  【3】处使用的是fo对象,但需要注意的是,这里是按值传递(by value),但由于fo对象已经被【1】处的引用更改了 状态,此时从7开始插入;
  【4】处依旧是按值传递,显而易见,fo对象还是从7开始传值,由此可见,【3】并没有改变fo的状态,同理,【4】也没有。

for_each()的返回值

  for_each算法有一个独门绝技,就是可以传回function object——

#include<iostream>
#include<vector>
#include<algorithm>

using namespace std;

class MeanValue {
private:
	long num;
	long sum;

public:
	MeanValue() :num(0), sum(0) {};

	void operator()(int elem) {
		++num;
		sum += elem;
	}

	double value() {
		return static_cast<double>(sum) / static_cast<double>(num);
	}
};

int main(){
	vector<int> coll = { 1,2,3,4,5,6,7,8,9,10 };
	MeanValue mv = for_each(coll.begin(), coll.end(), 
				MeanValue());		//【1】 这里传递的是一个MeanValue的临时对象,发挥功能的就是operator()
	cout << "mean value:" << mv.value() << endl;
	return 0;
}

在这里插入图片描述

作为关联容器排序规则

  关联容器都会提供一个定义排序规则的接口,而该接口用函数对象来定义规则,简直天作之合。一方面,函数对象拥有类型,可以作为一种模板参数传值;另一方面,函数对象又可以拥有状态,功能强大。

#include<iostream>
#include<set>
using namespace std;

class Person{
public:
	Person(string str1, string str2):firstname(str1),lastname(str2) {}
	string firstname;
	string lastname;
	friend ostream& operator<<(ostream&out, const Person & p);
};

class PersonSortCriterion {
public:
	bool operator() (const Person&p1, const Person&p2) const {
		return p1.firstname < p2.firstname ||
			(p1.firstname == p2.firstname&&p1.lastname < p2.lastname);
	}
};
// 为了方便输出,我们重载了<<符号
ostream &operator<<(ostream&out,const Person & p)
{
	// TODO: insert return statement here
	cout << p.firstname << " " << p.lastname;
	return out;
}

int main(){
	set<Person,PersonSortCriterion> ss{ 
		Person("Jason","Lee"),
		Person("Jack","Chen"),
		Person("Alpha","Lee"),
		Person("Alience","Steven"),
		Person("Luffy","Lily") };
	for (auto&elem : ss) {
		cout << elem << endl;
	}
	return 0;
}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/CV_Jason/article/details/83899253