看有的代码可以用函数实现,却用类来封装.
具体例子是这样:
比如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++中,我們有更方便的函數物件!於是,我們可以把「權值」放在函數物件中,作為它的資料成員。根據不同的要求,用不同的權值初始化函數對象,這樣就能讓最近鄰函數產生不同的結果!