一. 概念简述
从概念上说,函数对象是用作函数的对象;但从实现上说,函数对象是实现了 operator()的类的对象。虽然函数和函数指针也可归为函数对象,但只有实现了 operator()的类的对象才能保存状态(即类的成员属性的值),才能用于标准模板库(STL)算法。
常用于 STL 算法的函数对象可分为下列两种类型。
• 一元函数:接受一个参数的函数,如 f(x)。如果一元函数返回一个布尔值,则该函数称为谓词。
• 二元函数:接受两个参数的函数,如 f(x, y)。如果二元函数返回一个布尔值,则该函数称为二元谓词。
返回布尔类型的函数对象通常用于需要进行判断的算法,组合两个函数对象的函数对象称为自适应函数对象。
二.具体用法
1.一元函数
屏幕上显示元素
函数 FuncDisplayElement 接受一个类型为模板化类型 elementType 的参数,并使用控制台输出语句 std::cout 将该参数显示出来。该函数也可采用另一种表现形式,即其实现包含在类或结构的 operator()中:
template <typename elementType>
void FuncDisplayElement (const elementType& element)
{
cout << element << ' ' ;
};
template <typename elementType>
struct DisplayElement
{
void operator () (const elementType& element) const
{
cout << element << ' ';
}
};
利用for_each()显示集合内容在屏幕上
#include <algorithm>
#include <iostream>
#include <vector>
#include <list>
using namespace std;
template <typename elementType>
struct DisplatElement
{
void operator()(const elementType& element)const
{
cout << element << ' ';
}
};
int main()
{
vector<int> vecInt{1,2,3,4,5,6};
for_each(vecInt.begin(), vecInt.end(),DisplatElement<int>());
list<char> lisChar{ 'a','b','c','d','e' };
for_each(lisChar.begin(), lisChar.end(), DisplatElement<char>());
return 0;
}
在上述代码中,可不使用结构 DisplayElement,而使用 FuncDisplayElement,其效果相同:
for_each (lisChar.begin(),lisChar.end(), FuncDisplayElement<char>);
如果能够使用结构的对象来存储信息,则使用在结构中实现的函数对象的优点将显现出来。这是 FuncDisplayElement 不像结构那么强大的地方,因为结构除 operator( )外还可以有成员属性。如下述代码中,operator( )不再是 const 成员函数,因为它对成员 count 进行递增(修改),以记录自己被调用用于显示数据的次数。该计数是通过公有成员属性 count 暴露的。
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
template <typename elementType>
struct DisplayElementKeepCount
{
int count;
DisplayElementKeepCount() :count(0) {}
void operator()(const elementType& element)
{
++count;
cout << element << ' ';
}
};
int main()
{
vector<int> vecInt{1,2,3,4,5,6};
DisplayElementKeepCount<int> result;
result = for_each(vecInt.begin(), vecInt.end(), DisplayElementKeepCount<int>());
cout << endl << "输出次数:" << result.count;
return 0;
}
2.一元谓词
返回布尔值的一元函数是谓词。这种函数可供 STL 算法用于判断。
在vector中查找能被某数整除的元素
首先,IsMutilple 这里的 operator( )返回布尔值,可用作一元谓词。该结构有一个构造函数,它初始化除数的值。然后用保存在对象中的这个值来判断要比较的元素是否可以被它整除,如 operator( )的实现所示,它使用数学运算取模%来返回除法运算的余数。然后将余数与零进行比较,以判断被除数是否为除数的整数倍。
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
template <typename numberType>
struct IsMultiple
{
numberType Divisor;
IsMultiple(const numberType& divisor)
{
Divisor = divisor;
}
bool operator () (const numberType& element) const
{
return ((element % Divisor) == 0);
}
};
int main()
{
vector<int> vecInt{11,23,34,48,59,61};
cout << "Enter divisor(>0): ";
int divisor = 1;
cin >> divisor;
auto element = find_if(vecInt.begin(), vecInt.end(), IsMultiple<int>(divisor));
if (element != vecInt.end())
{
cout << "first find num : " << *element << endl;
}
return 0;
}
然后,在main()函数中首先声明了一个整型 vector。find_if()使用了一元谓词,这里将函数对象 IsMutilple 初始化为用户提供的除数,find_if() 对指定范围内的每个元素调用一元谓词 IsMutilple::operator( )。当 operator( )返回 true(即元素可被用户提供的除数整除)时,find_if 返回一个指向该元素的迭代器。然后将 find_if( )操作的结果与容器的 end( )进行比较,以核实是否找到了满足条件的元素。接下来使用迭代器 iElement 显示该元素的值。
3.二元函数
将两个范围相乘
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
template <typename elementType>
struct Multiply
{
elementType operator () (const elementType& elem1, const elementType& elem2) const
{
return (elem1 * elem2);
}
};
int main()
{
vector<int> vecInt1{ 0,1,2,3,4 };
vector<int> vecInt2{ 100,200,300,400,500 };
vector<int> vecRes; vecRes.resize(vecInt2.size());
//注意,vecInt1和vecInt2的元素个数应相同。否则会抛出异常
transform(vecInt1.begin(), vecInt1.end(), vecInt2.begin(), vecRes.begin(), Multiply<int>());
for (size_t i = 0; i < vecRes.size(); i++)
{
cout << vecRes[i] << endl;
}
return 0;
}
4.二元谓词
接受两个参数并返回一个布尔值的函数是二元谓词。这种函数用于诸如 std::sort( )等 STL 函数中。
使用函数对象对字符串进行不区分大小写的排序
#include <algorithm>
#include <iostream>
#include <vector>
#include <string>
using namespace std;
struct CompareStringNoCase
{
bool operator()(const string& str1, const string& str2)const
{
string str1LowerCase;
str1LowerCase.resize(str1.size());
transform(str1.begin(), str1.end(), str1LowerCase.begin(), ::tolower);
string str2LowerCase;
str2LowerCase.resize(str2.size());
transform(str2.begin(), str2.end(), str2LowerCase.begin(), ::tolower);
return str1LowerCase < str2LowerCase;
}
};
template <typename T>
void DisplayContents(const T& container)
{
for (auto element = container.cbegin(); element != container.cend(); ++element)
{
cout << *element << endl;
}
}
int main()
{
vector<string> vecStr{"jim","Jack","Sam","Anna"};
cout << "原始: " << endl;
DisplayContents(vecStr);
sort(vecStr.begin(), vecStr.end());
cout << "默认换: " << endl;
DisplayContents(vecStr);
sort(vecStr.begin(), vecStr.end(), CompareStringNoCase());
cout << "重新换: " << endl;
DisplayContents(vecStr);
return 0;
}
输出显示了 vector 在三个阶段的内容。第一次按插入顺序显示内容。第二次是在使用默认排序谓词重新排序后进行的;输出表明,jim 没有紧跟在 Jack 后面,这是因为使用 string::operator<排序时区分大小写。为确保 jim 紧跟在 Jack 后面(虽然大小写不同),最后一次显示内容前,使用了排序谓词 CompareStringNoCase<>。