第二十一篇:最佳谓词函数 --- 函数对象
前言
学习C++标准算法时,我们知道带有_if的算法都带有一个谓词函数。此前我们直接使用函数名充当谓词函数,这样做有很大的局限性( 下文会做分析 )。为此本文将介绍C++中一个新的技术:函数对象。用它来充当谓词函数就能解决这个问题。
什么是函数对象?
函数对象就是重载了函数调用操作符的对象。我们知道,函数调用规则已经非常固定了,那么为何要重载函数调用操作符呢?因为重载后,这个函数调用操作符将不再用作一般意义的函数调用功能。不能调用各种函数的对象那还叫对象吗?从语义上来讲,它不符合对象的设定原则" 对现实事物的虚拟 ",因此我们叫它函数对象以区别于其他对象。那这样的对象有什么用?这个问题稍后讨论,下面来看一个简单的用作求绝对值的函数对象实现:
1 #include <iostream> 2 #include <string> 3 4 using namespace std; 5 6 /* 7 * 1. 函数对象的作用是虚拟函数,因此对它的命名也按照函数命名规范。 8 * 2. 重载了函数调用操作符,构造函数也不能用了。当然函数对象只需要()这一个" 函数 ",无需定义其他任何 9 * 函数,包括构造函数。 10 */ 11 class absInt { 12 public: 13 int operator() (int val) { 14 return val < 0? -val : val; 15 } 16 }; 17 18 19 int main() 20 { 21 absInt absObj; 22 int val1 = -1; 23 int val2 = 1; 24 25 /* 26 * absObj本质是对象,但它虚拟成了一个返回绝对值的函数。 27 */ 28 cout << absObj(val1) << endl; 29 cout << absObj(val2) << endl; 30 31 return 0; 32 }
运行结果:
仔细阅读代码便能发现,该程序将对象虚拟化为一个函数来用。
为何要定义函数对象?
要对象就用对象,函数就用函数,为何费那么大劲把对象整成函数?相信有人会这么问。别急,我们先来回顾一个使用标准算法的例子,这个代码返回容器中长度超过6的单词:
1 #include <iostream> 2 #include <algorithm> 3 #include <vector> 4 #include <string> 5 6 using namespace std; 7 8 bool GT6 (const string &s) { 9 return s.size() >= 6; 10 } 11 12 int main() 13 { 14 /* 15 * 构建测试容器 16 */ 17 vector<string> words; 18 words.push_back("China"); 19 words.push_back("America"); 20 words.push_back("England"); 21 words.push_back("Japan"); 22 23 // 获取容器中长度大于6的字符串的个数 24 vector<string> :: size_type wc = count_if(words.begin(), words.end(), GT6); 25 26 // 打印结果 27 cout << wc << endl; 28 29 return 0; 30 }
这段代码没有问题,正常地返回了结果:
但是,如果我改变注意了,又想获得长度大于5的字符串,那么我该怎么做?修改函数调用操作符重载函数是可行的办法,但是当函数复杂起来的时候,这会是一个很麻烦的工作。好在改用函数对象充当谓词函数后就能解决这个问题了。
使用函数对象的改进版本
1 #include <iostream> 2 #include <algorithm> 3 #include <vector> 4 #include <string> 5 6 using namespace std; 7 8 class GT_cls { 9 public: 10 GT_cls (string::size_type val=0) : bound(val) {}; 11 bool operator() (const string &s) { 12 return s.size() >= bound; 13 } 14 15 private: 16 string::size_type bound; 17 }; 18 19 int main() 20 { 21 /* 22 * 构建测试容器 23 */ 24 vector<string> words; 25 words.push_back("China"); 26 words.push_back("America"); 27 words.push_back("England"); 28 words.push_back("Japan"); 29 30 /* 31 * 获取容器中长度大于6的字符串的个数 32 * 如要取长度大于5的,只要将下面语句中的6改成5即可,不用修改重载函数。 33 */ 34 vector<string> :: size_type wc = count_if(words.begin(), words.end(), GT_cls(5)); 35 36 // 打印结果 37 cout << wc << endl; 38 39 return 0; 40 }
运行结果:
如果要找长度大于8的单词个数?同理,将函数对象参数改成8即可。
说明
请细细体会上面的代码,分析其执行过程。