您的位置:首页 > 其它

STL 之 函数对象(函数符)

2010-09-02 18:55 239 查看
函数对象

也叫做 函数符 (functor)。函数符是可以以函数方式与()结合使用的任意对象。这包括函数名、指向函数的指针和重载了()操作符的类对象(即定义了函数operator()()的类)。

例如,这样定义一个类:
class Linear
{ private:
double slope,y0;
public:
Linear (double _sl = 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);

其中y1将使用表达式0 + 1 * 12.5来计算,y2将使用表达式10.0 + 2.5 * 0.4来计算。

举例说明函数符调用

函数for_each(),它将指定的函数用于区间中的每个成员:
for_each(books.begin(), books.end(), ShowReview);

通常,第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(...)的表达式。

在上面范例中,f是指向函数的指针,而f(...)调用该函数。如果最后的for_each()参数是一个对象,则f(...)将是调用重载的()操作符的对象。

函数符概念

生成器(generator),是不用参数就可以调用的函数符。

一元函数(unary function),是用一个参数可以调用的函数符。

二元函数(binary function),是用两个参数可以调用的函数符。

(例如,提供给for_each()的函数符应当是一元函数,因为它每次用于一个容器元素。)

返回bool值的一元函数是 断言 (predicate)。

返回bool值的二元函数是 二元断言 (binary predicate)。

list模板有一个将断言作为参数的remove_if()成员,该函数将断言应用于区间中的每个元素,如果断言返回true,则删除这些元素。

例如,下面的代码删除链表scores中所有大于100的元素:
bool tooBig(int n) { return n > 100; }
list<int> scores;
...
scores.remove_if(tooBig);

现在假设又要删除另一个链表中所有大于200的值。就必须重新设计一个断言。如果能将取舍值作为第二个参数传递给tooBig(),则可以使用不同的值调用该函数,但断言又只能有一个参数。不过,如果设计一个tooBig类,则可以使用类成员而不是函数参数,来传递额外的信息:

template<class T>
class tooBig
{ private:
T cutoff;
public:
tooBig (const T & t): cutoff(t) {}
bool operator() (const T & v) { return v > cutoff; }
};

这里,一个值(V)作为函数参数传递,而第二个参数(cutoff)是由类构造函数设置的。有了该定义后,就可以将不同的tooBig对象初始化为不同的取舍值,供调用remove_if()时使用:
scores.remove_if(tooBig<int>(200));

函数符(tooBig<int>(200))是一个匿名对象,它是由构造函数调用创建的。

拓展设计

假设已经有了一个接受两个参数的模板函数:
template <class T>
bool tooBig (const T & val, const T & lim)
{ return val > lim; }

则可以使用类将它转换为单个参数的函数对象:
template <class T>
class TooBig2
{ private:
T cutoff;
public:
tooBig (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)) // same as if ( tooBig(x,100) )
...

类函数符TooBig2是一个函数适配器,使函数能够满足不同的接口。

预定义的函数符

STL定义了多个基本函数符,它们执行诸如将两个值相加、比较两个值否相等操作。

例如,考虑函数transform()。它有两个版本。第一个版本接受4个参数,前两个参数指定容器区间的迭代器,第三个参数是指定将结果复制到哪里的迭代器,最后一个参数是一个函数符,它被应用于区间中的每个元素,生成结果中的新元素:

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);

上述代码计算每个元素的平方根,并将结果发送到输出流。目标迭代器也可以位于原始区间中。

例如将上述范例中的out替换为gr8.begin()后,新值将覆盖原来的值。

第二个版本使用一个接受两个参数的函数,并将该函数用于两个区间中的元素。它用另一个参数(即第3个)标识第二个区间的起始位置。
例如,如果m8是另一个vector<double>对象,mean(double,double)返回两个值的平均值,则下面的代码将输出来自gr8和m8的值的平均值:
transform(gr8.begin(),gr8.end(),m8.begin(),out,mean);

对于所有内置的算术操作符、关系操作符和逻辑操作符,STL都提供了等价的函数符。

下图列出了这些函数符的名称。它们可以用于处理C++内置类型或任何用户定义类型(如果重载了相应的操作符)。



例如使用plus<>()。它在头文件functional中定义。
#include <functional>
...
plus<double> add; // create a plus<double> object
double y = add(2.2,3.4); // using plus<double>::operator()()

将它用作函数符:
transform(gr8.begin(),gr8.end(),m8.begin(),out,plus<double>());

这里没有创建命名的对象,而是用plus<double>构造函数构造了一个函数符,括号表示调用默认的构造函数。

自适应函数符和函数适配器

上图列出的预定义函数符都是自适应的。STL有5个相关的概念:
自适应生成器 (adaptable generator)
自适应一元函数 (adaptable unary function)
自适应二元函数 (adaptable binary function)
自适应断言 (adaptable predicate)
自适应二元断言 (adaptable binary predicate)

使函数符成为自适应的原因是,它携带了标识参数类型和返回值类型的typedef成员。这些成员分别是result_type、first_argument_type和second_argument_type。

例如,plus<int>对象的返回值类型被标识为plus<int>::result_type,这是int的typedef。

函数符自适应性的意义在于:函数适配器对象可以使用函数对象,并认为存在这些typedef成员。
例如,接受一个自适应函数符参数的函数可以使用result_type成员来声明一个与函数的返回值类型匹配的变量。

STL提供了使用这些工具的函数适配器类。例如,假设要将矢量gr8的每个元素都增加2.5倍,则需要使用接受一个一元函数参数的transform()版本。multiplies()函数符可以执行乘法,但它是二元函数。因此需要一个函数适配器,将接受两个参数的函数符转换为接受一个参数的函数符。STL使用binderlst和binder2nd类自动完成这一过程,它们将自适应二元函数转换为自适应一元函数。

假设有一个自适应二元函数对象f2(),则可以创建一个binderlst对象,该对象与一个将被用作f2()的第一个参数的特定值(val)相关联:
binderlst (f2,val) f1;

使用单个参数调用f1(x)时,返回的值与将val作为第一参数、将f1()的参数作为第二参数调用f2()返回的值相同。

STL提供了函数bindlst(),以简化binderlst类的使用。可以用其提供用于构建binderlst对象的函数名称和值,它将返回一个这种类型的对象。
例如,要将二元函数multiplies()转换为将参数乘以2.5的一元函数,则可以:
bindlst (multiplies<double>(), 2.5)

因此,将gr8中的每个元素与2.5相乘,并显示结果的代码如下:
transform(gr8.begin(), gr8.end(), out,
bindlst (multiplies<double>(), 2.5) );

binder2nd类与此类似,只是将常数赋给第二个参数,而不是第一个参数。它有一个名为bind2nd的助手函数,该函数的工作方式类似于bindlst。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: