C++用类封装函数有什么好处么?
天蓬老师
天蓬老师 2017-04-17 11:49:55
0
3
683

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

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

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

天蓬老师
天蓬老师

欢迎选择我的课程,让我们一起见证您的进步~~

全部回覆(3)
PHPzhong

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

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

左手右手慢动作

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

熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!