天天看點

C++仿函數1.為什麼要有仿函數2.仿函數的定義3.仿函數執行個體參考文獻

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, 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,IsLessThanTenFunctor(10));
    cout<<result<<endl;
    return 0;
}           

複制

參考文獻

[1]仿函數.百度百科