27-函数符概念
16.5.1 函数符概念
正如STL定义了容器和迭代器的概念一样,它也定义了函数符概念。
- 生成器(generator)是不用参数就可以调用的函数符。
- 一元函数(unary function)是用一个参数可以调用的函数符。
- 二元函数(binary function)是用两个参数可以调用的函数符。
例如,提供给for_each()的函数符应当是一元函数,因为它每次用于一个容器元素。
当然,这些概念都有相应的改进版:
- 返回bool值的一元函数是谓词(predicate);
- 返回bool值的二元函数是二元谓词(binary predicate)。
一些STL函数需要谓词参数或二元谓词参数。例如,程序清单16.9使用了sort()的这样一个版本,即将二元谓词作为其第3个参数:
bool WorseThan(const Review & r1, const Review & r2);
...
sort(books.begin(), books.end(), WorseThan);
list模板有一个将谓词作为参数的remove_if()成员,该函数将谓词应用于区间中的每个元素,如果谓词返回true,则删除这些元素。例如,下面的代码删除链表three中所有大于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()时使用。程序清单16.15演示了这种技术。
程序清单16.15 functor.cpp
// functor.cpp -- using a functor
#include <iostream>
#include <list>
#include <iterator>
#include <algorithm>
template<class T> // functor class defines operator()()
class TooBig
{
private:
T cutoff;
public:
TooBig(const T & t) : cutoff(t) {}
bool operator()(const T & v) { return v > cutoff; }
};
void outint(int n) {std::cout << n << " ";}
int main()
{
using std::list;
using std::cout;
using std::endl;
TooBig<int> f100(100); // limit = 100
int vals[10] = {50, 100, 90, 180, 60, 210, 415, 88, 188, 201};
list<int> yadayada(vals, vals + 10); // range constructor
list<int> etcetera(vals, vals + 10);
// C++11 can use the following instead
// list<int> yadayada = {50, 100, 90, 180, 60, 210, 415, 88, 188, 201};
// list<int> etcetera {50, 100, 90, 180, 60, 210, 415, 88, 188, 201};
cout << "Original lists:\n";
for_each(yadayada.begin(), yadayada.end(), outint);
cout << endl;
for_each(etcetera.begin(), etcetera.end(), outint);
cout << endl;
yadayada.remove_if(f100); // use a named function object
etcetera.remove_if(TooBig<int>(200)); // construct a function object
cout <<"Trimmed lists:\n";
for_each(yadayada.begin(), yadayada.end(), outint);
cout << endl;
for_each(etcetera.begin(), etcetera.end(), outint);
cout << endl;
return 0;
}
一个函数符(f100)是一个声明的对象,而另一个函数符(TooBig
Original lists:
50 100 90 180 60 210 415 88 188 201
50 100 90 180 60 210 415 88 188 201
Trimmed lists:
50 100 90 60 88
50 100 90 180 60 88 188
假设已经有了一个接受两个参数的模板函数:
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)) // same as if (tooBig(x,100))
...
因此,调用tB100(x)相当于调用tooBig(x, 100),但两个参数的函数被转换为单参数的函数对象,其中第二个参数被用于构建函数对象。简而言之,类函数符TooBig2是一个函数适配器,使函数能够满足不同的接口。
在该程序清单中,可使用C++11的初始化列表功能来简化初始化。为此,可将如下代码:
int vals[10] = {50, 100, 90, 180, 60, 210, 415, 88, 188, 201};
list<int> yadayada(vals, vals + 10); // range constructor
list<int> etcetera(vals, vals + 10);
替换为下述代码:
list<int> yadayada = {50, 100, 90, 180, 60, 210, 415, 88, 188, 201};
list<int> etcetera {50, 100, 90, 180, 60, 210, 415, 88, 188, 201};