首页 > C++用类封装函数有什么好处么?

C++用类封装函数有什么好处么?

看有的代码可以用函数实现,却用类来封装.

具体例子是这样:
比如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());

这样有很明显的好处 还是单纯的风格问题,完全等价?


这个struct其实是functor,国内译成仿函数,它的好处是可以保存状态。

我举个例子,你现在用compare_nocase的函数指针作为参数,假如突然又有一个地方要求你比较字符串,但此时要求你忽略首字母,从第二个字符串开始比较,那么你应该怎么做?

1.要么你重新写一个compare_nocase2函数,但会造成大量重复代码。
2.要么你弄个int start变量,然后放在compare_nocase的外面,在执行我刚才说的这个需求时候,先改变start=2,执行完以后再把全局变量改回去。

可以看到,都不优雅。
或许你想到了把compare_nocase写到一个类里,但这必须要是static method。

而functor的解决很简单。

cppstruct 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的话,可以这样写

cppmylist.sort([](string first, string second) {
          // 比较逻辑
           });

C++14的话还能把它改成auto~~


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++中,我们有更加方便的函数对象!于是,我们可以把“权值”放在函数对象中,作为它的数据成员。根据不同的要求,用不同的权值初始化函数对象,这样就能让最近邻函数产生不同的结果!


上面那种是 functions, 下面这种叫做 functors. ( 我姑且翻译成函子 )

两者最本质的区别在于,上面仅仅是一个过程;而下面,却可以包含状态。后者,可以轻松实现闭包。

在 C++11 里面,后者直接演化为 lambda 了。


我就用你提到的 sort 来举一个小例子:

cppbool 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 却依然可以大显身手。

cppstruct 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

例子可能有点怪。。但你明白这意思了么?

【热门文章】
【热门文章】