前一小节《重载操作符》http://blog.csdn.net/thefutureisour/article/details/7722076中,我们讨论了大量对操作符的重载,而这一小节,我们将讨论一种特殊的重载问题:对于调用操作符“()”的重载。把它单独拿出来讨论的原因是虽然它的定义方法与前面的重载基本相同,但是它的应用场合却与一般的重载截然不同。先看一个简单的例子:
struct absInt
{
int operator()(int val)
{
return val < 0 ? -val : val;
}
};
这定义了一个类,为这个类重载了调用操作符,那么在主函数中,我们可以这样使用它:
int i = -42;
absInt absObj;
unsigned int ui = absObj(i);
cout<<ui<<endl;
虽然absObj(i)看起来很像是调用了一个函数,但他的的确确是一个对象,而且是重载了调用操作符的对象,所以这里的()并不是调用某个函数,而是使用这个类自己重载的操作符。显然的,对用操作符应该声明为成员的共有函数,而且,我们可以通过形参的类型,数量,以及函数返回值来重载多个调用操作符。
对于定义了调用操作符的类,它的对象成为函数对象。
乍看之下,这个例子似乎只不过是玩了一个花活,没什么实用价值,因为我们总是可以定义了一个具体的函数来完成相同的功能。其实不然,在有的场合,还非得用它不可。
还记得我们前面《泛型算法库简介》http://blog.csdn.net/thefutureisour/article/details/7694396这一小节中,有一个判断单词的长度是否大于6的程序:
bool GT6(const string &s)
{
return s.size() >= 6;
}
using namespace std;
int main()
{
string s1[10] = {"the","quick","yellow","fox","jumps","over","the","slow","red","turtle"};
vector<string> svec(s1,s1+10);
for(vector<string>::iterator iter = svec.begin();iter != svec.end();++iter)
cout<<*iter<<"\t";
vector<string>::size_type wc = count_if(svec.begin(),svec.end(),GT6);
cout<<wc<<endl;
return 0;
}
程序的关键是调用count_if函数来对容器做判断,这个函数接受一个谓词函数,这个谓词函数只有一个形参(类型与容器的中元素的类型相同),且返回值为bool。对于容器中的每个元素,利用谓词函数来判断容器的元素是否满足谓词,count_if的返回值为容器中满足谓词函数的个数。
但这个程序有一个小问题,就是由于count_if只能接受一个带一个形参,这个形参还必须是容器的元素的类型的谓词函数,所以“大于6”这个标准是写在代码中的,如果我们想修改判断标准:改成大于5,那么就必须动谓词函数程序了。这样用起来很不方便,我们希望能把这个标准变成一个参数传递进来,这时我们的函数对象就派上用场了。
class GT_cls
{
public:
GT_cls(size_t val = 0):bound(val){}
bool operator()(const string &s)
{
return s.size() >= bound;
}
private:
string::size_type bound;
};
我们为这个类提供了一个数据成员,并提供了一个带参数的构造函数!利用这个参数来控制比较的对象。于是我们就可以这样使用:
GT_cls cls5(5);
wc = count_if(svec.begin(),svec.end(),cls5);
或者
wc = count_if(svec.begin(),svec.end(),GT_cls(5));
二者的区别在于可能第一种用法更像是“函数对象”的本意:定义了操作符的类的对象,而第二种用法则更加简洁。
如果我们愿意,甚至可以判统计长度在1到10之内的单词数:
for(size_t i = 0; i != 11;++i)
cout<<count_if(svec.begin(),svec.end(),GT_cls(i))<<" words "<<i<<" characters or longer"<<endl;
可以发现,由于函数对象的使用,算法库中接受谓词的算法一下子就灵活了起来。在举几个例子,比如查找容器中第一个大于某个值的元素,或者替换容器中所有值为某个数为指定值:
#define MAX 5
class GT_clc
{
public:
//构造函数接受一个参数
GT_clc(int nm):bound(nm){}
//重载调用操作符
bool operator()(const int &val)
{
return val > bound;
}
private:
int bound;
};
class Eqal
{
public:
Eqal(int val):bound(val){}
bool operator()(const int nm)
{
return nm == bound;
}
private:
int bound;
};
int main()
{
int arr[MAX] = {1,2,5,1,4};
vector<int> ivec(arr,arr+MAX);
vector<int>::iterator iter;
iter = find_if(ivec.begin(),ivec.end(),GT_clc(2));
if(iter != ivec.end())
cout<<"the first number great than 2 is "<<*iter<<endl;
else
cout<<"no number great than 2! "<<endl;
replace_if(ivec.begin(),ivec.end(),Eqal(1),100);
for(vector<int>::iterator it = ivec.begin();it != ivec.end();++it)
cout<<*it<<"\t";
return 0;
}
因为函数对象与算法库的结合是如此的方便,所以标准库本身就定义了一些对象类型方便使用者使用,它们定义在functional头文件中。这些函数对象都是模板,举一个没有用的例子:
plus<int> intAdd;
int sum = intAdd(10,20);
cout<<sum<<endl;
这里使用了保准库的plus类,定义了这个类的一个对象intAdd,此时,标准库已经为你重载好调用操作符“()”了。你就可以直接调用intAdd(10,20)了。
我们还是看一个有用的例子吧,sort的排序结果是从小到大的,如果我们希望从大到小排序,就需要传递一个谓词函数:
sort(svec.begin(),svec.end(),greater<string>());
其中greater<string>就是标准库定义的函数对象。
但是这里有一点小问题,就是标准库函数对象规定死了它们的操作数的个数和意义,是得有时候使用起来不是很灵活。因此,标准库又定义了函数适配器来扩展或者特化这些函数对象。适配器分为两种:
1.绑定器:通过将一个操作数与函数对象绑定是得二元函数对象变为一员函数对象。具体的说,也分为两种bind1st和bind2nd。它们接受一个函数与一个参数值,将参数值绑定到函数的第一个或者第二个形参上,举个例子:
如果想计算容器中小于或者等于3的元素的个数,可以:
count_if(ivec.begin(),ivec.end(),bind2nd(less_equal<int>(),3));
这里我们使用了函数对象less_equal<int>(),由于这个函数对象二元的,所以我们使用bind2nd将函数对象的第二个操作数与3绑定。
在举一个例子:如果想输出容器中所有大于1024的数,可以:
int main()
{
int arr[MAX] = {0,500,1000,1500,2000};
vector<int> ivec(arr,arr+MAX);
vector<int>::iterator iter = ivec.begin();
while((iter =find_if(iter,ivec.end(),bind2nd(greater<int>(),1024))) != ivec.end())
{
cout<<*iter<<endl;
++iter;
}
return 0;
}
2.求反器:作用是将谓词函数对象的真值求反。他也分为两种:not1和not2,分别作用于一元函数对象和二元函数对象。
比如对前面的小于3求反:
val = count_if(ivec.begin(),ivec.end(),not1(bind2nd(less_equal<int>(),3)));