4.3 函数对象的本质

156 阅读3分钟

4.3.1 函数对象的回调行为

通过上面的讲解,已经实现了自己的函数对象,那么函数对象到底是什么呢?

首先函数对象是一个类对象,它是一个重载了函数调用操作符的类所定义的对象。这个类中必须包含函数调用操作符()的重载,其次它可以有自己的成员属性和成员方法,比如,上面我们定义的类中除了有函数调用操作符的重载,还有私有属性m_count和成员方法get_count()。

在for_each中对函数对象的使用上来看,我们不难发现,函数对象的行为很像一个函数,确切的说很像一个回调函数(C语言中函数指针做函数参数),所以它也叫仿函数。下面我们用一个普通函数来做for_each的参数,测试我们的猜想。

首先,定义一个模板函数

template<typename T>
void my_add(T& t)
{
	t++;
}

我们把这个函数做参数,并在main函数中继续添加如下测试代码

for_each(V.begin(), V.end(), my_add<int>);
print_vector_int(V);

编译运行,查看打印结果

可以看到,通过普通的模板函数,我们也实现了函数对象的功能,对容器中每个元素遍历并加1。由此可见,函数对象确实和回调函数有着类似的功能。

4.3.2 函数对象和回调函数的区别

既然回调函数和函数对象可以实现相同的功能,那么为什么要用函数对象呢?

首先,我们知道,函数对象是一个重载了()操作符的类所定义的对象,既然是类对象,那么他便可以拥有自己的属性和方法。就像前面我们通过类的私有属性m_count来计数容器中的元素个数,并通过成员函数get_count()来返回私有属性m_count。类中可以封装属性和方法,通过类对象做函数参数可以把属性和方法一块传入调用函数中,这是普通函数所不具备的。

4.4 完整代码

最后附上完整代码

#include <iostream>
using namespace std;

#include <vector>
#include <algorithm>

void print_vector_int(vector<int>& v)
{
	//for(vector<int>::iterator it = v.begin(); it != v.end(); it++)
	//{
	//	cout << *it << " ";
	//}
	for (unsigned int i = 0; i < v.size(); i++)
	{
		cout << v.at(i) << " ";
	}
	cout << endl;
}

template<typename _MyType>
class MySort
{
public:
	MySort()
	{
		this->m_count = 0;
	}
public:
	void operator()(_MyType& t) //怎么确定这个函数对象的参数呢?
	{
		t++;
		this->m_count++;
	}
public:
	int get_count()
	{
		return m_count;
	}
private:
	int m_count; //记录容器中元素个数
};

template<typename T>
void my_add(T& t)
{
	t++;
}

int main()
{
	vector<int> V;
	for (int i = 0; i < 10; i++)
	{
		V.push_back(rand());
	}
	print_vector_int(V);

	/*
	template <class _InIt, class _Fn>
	_Fn for_each(_InIt _First, _InIt _Last, _Fn _Func) { // perform function for each element [_First, _Last)
		_Adl_verify_range(_First, _Last); //范围:容器迭代器 begin 到 end
		auto _UFirst      = _Get_unwrapped(_First); //指针_UFirst指向迭代器_First的位置
		const auto _ULast = _Get_unwrapped(_Last); //指针_ULast迭代器_Last的位置
		for (; _UFirst != _ULast; ++_UFirst) { //遍历容器
			_Func(*_UFirst); //把容器元素逐个放入函数_Func作为其函数参数
		}

		return _Func; //返回函数对象
	}
	*/
	for_each(V.begin(), V.end(), MySort<int>()); //匿名对象
	print_vector_int(V);
	
	MySort<int> m_sort1; //MySort有私有变量m_count,所以需要提供构造函数初始化m_count
	for_each(V.begin(), V.end(), m_sort1);
	cout << "计数:" << m_sort1.get_count() << endl;
	print_vector_int(V);
	
	MySort<int> m_sort2; //MySort有私有变量m_count,所以需要提供构造函数初始化m_count
	m_sort2 = for_each(V.begin(), V.end(), m_sort2);
	cout << "计数:" << m_sort2.get_count() << endl;
	print_vector_int(V);
	
	for_each(V.begin(), V.end(), my_add<int>);
	print_vector_int(V);
	
	system("pause");
	return 0;
}

总结

学习STL最好的教材就是源码,通过分析源码可以深刻理解STL容器、迭代器和算法的精髓。参考源码,才能写出我们自己的、编译器认可的、规范的代码。