淺析C++ 仿函式
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.仿函式例項
我們先來看一個仿函式的例子:
#include <iostream> #include <string> using namespace std; class Functor { public: void operator() (const string& str) const { cout << str << endl; } }; int main() { Functor myFunctor; myFunctor("Hello world!"); return 0; }
程式輸出:
Hello world!。
可以見到,仿函式提供了第四種解決方案:成員變數。成員函式可以很自然的訪問成員變數,從而解決上文最開始的那個問題。
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"); return 0; }
程式輸出:
Hello and world!。
這個例子應該可以讓您體會到仿函式的一些作用:它既能像普通函式一樣傳入給定數量的引數,還能儲存或者處理更多我們需要的有用資訊。於是本小節開頭的問題就迎刃而解了:
#include <iostream> using namespace std; class IsGreaterThanThresholdFunctor { public: explicit IsLessThanTenFunctor(int tmp_threshold) : threshold(tmp_threshold{} bool operator() (int num) const { return num>10 ? true : false; } private: const int threshold; }; int RecallFunc(int *start,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,IsLessThanTenFunctor(10)); cout<<result<<endl; return 0; }
以上就是淺析C++ 仿函式的詳細內容,更多關於C++ 仿函式的資料請關注我們其它相關文章!