歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

C++函數配接器

一、概述

  配接器(adaptor)在STL組件的靈活運用功能上,扮演著軸承、轉換器的角色,將一種容器或迭代器裝換或封裝成另一種容器或迭代器。adaptor這個概念,實際上是一種設計模式,其定義如下:

  將一個class的接口轉換為另一個class的接口,使原本因接口不兼容而不能合作的classes,可以一起運作。

配接器按功能可以分為如下3類:

可以改變函數或仿函數接口的適配器,稱為仿函數適配器;

    針對容器的適配器,稱為容器適配器;

    針對迭代器的適配器,稱為迭代器適配器。

本博客只介紹仿函數適配器,在實際編程中比較常見。

二、什麼是可配接對象

  什麼是可配接對象?看到這句話可能還雲裡霧裡的,真不太明白,下面通過一個很簡單的給數組排序的例子來解釋一下。

#include <iostream>

#include <vector>

#include <algorithm>

#include <iterator>    //ostream_iterator

using namespace std;

struct myLess

{

        bool operator()(int lhs, int rhs) const

        { 

                return lhs < rhs;

        } 

};

int main()

{

        int IntArray[] = {7,4,2,9,1};

        sort(IntArray, IntArray + sizeof(IntArray) / sizeof(int), myLess());

        copy(IntArray, IntArray + sizeof(IntArray) / sizeof(int), ostream_iterator<int>(cout, "\n"));

        return 0;

}

 

#程序執行結果

[root@Oracle Documents]# ./a.out

1

2

4

7

9

  可以看到這個程序正確執行了,現在我想讓程序內的數組進行降序。當然你可以重新定義一個仿函數,但是我想用一個更快捷的方法,那就是not2函數。

//修改排序那一行的函數

sort(IntArray, IntArray + sizeof(IntArray) / sizeof(int), not2(myLess()));

 

  但是我發現這樣是編譯不過的,為什麼呢?這就回到我們的主題了,因為myLess不是一個可配接對象。那麼如何讓它變成一個可配接對象呢,繼續往下看。

三、unary_function和binary_function

  為什麼剛剛寫的myLess對象是不可配接的呢?因為它缺少argument_type、first_argument_type、second_argument_type和result_type這些特殊類型的定義。而unary_function和binary_function則可以提供這些類型的定義。我們在定義仿函數的時候,只需繼承自這2個函數,那麼我們的仿函數就是可配接的對象了。由於unary_function和binary_function是STL提供的模版,所以必須要指定必要的參數類型。

 

#include <iostream>

#include <vector>

#include <algorithm>

#include <iterator>    //ostream_iterator

#include <functional>  //binary_function, not2

using namespace std;

//第一個參數,第二個參數,返回值

struct myLess : public binary_function<int, int, bool>

{

        bool operator()(int lhs, int rhs) const

        { 

                return lhs < rhs;

        } 

};

int main()

{

        int IntArray[] = {7,4,2,9,1};

        sort(IntArray, IntArray + sizeof(IntArray) / sizeof(int), not2(myLess()));

        copy(IntArray, IntArray + sizeof(IntArray) / sizeof(int), ostream_iterator<int>(cout, "\n"));

        return 0;

}


 

#程序執行結果

[root@oracle Documents]# ./a.out

9

7

4

2

1

  傳遞給unary_function和binary_function的模版參數正是函數子類的operator()的參數類型和返回值。如果operator()接受一個參數,則使用unary_function<參數, 返回值>;如果operator()接受兩個參數,則使用binary_function<參數1, 參數2, 返回值>。

  一般情況下,傳遞給unary_function和binary_function的非指針類型需要去掉const和引用(&)部分。如下:

struct myLess : public binary_function<myClass, myClass, bool>

{

        bool operator()(const myClass &lhs, const myClass &rhs) const

        { 

                return lhs < rhs;

        } 

};

  但是以指針作為參數或返回值的函數子類,一般規則是,傳給unary_function和binary_function的類型與operator()的參數和返回類型完全相同。如下:

struct myLess : public binary_function<const myClass *, const myClass *, bool>

{

        bool operator()(const myClass *lhs, const myClass *rhs) const

        { 

                return lhs < rhs;

        } 

};

四、標准的函數配接器

1. not1和not2

  這2個配接器都是對可配接對象的否定。上面已經介紹過使用方法了。那麼什麼時候用not1,什麼時候用not2呢?

  如果可配接對象的operator()接受一個參數則使用not1;如果可配接對象的operator()接受兩個參數則使用not2。

