函数对象也叫做函数符(functor)。
函数符是可以以函数方式和( )结合使用的任意对象。
包括函数名,指向函数的指针,重载了()运算符的类对象。
可以这样定义一个类:
class Linear
{
private:
double slope;
double y0;
public:
Linear(double s1_=1, double y_ = 0):slope(s1_),y0(y_) { }
double operator() (double x) {return y0 + slope * x;}
}
重载了()运算符后,就可像使用函数那样使用Linear对象;
Linear f1;
Linear f2(2.5, 10.0);
double y1 = f1(12.5); // right-hand side is f1.operator( )(12.5)
double y2 = f2(0.4);
接下来讨论另一个例子:
for_each(books.begin(), books.end(), ShowReview);
通常,第3个参数可以是常规函数,也可以是函数符。
现在有个问题,如何声明第3个参数?
不能把它声明为函数指针,因为函数指针指定了参数类型。
由于容器是可以包含任意类型的,所以预先也无法知道应使用哪种类型。
STL是通过模板解决该问题。
for_each的原型看上去就像这样:
template <class InputIterator, class Function>
Function for_each(InputIterator first, InputIterator last, Function f);
ShowReview的原型如下:
void ShowReview(const Review &);
这样标识符ShowReview的类型将为void(*)(const Review &)。这也是赋给模板参数Function的类型。
Function还可以表示具有重载()运算符的类类型。
最终,for_each()代码将具有一个使用f()的表达式。
在ShowReview示例中,f是指向函数的指针,而f()调用函数。
如果最后的for_each()参数是一个对象,则f()将是调用其重载()运算符的对象。
====================================================
一、函数符的概念
生成器是不用参数就可以调用的函数符;
一元函数是用一个参数就可以调用的函数符;
二元函数是用两个参数就可以调用的函数符;
返回bool值的一元函数是谓词;
返回bool值的二元函数是二元谓词;
接下来有一个例子演示类函数符适用的地方。
1 //functor.cpp -- using a functor 2 3 #include <iostream> 4 #include <list> 5 #include <iterator> 6 #include <algorithm> 7 8 template<class T> 9 class TooBig 10 { 11 12 }; 13 14 void outint(int n) {std::cout<< n << " ";} 15 16 int main() 17 { 18 using std::list; 19 using std::cout; 20 using std::endl; 21 22 TooBig<int> f100(100); 23 int vals[10] = {50, 100, 90, 180, 60, 210, 415, 88, 188, 201}; 24 list<int> yadayada = {vals, vals+10}; 25 list<int> etcetera = {vals, vals+10}; 26 27 cout<<"Original lists:\n"; 28 for_each(yadayada.begin(), yadayada.end(), outint); 29 cout<<endl; 30 for_each(etcetera.begin(), etcetera.end(), outint); 31 cout<<endl; 32 yadayada.remove_if(f100); 33 etcetera.remove_if(TooBig<int>(200)); 34 cout<<"Trimmed lists:\n"; 35 for_each(yadayada.begin(), yadayada.end(), outint); 36 cout<<endl; 37 for_each(etcetera.begin(), etcetera.end(), outint); 38 cout<<endl; 39 return 0; 40 }
再来一个例子:
假设已经有了一个接受两个参数的模板函数:
template <class T>
bool tooBig(const T & val, const T & lim)
{
return val >lim;
}
则可以使用将它转换为单个参数的函数对象:
template<class T>
class TooBig2
{
private:
T cutoff;
public:
TooBig2(const T & t):cutoff(t) { }
bool operator() (const T & v) {return tooBig<T>(v, cutoff);}
};
即可以这样做:
TooBig2<int> tB100(100);
int x;
cin>>x;
if(tB100(x))
...
类函数符TooBig2是一个函数适配器,使函数能够满足不同的接口。
====================================================
二、预定义的函数符
STL定义了多个基本函数符,它们执行诸如将两个值相加、比较两个值是否相等操作。
提供这些函数对象是为了支持将函数作为参数的STL函数。
例如,考虑函数transform(),它有两个版本。
第一个版本使用接受4个参数的函数:
前两个参数是指定容器区间的迭代器;
第3个参数是指定将结果复制到哪里的迭代器;
最后一个参数是一个函数符;
const int LIM =5;
double arr1[LIM] = {36, 39, 42, 45, 48};
vector<double> gr8(arr1, arr1+LIM);
ostream_iterator<double, char> out(cout, " ");
transform(gr8.begin(), gr8.end(), out, sqrt);
第二个版本使用接受5个参数的函数:
第3个参数标识第二个区间的起始位置;
如果m8是另一个vector<double>对象,mean(double, double)返回两个值的平均值,
则下面的代码将输出来自gr8和m8的值的平均值:
transform(gr8.begin(), gr8.end(), m8.begin(), out, mean);
现在假设要将两个数组相加,不能将+作为参数,因为对于类型double来说,+是内置的运算符,而不是函数。
可以定义一个将两个数相加的函数,然后使用它:
double add(double x, double y) {return x+y;}
...
transform(gr8.begin(), gr8.end(), m8.begin(), out, add);
但是这样做的话,就必须为每种类型单独定义一个add函数。更好的办法是定义一个模板(除非STL已经有一个模板了);
头文件functional定义了多个模板类函数对象,其中包括plus<>()。
因此可以用plus<>类完成常规的相加运算;
#include <functional>
...
plus<double> add;
double y =add(2.2, 3.4);
transform(gr8.begin(), gr8.end(), m8.begin(), out, plus<double>); //它使得将函数对象作为参数非常方便。
对于所有内置的运算符,STL都提供了等价的函数符。
====================================================
三、自适应函数符和函数适配器
自适应的英文名叫adapter,其实也可以理解成适配器。函数符适配器。
适配的目的是满足将原先不满足要求的函数符,适配成满足要求的。就像是插座转换器一样。
例如:
transform只能接受一元函数参数。
multiplies()函数符可以执行乘法运算符, 但是它是二元函数。
因此需要函数适配器,将接受2个参数的函数符转换为接受1个参数的函数符。
前面的示例TooBig2提供了这种方法。即用定义类函数符的方法。
那么是否能将这个过程自动化呢?是可以的。
就叫做自动化适配。
STL使用binder1st和binder2nd类自动完成这一过程。
它们将自适应二元函数转换为自适应一元函数。
而且前提是被适配的函数符必须是自适应的。自动化适配。
接下来看一个例子:
binder1st(f2, val) f1;
这样的话f1(x)等价于f2(val,x);
f2被适配,当然f2必须是一个自适应函数时,才能实现。
这样看上去还是有点麻烦,STL提供了函数bind1st(),以简化binder1st类的使用。
bind1st(multiples<double>(), 2.5);
将gr8中的每个元素与2.5相乘,并显示结果的代码如下:
transform(gr8.begin(), gr8.end(), out, bind1st(multiples<double>(), 2.5));
1 #include <iostream> 2 #include <vector> 3 #include <iterator> 4 #include <algorithm> 5 #include <functional> 6 7 void Show(double); 8 const int LIM = 6; 9 10 11 int main() 12 { 13 using namespace std; 14 double arr1[LIM] = {28, 29, 30, 35, 38, 59}; 15 double arr2[LIM] = {63, 65, 69, 75, 80, 99}; 16 vector<double> gr8(arr1, arr1+LIM); 17 vector<double> m8(arr2, arr2+LIM); 18 cout.setf(ios_base::fixed); 19 cout.precision(1); 20 cout<<"gr8:\t"; 21 for_each(gr8.begin(), gr8.end(), Show); 22 cout<<endl; 23 cout<<"m8: \t"; 24 for_each(m8.begin(), m8.end(), Show); 25 cout<<endl; 26 27 vector<double> sum(LIM); 28 transform(gr8.begin(), gr8.end(), m8.begin(), sum.begin(), plus<double>()); 29 cout<<"sum: \t"; 30 for_each(sum.begin(), sum.end(), Show); 31 cout<<endl; 32 33 vector<double> prod(LIM); 34 transform(gr8.begin(), gr8.end(), m8.begin(), prod.begin(), bind1st(multiplies<double>(), 2.5)); 35 cout<<"prod: \t"; 36 for_each(prod.begin(), prod.end(), Show); 37 cout<<endl; 38 return 0; 39 } 40 41 void Show(double v) 42 { 43 std::cout.width(6); 44 std::cout<< v <<' '; 45 }