首頁  >  問答  >  主體

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

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

天蓬老师天蓬老师2765 天前688

全部回覆(3)我來回復

  • 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
    

    例子可能有點怪。 。但你明白這意思了麼?

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

    回覆
    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++中,我們有更方便的函數物件!於是,我們可以把「權值」放在函數物件中,作為它的資料成員。根據不同的要求,用不同的權值初始化函數對象,這樣就能讓最近鄰函數產生不同的結果!

    回覆
    0
  • 取消回覆