Rumah > Soal Jawab > teks badan
看有的代码可以用函数实现,却用类来封装.
具体例子是这样:
比如STL的list容器,sort的函数可以自定义
一般这样处理:
// comparison, not case sensitive.
bool compare_nocase (string first, string second)
{
unsigned int i=0;
while ( (i<first.length()) && (i<second.length()) )
{
if (tolower(first[i])<tolower(second[i])) return true;
++i;
}
if (first.length()<second.length()) return true;
else return false;
}
mylist.sort(compare_nocase);
这个是c++参考手册的例子,项目中我看到好多地方这么用了
struct mylistSort {
bool operator() (string first, string second) const {
//todo
}
};
mylist.sort(mylistSort());
这样有很明显的好处 还是单纯的风格问题,完全等价?
PHPz2017-04-17 11:51:55
上面那种是 functions, 下面这种叫做 functors. ( 我姑且翻译成函子 )
两者最本质的区别在于,上面仅仅是一个过程;而下面,却可以包含状态。后者,可以轻松实现闭包。
在 C++11 里面,后者直接演化为 lambda 了。
我就用你提到的 sort
来举一个小例子:
cpp
bool myfunction (int i,int j) { return (i<j); } struct myclass { bool operator() (int i,int j) { return (i<j);} } myobject; std::vector<int> myvector{32,71,12,45,26,80,53,33}; std::sort (myvector.begin(), myvector.end(), myfunction); std::sort (myvector.begin(), myvector.end(), myobject);
简化了你的例子,我们来关注本质区别。看起来,好像等效对不?
那么现在需求变了,排序的时候,我只希望排值大于 40
的元素,请问咋整,你说,只好把这个 40
写到函数里了。那如果我说这个 40
是来自用户输入呢?也可能是 50
或是 60
,请问怎么办?
此时,function 好像有点没有用武之地了。但我们的 functor 却依然可以大显身手。
cpp
struct myclass { int flag; myclass(int i) : flag(i) { } bool operator() (int i,int j) { return ((flag < i || flag < j) && i < j);} }; std::vector<int> myvector{32,71,12,45,26,80,53,33}; myclass myobject(40); std::sort (myvector.begin(), myvector.end(), myobject); // output: 32 12 26 33 45 53 71 80
例子可能有点怪。。但你明白这意思了么?
ringa_lee2017-04-17 11:51:55
这个struct其实是functor,国内译成仿函数,它的好处是可以保存状态。
我举个例子,你现在用compare_nocase的函数指针作为参数,假如突然又有一个地方要求你比较字符串,但此时要求你忽略首字母,从第二个字符串开始比较,那么你应该怎么做?
1.要么你重新写一个compare_nocase2函数,但会造成大量重复代码。
2.要么你弄个int start变量,然后放在compare_nocase的外面,在执行我刚才说的这个需求时候,先改变start=2,执行完以后再把全局变量改回去。
可以看到,都不优雅。
或许你想到了把compare_nocase写到一个类里,但这必须要是static method。
而functor的解决很简单。
cpp
struct mylistSort { int start; mylistSort(int p) { start = p; } bool operator() (string first, string second) const { int i=start-1; while ( (i<first.length()) && (i<second.length()) ) { if (tolower(first[i])<tolower(second[i])) return true; ++i; } if (first.length()<second.length()) return true; else return false; } };
这样你从首字符开始比较就可以mylist.sort(mylistSort(1));而当你需要忽略首字符,从第二个字母开始比较的时候就可以mylist.sort(mylistSort(2));
这样就轻松避免了全局变量的状态管理。
事实上functor还有很多其他好处,特别是配合template来写,会发挥很大作用!
对了,C++11的话,可以这样写
cpp
mylist.sort([](string first, string second) { // 比较逻辑 });
C++14的话还能把它改成auto~~
伊谢尔伦2017-04-17 11:51:55
myListSort这种用法称为“函数对象”或“仿函数”。从名称可以看出来,myListSort是一个类(或结构),而非函数,但是它的使用方法又颇似函数,即可以用调用函数的方式“调用”它,原因就在于它重载了调用操作符“()”。
有什么好处呢?举个经典例子吧(C++ Primer上给出的):假如你想统计一篇文章中有多少单词的长度在6以上,那么肯定需要定义一个函数,用来确定一个单词的长度是否在6以上,这个函数如下:
bool GT6(const string &s) {
return s.size() >= 6;
}
然后把该函数传给count_if,用以统计vector words中长度在6以上的单词数量:
vector<string>::size_type wc = count_if(words.begin(), words.end(), GT6);
这个需求很容易实现,但是,问题在于,如果像上面一样把6写死在代码中,那么现在新来了一个需求,要统计长度在5以上的单词数量,或者长度在10以上的单词数量,就意味着我们必须重新实现类似的函数GT5和GT10。如果有更多类似需求呢?是不是要一个函数一个函数都实现了呢?
用函数对象可以帮我们省去这些麻烦。因为函数对象是类类型,可以有自己的数据成员。定义一个数据成员bound,初始化函数对象的时候就顺便为bound赋值了。假如想要统计长度在5以上的单词数量,则bound=5;6以上,则bound=6,以此类推,具体代码如下:
class GT_cls {
public:
GT_cls(size_t val = 0) : bound(val) { }
bool operator() (const string &s) {
return s.size() >= bound;
}
private:
std::string::size_type bound;
};
现在,假如你想统计words中长度在5以上的单词数量,代码如下:
vector<string>::size_type wc = count_if(words.begin(), words.end(), GT_cls(5));
同理,想要统计words中长度在6以上的单词数量,代码如下:
vector<string>::size_type wc = count_if(words.begin(), words.end(), GT_cls(6));
显然,用函数对象的好处就在于它可以拥有数据成员,从而让这个“仿函数”更加灵活易用。
再举一例,比如你想定义一个最近邻函数。何为“最近”?这就意味着我们必须定义一种距离度量。假如我们使用的是加权欧式距离作为度量。在C语言中,可以把距离度量作为函数指针传入最近邻函数,在C++中,我们有更加方便的函数对象!于是,我们可以把“权值”放在函数对象中,作为它的数据成员。根据不同的要求,用不同的权值初始化函数对象,这样就能让最近邻函数产生不同的结果!