2. bind1st和bind2nd

  bind1st表示我們綁定第一個參數,bind2st表示我們綁定第二個參數。

 

#include <iostream>

#include <vector>

#include <algorithm>

#include <iterator>    //ostream_iterator

#include <functional>  //binary_function, bind1st

using namespace std;

struct myLess : public binary_function<int, int, bool>

{

        bool operator()(int lhs, int rhs) const

        { 

                return lhs < rhs;

        } 

};

int main()

{

        int IntArray[] = {7,4,2,9,1};

        vector<int> IntVec(IntArray, IntArray + sizeof(IntArray) / sizeof(int));

        IntVec.erase(remove_if(IntVec.begin(), IntVec.end(), bind1st(myLess(), 5)), IntVec.end());

        copy(IntVec.begin(), IntVec.end(), ostream_iterator<int>(cout, "\n"));

        return 0;

}

 

#程序執行結果

[root@oracle Documents]# ./a.out

4

2

1

  bind1st(myLess(), 5)相當於把5賦值給lhs,那麼表達式就變成 5 < rhs,所以7和9就被刪除了。

  如果把bind1st(myLess(), 5)改成bind2nd(myLess(), 5)),就相當於把5賦值給rhs,那麼表達式就變成 lhs < 5, 所以1、2和4就被刪除了。bind1st(myLess(), 5) 等於 not1(bind2nd(myLess(), 5)))。

五、ptr_fun、mem_fun和mem_fun_ref

  我們已經知道仿函數通過繼承unary_function和binary_function可以變成可配接對象,那麼普通函數或者類的成員函數如何變成可配接對象呢?這就需要用到標題中的三個函數了。

#include <iostream>

#include <vector>

#include <algorithm>

#include <iterator>    //ostream_iterator

#include <functional>  //not2

using namespace std;

class sortObj

{

public:

        bool memComp(const sortObj *other)

        { 

                return *this < *other;

        } 

        bool memComp_const(const sortObj &other) const

        { 

                return *this < other;

        } 

public:

        sortObj(int v) : value(v){}

        ~sortObj(){}

        friend bool operator<(const sortObj &lhs, const sortObj &rhs)

        { 

                return lhs.value < rhs.value;

        }

        friend ostream & operator<<(ostream &os, const sortObj &obj)

        {

                return os << obj.value << endl;

        }

private:

        int value;

};

bool sortFun(const sortObj &lhs, const sortObj &rhs)

{

        return lhs < rhs;

}

//把指針轉換成對象

sortObj & ptrToObj(sortObj *ptr)

{

        return *ptr;

}

int main()

{

        sortObj objArray[] = {

                sortObj(7),

                sortObj(4),

                sortObj(2),

                sortObj(9),

                sortObj(1)

        };

        vector<sortObj> objVec(objArray, objArray + sizeof(objArray) / sizeof(sortObj));

        //配接普通函數(降序)

        sort(objVec.begin(), objVec.end(), not2(ptr_fun(sortFun)));

        copy(objVec.begin(), objVec.end(), ostream_iterator<sortObj>(cout, ""));

        cout << endl;

        srand(time(NULL));

        random_shuffle(objVec.begin(), objVec.end());  //打亂順序

        //配接對象的成員函數(升序)

        sort(objVec.begin(), objVec.end(), mem_fun_ref(&sortObj::memComp_const));

        copy(objVec.begin(), objVec.end(), ostream_iterator<sortObj>(cout, ""));

        cout << endl;

        //配接指針的成員函數(降序)

        vector<sortObj *> objVecPtr;

        objVecPtr.push_back(new sortObj(7));    //內存洩漏了,不要在意這些細節

        objVecPtr.push_back(new sortObj(4));

        objVecPtr.push_back(new sortObj(2));

        objVecPtr.push_back(new sortObj(9));

        objVecPtr.push_back(new sortObj(1));

        sort(objVecPtr.begin(), objVecPtr.end(), not2(mem_fun(&sortObj::memComp)));

        transform(objVecPtr.begin(), objVecPtr.end(), ostream_iterator<sortObj>(cout, ""), ptrToObj);

        return 0;

}

  上述代碼中,首先調用not2(ptr_fun(sortFun)),用ptr_fun對普通函數sortFun進行配接;其次調用mem_fun_ref(&sortObj::memComp_const)和not2(mem_fun(&sortObj::memComp))對sortObj類的成員函數進行配接。這裡有的童鞋可能有疑問:memComp明明只有一個形參,為什麼用not2而不是not1?成員函數在別調用的時候,會自動傳進this指針的,所以這裡還是兩個參數。

  mem_fun和mem_fun_ref都是對類的成員函數進行配接,那麼它們有什麼區別嗎?相信細心的童鞋已經看出來了,當容器中存放的是對象實體的時候用mem_fun_ref,當容器中存放的是對象的指針的時候用mem_fun。

  mem_fun和mem_fun_ref有一個很大的弊端:它們只能接收0個或1個參數(不算this指針)。這個實在有點局限。新的bind函數模板可以用於任何函數、函數指針、成員函數、函數對象、模板函數、lambda表達式,還可以嵌套bind。

