Heim  >  Fragen und Antworten  >  Hauptteil

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());

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

天蓬老师天蓬老师2715 Tage vor653

Antworte allen(3)Ich werde antworten

  • PHPz

    PHPz2017-04-17 11:51:55

    上面那种是 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
    

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

    Antwort
    0
  • ringa_lee

    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的解决很简单。

    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~~

    Antwort
    0
  • 伊谢尔伦

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

    Antwort
    0
  • StornierenAntwort