1. 程式人生 > 程式設計 >淺析C++ 仿函式

淺析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++ 仿函式的資料請關注我們其它相關文章!