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。
也叫做 函数符 (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。
相关文章推荐
- 【编码随笔】STL中系统常见自带函数对象小结(不断更新)
- 第十五周项目 程序填空(1) 运用STL函数对象
- STL函数对象和Lambda表达式
- STL之函数对象和谓词
- 对STL中函数对象的认识
- 从零开始学C++之STL(八):函数对象、 函数对象与容器、函数对象与算法
- C++ STL 学习笔记 函数对象
- C++STL之函数对象及谓词
- STL学习之函数对象
- STL 函数对象
- STL_iterator迭代器(3)——函数和函数对象
- STL共性机制和函数对象的概念
- STL中的函数对象(一)
- STL运用的C++技术(6)——函数对象
- 从零开始学C++之STL(八):函数对象、 函数对象与容器、函数对象与算法
- STL学习笔记(六) 函数对象
- 从零开始学C++之STL(八):函数对象、 函数对象与容器、函数对象与算法
- STL中伪函数、函数对象(functor)初步理解(上)
- c++ stl Function Objects 函数对象
- C++ 11 - STL - 函数对象(Function Object) (上)