六、bind

  上面介紹的這些配接器都是C++11之前使用的,在C++11中這些配接器已經被廢棄了,改成使用bind函數。如果想在C++11之前的版本中使用這個函數,有Linux下有兩種方法。

1. 包含<functional>頭文件,在編譯的時候增加編譯參數-std=c++0x,那麼就可以使用std::bind了

2. 包含#include <tr1/functional> 頭文件,直接可以使用std::tr1::bind了。

  在bind中有2種方式可以把值傳遞進bind函數中,一種是預先綁定的參數,這個參數是通過pass-by-value傳遞進去的;另一種是不預先綁定的參數,這種參數是通過placeholders占位符傳遞進去的,它是pass-by-reference的。

  上述代碼中的3個排序函數配接器可以替換成下面這樣的bind,如下:

//綁定普通函數(降序)

sort(objVec.begin(), objVec.end(), tr1::bind(sortFun, tr1::placeholders::_2, tr1::placeholders::_1));

//綁定類對象的成員函數(升序)

sort(objVec.begin(), objVec.end(), tr1::bind(&sortObj::memComp_const, tr1::placeholders::_1, tr1::placeholders::_2));

//綁定類指針的成員函數(降序)

sort(objVecPtr.begin(), objVecPtr.end(), tr1::bind(&sortObj::memComp, tr1::placeholders::_2, tr1::placeholders::_1));

  注意,在上面的例子中,我使用了not2方法對結果進行倒序。但是bind和not2是不兼容的,實現倒序的方法也很簡單,先傳遞placeholders::_2,再傳遞placeholders::_1就可以實現了。

bind的其他用法實例:

#include <iostream>

#include <tr1/memory>

#include <tr1/functional>

using namespace std;

using namespace std::tr1;

int main()

{

        //嵌套bind

        //(x + y) * x

        function<int (int, int)> func = bind(multiplies<int>(),

                                bind(plus<int>(), placeholders::_1, placeholders::_2),

                                placeholders::_1);

        cout << func(2, 3) << endl;

        //reference_wrapper<T>類型, 實現綁定引用

        int x = 10;

        function<int ()> funcMinus = bind(minus<int>(), 100, cref(x));

        cout << funcMinus() << endl;    //輸出90

        x = 50;

        cout << funcMinus() << endl;    //輸出50

        return 0;

}

  function的<>中定義的是綁定後的函數的原型,即func和funcMinus的函數原型

#程序執行結果

[root@oracle Documents]# ./a.out

10

90

50

七、內置仿函數

上面介紹bind的例子中已經使用過C++內置的仿函數,這裡再進行一下匯總

 1)算術類仿函數

 加:plus<T>        接收2個參數

 減:minus<T>      接收2個參數

 乘:multiplies<T>    接收2個參數

 除:divides<T>      接收2個參數

 模取:modulus<T>    接收2個參數

 否定:negate<T>    接收1個參數(正數變負數,負數變正數)

 2)關系運算類仿函數

 等於:equal_to<T>        接收2個參數

 不等於:not_equal_to<T>    接收2個參數

 大於:greater<T>          接收2個參數

 大於等於:greater_equal<T>  接收2個參數

 小於:less<T>            接收2個參數

 小於等於:less_equal<T>    接收2個參數

 3)邏輯運算仿函數

 邏輯與:logical_and<T>    接收2個參數

 邏輯或:logical_or<T>      接收2個參數

 邏輯否:logical_no<T>      接收2個參數

Copyright © Linux教程網 All Rights Reserved