什麼是仿函數?
轉自://blog.csdn.net/K346K346/article/details/82818801
1.為什麼要有仿函數
我們先從一個非常簡單的問題入手,來了解為什麼要有仿函數。假設我們現在有一個數組,數組中存有任意數量的數字,我們希望能夠統計出這個數組中大於 10 的數字的數量,你的代碼很可能是這樣的:
#include <iostream> using namespace std; int RecallFunc(int *start, int *end, bool (*pf)(int)) { int count=0; for(int *i=start;i!=end+1;i++) { count = pf(*i) ? count+1 : count; } return count; } bool IsGreaterThanTen(int num) { return num>10 ? true : false; } int main() { int a[5] = {10,100,11,5,19}; int result = RecallFunc(a,a+4,IsGreaterThanTen); cout<<result<<endl; return 0; }
RecallFunc() 函數的第三個參數是一個函數指針,用於外部調用,而 IsGreaterThanTen() 函數通常也是外部已經定義好的,它只接受一個參數的函數。如果此時希望將判定的閾值也作為一個變量傳入,變為如下函數就不可行了:
bool IsGreaterThanThreshold(int num, int threshold) { return num>threshold ? true : false; }
雖然這個函數看起來比前面一個版本更具有一般性,但是它不能滿足已經定義好的函數指針參數的要求,因為函數指針參數的類型是bool (*)(int),與函數bool IsGreaterThanThreshold(int num, int threshold)的類型不相符。如果一定要完成這個任務,按照以往的經驗,我們可以考慮如下可能途徑:
(1)閾值作為函數的局部變量。局部變量不能在函數調用中傳遞,故不可行;
(2)函數傳參。這種方法我們已經討論過了,多個參數不適用於已定義好的 RecallFunc() 函數。
(3)全局變量。我們可以將閾值設置成一個全局變量。這種方法雖然可行,但是不優雅,且非常容易引入 Bug,比如全局變量容易同名,造成命名空間污染。
那麼有什麼好的處理方法呢?仿函數應運而生。
2.仿函數的定義
仿函數(Functor)又稱為函數對象(Function Object)是一個能行使函數功能的類。仿函數的語法幾乎和我們普通的函數調用一樣,不過作為仿函數的類,都必須重載 operator() 運算符。因為調用仿函數,實際上就是通過類對象調用重載後的 operator() 運算符。
如果編程者要將某種「操作」當做算法的參數,一般有兩種方法:
(1)一個辦法就是先將該「操作」設計為一個函數,再將函數指針當做算法的一個參數。上面的實例就是該做法;
(2)將該「操作」設計為一個仿函數(就語言層面而言是個 class),再以該仿函數產生一個對象,並以此對象作為算法的一個參數。
很明顯第二種方法會更優秀,因為第一種方法擴展性較差,當函數參數有所變化,則無法兼容舊的代碼,具體在第一小節已經闡述。正如上面的例子,在我們寫代碼時有時會發現有些功能代碼,會不斷地被使用。為了復用這些代碼,實現為一個公共的函數是一個解決方法。不過函數用到的一些變量,可能是公共的全局變量。引入全局變量,容易出現同名衝突,不方便維護。
這時就可以使用仿函數了,寫一個簡單類,除了維護類的基本成員函數外,只需要重載 operator() 運算符 。這樣既可以免去對一些公共變量的維護,也可以使重複使用的代碼獨立出來,以便下次復用。而且相對於函數更優秀的性質,仿函數還可以進行依賴、組合與繼承等,這樣有利於資源的管理。如果再配合模板技術和 Policy 編程思想,則更加威力無窮,大家可以慢慢體會。Policy 表述了泛型函數和泛型類的一些可配置行為(通常都具有被經常使用的缺省值)。
STL 中也大量涉及到仿函數,有時仿函數的使用是為了函數擁有類的性質,以達到安全傳遞函數指針、依據函數生成對象、甚至是讓函數之間有繼承關係、對函數進行運算和操作的效果。比如 STL 中的容器 set 就使用了仿函數 less ,而 less 繼承的 binary_function,就可以看作是對於一類函數的總體聲明,這是函數做不到的。
//less的定義 template<typename _Tp> struct less : public binary_function<_Tp, _Tp, bool> { bool operator()(const _Tp& __x, const _Tp& __y) const { return __x < __y; } }; //set的申明 template<typename _Key, typename _Compare = std::less<_Key>,typename _Alloc = std::allocator<_Key>> class set;
仿函數中的變量可以是 static 的,同時仿函數還給出了 static 的替代方案,仿函數內的靜態變量可以改成類的私有成員,這樣可以明確地在析構函數中清除所用的內容,如果用到了指針,那麼這個是不錯的選擇。有人說這樣的類已經不是仿函數了,但其實,封裝後從外界觀察,可以明顯地發現,它依然有函數的性質。
3.仿函數實例
我們先來看一個仿函數的例子。
class StringAppend { public: explicit StringAppend(const string& str) : ss(str){} void operator() (const string& str) const { cout<<str<<' '<<ss<<endl; } private: const string ss; }; int main() { StringAppend myFunctor2("and world!"); myFunctor2("Hello"); }
編譯運行輸出:
Hello and world!
這個例子應該可以讓您體會到仿函數的一些作用:它既能像普通函數一樣傳入給定數量的參數,還能存儲或者處理更多我們需要的有用信息。於是仿函數提供了第四種解決方案:成員變量。成員函數可以很自然地訪問成員變量,從而可以解決第一節「1.為什麼要有仿函數」中提到的問題:計數出數組中大於指定閾值的數字數量。
#include <iostream> using namespace std; class IsGreaterThanThresholdFunctor { public: explicit IsGreaterThanThresholdFunctor(int t):threshold(t){} bool operator() (int num) const { return num > threshold ? true : false; } private: const int threshold; }; int RecallFunc(int *start, int *end, IsGreaterThanThresholdFunctor myFunctor) { int count = 0; for (int *i = start; i != end + 1; i++) { count = myFunctor(*i) ? count + 1 : count; } return count; } int main() { int a[5] = {10,100,11,5,19}; int result = RecallFunc(a, a + 4, IsGreaterThanThresholdFunctor(10)); cout << result << endl; }
編譯運行輸出:
3