仿函数(函数对象)
轉載:http://www.cnblogs.com/wuchanming/p/4411867.html
本文乃作者學習《C++標準程序庫》的學習筆記,首先介紹了仿函數(函數對象)和函數適配器(配接器)的概念,然后列出STL中所有的仿函數,以及函數適配器,并摘錄了幾個例子演示仿函數和函數適配器的用法,最后討論了仿函數的組合,以及實現方法。
1.仿函數是什么東西?
《C++標準程序庫》里對仿函數的解釋是:仿函數是泛型編程強大威力和純粹抽象概念的又一例證。你可以說,任何東西,只要其行為像函數,它就是一個函數。因此如果你定義了一個對象,行為像函數,它就可以被當做函數來用。
那么,什么才算具備函數行為呢?所謂函數行為,是指可以"使用小括號傳遞參數,籍以調用某個東西"。例如:
function(argc1, argc2);如果你指望對象也可以如此這般,就必須讓它們也可以被"調用"——通過小括號的運用和參數的傳遞。你只需要定義operator(),并給予合適的參數型別:
class X {public: // define 'function call' operator return-value operator()(arguments) const; ...... };現在,你可以把這個類別的對象當做函數來調用了:
X fo; ...... // call operator () for function object to fo(argc1, argc2);上述調用等價于:
fo.operator() (argc1, argc2);總結如下:
- 函數對象是一個普通對象
- 重載了operator ()操作符
- 函數對象一般都比較簡單,主要用到operator()操作,其他成員函數和成員變量都是為operator()服務
為了幫助理解仿函數,我們來看一個例子:
#include <algorithm> class gen_by_two { public: gen_by_two( int seed = 0 ) : _seed( seed ){} int operator()() { return _seed += 2; } private: int _seed; }; vector<int> ivec( 10 ); // fills ivec: 102 104 106 108 110 112 114 116 118 120 generate_n( ivec.begin(), ivec.size(), gen_by_two(100) );或者
generate_n( ivec.begin(), ivec.size(), gen_by_two() );
上面的程序使用generate_n函數,給容器賦值,在調用gen_by_two(100)的時候,我們其實是生成了一個對象,并調用對象的構造函數給成員變量賦值,然后多次(v.size())調用該變量的operator()成員函數,將返回的值依次存入容器中。
2.函數適配器又是什么東西?
函數適配器又稱"函數配接器",是只能夠將仿函數和另一個仿函數(或某個值,或某一個函數)結合起來的仿函數。函數適配器聲明于<functional>中。
下面來看一個例子:
find_if(coll.begin(), coll.end(), //range bind2nd(greater<int>(), 42));criterion其中表達式bind2nd(greater<int>(),42)導致一個組合型的仿函,檢查某個int值是否大于42.實際上bind2nd是將一個二元仿函數轉換為一個一元仿函數。bind2nd的意思就是將42作為比較(greater<int>())函數的第二個參數,也就相當于是elem.value > 42,如果容器中沒有42這個值,那么下面這條語句和上面的語句是一樣的。
find_if(coll.begin(), coll.end(), //range bind1st(less<int>(), 42));criterion上面兩個小例子演示了適配器的功能,同時還講解了bind1st和bind2nd的區別。
3.預定義的仿函數
4.預定義的函數適配器
5.仿函數(以及函數適配器)的使用示例
下面摘錄幾個《C++標準程序庫》上的例子,以及個別我自己補充的實例,用于演示仿函數的使用,希望能夠通過例子快速掌握仿函數。
5.1 查找25或者35第一次出現的位置
pos = find_if(coll.begin(), coll.end(), //range compose_f_gx_hx(logical_or<bool>(), //criterion bind2nd(equal_to<int>(), 25), bind2nd(equal_to<int>(), 35)));5.2 將集合中全部元素都設為相反值
transform(coll.begin(), coll.end(),//source coll.begin(),//destination negate<int>());//operation5.3 對集合中的所有元素求平方
transform(coll.begin(), coll.end(),//first source coll.begin(),//second source coll.begin(),//destination multiplies<int>());//operation5.4 所有元素乘以10
transform(coll.begin(), coll.end(),//source back_inserter(coll2),//destination bind2nd(multiplies<int>(),10));//operation5.5 將a替換為b
replace_if(coll2.begin(), coll2.end(),//range bind2nd(equal_to<int>(), 70),//replace criterion 42);//new value5.6 刪除小于50的元素
coll.erase(remove_if(coll.begin(), coll.end(),//range bind2nd(less<int>(), 50)), //remove criterion coll.end());5.7 返回第一個偶數
pos = find_if(coll.begin(), coll.end(), //range not1(bind2nd(modulus<int>(), 2)));上面幾個例子都還是挺使用的,返回第一偶數么,怎么看怎么奇怪,沒有關系,我們來看一個不奇怪的:
5.8 調整數組順序,使得奇數位于偶數前面
stable_partition(v.begin(), v.end(), bind2nd(modulus<int>(), 2));關于這里用到的泛型算法,如果還有不熟悉的,可以參考這里
5.9 mem_fun_ref 與mem_fun 的區別以及用法
- mem_fun_ref:調用某個對象的成員函數
- mem_fun:功能和mem_fun_ref 一樣,如果容器里存放的是指向對象的指針,而不是對象,則應該使用mem_fun
下面來看一個使用mem_fun_ref 的例子,假設我們定義了一個類Person,并且定義了一個print 的成員函數,容器coll 里存放了coll.size() 個Person 對象,現在要調用每個對象的print 成員函數,那么我們就可以像下面這樣。
class Person { public: ... void print() const { std::cout << name << std::endl; } void printWithPrefix(std::string prefix) const { std::cout << prefix << name << std::endl; } private: std::string name; }; void foo(const std::vector<Person> &coll) { for_each(coll.begin(), coll.end(), mem_fun_ref(&Person::print)); }我們不能直接把一個成員函數傳遞給一個算法,所以這里必須運用函數適配器,下面這種做法會導致編譯錯誤。
for_each(coll.begin(), coll.end(), &Person::print); //ERROR通過使用函數適配器,我們還可以像被調用的成員函數傳遞一個參數。如下所示:
//call member function printWithPrefix() for each elementfor_each(coll.begin(), coll.end(), bind2nd(mem_fun_ref(&Person::printWithPrefix), "person:"));5.10 ptr_fun
ptr_fun 使得我們能夠在其他函數適配器中使用一般函數,加入你自己定義了一個函數check(),用于檢驗容器中的中的元素是否符合某種條件,你就可以這樣:
pos = find_if(coll.begin(), coll.end(), not1(ptr_fun(check)));這里不能使用not1(check),因為not1()需要用到由仿函數提供的某些特殊型別.
第二種用法是,當你有一個雙參數的全局函數,又想把它當做一個單參數函數來使用,可以用如下語句:
//find first string that is not empty pos = find_if(coll.begin(), coll.end(), bind2nd(ptr_fun(strcmp),""));6.讓自定義的仿函數也可以使用函數適配器
你可以編寫自己的仿函數,但如果希望它們能夠和函數適配器搭配運用,就必須滿足某些條件:必須提供一些型別成員來反映其參數和返回值的型別。為了方便我們,C++標準庫提供了一些結構如下:
轉載:http://mingxinglai.com/cn/2012/09/function-object/
總結
- 上一篇: 成都欢乐谷靠近哪个地铁站
- 下一篇: C++ template —— 动多态与