zoukankan      html  css  js  c++  java
  • C++_标准模板库STL概念介绍3-函数对象

    函数对象也叫做函数符(functor)

    函数符是可以以函数方式和( )结合使用的任意对象。

    包括函数名指向函数的指针重载了()运算符的类对象

    可以这样定义一个类:

    class Linear

    {

    private:

      double slope;

      double y0;

    public:

      Linear(double s1_=1, double y_ = 0):slope(s1_),y0(y_)  { }

      double operator() (double x) {return y0 + slope * x;}

    }

    重载了()运算符后,就可像使用函数那样使用Linear对象;

    Linear f1;

    Linear f2(2.5, 10.0);

    double y1 = f1(12.5);   // right-hand side is f1.operator( )(12.5)

    double y2 = f2(0.4);

    接下来讨论另一个例子:

    for_each(books.begin(), books.end(), ShowReview);

    通常,第3个参数可以是常规函数,也可以是函数符。

    现在有个问题,如何声明第3个参数?

    不能把它声明为函数指针,因为函数指针指定了参数类型。

    由于容器是可以包含任意类型的,所以预先也无法知道应使用哪种类型。

    STL是通过模板解决该问题。

    for_each的原型看上去就像这样:

    template <class InputIterator, class Function>

    Function for_each(InputIterator first, InputIterator last, Function f);

    ShowReview的原型如下:

    void ShowReview(const Review &);

    这样标识符ShowReview的类型将为void(*)(const Review &)。这也是赋给模板参数Function的类型。

    Function还可以表示具有重载()运算符的类类型。

    最终,for_each()代码将具有一个使用f()的表达式。

    在ShowReview示例中,f是指向函数的指针,而f()调用函数。

    如果最后的for_each()参数是一个对象,则f()将是调用其重载()运算符的对象。

    ====================================================

    一、函数符的概念

    生成器是不用参数就可以调用的函数符;

    一元函数是用一个参数就可以调用的函数符;

    二元函数是用两个参数就可以调用的函数符;

    返回bool值的一元函数是谓词

    返回bool值的二元函数是二元谓词

    接下来有一个例子演示类函数符适用的地方。

     1 //functor.cpp  -- using a functor
     2 
     3 #include <iostream>
     4 #include <list>
     5 #include <iterator>
     6 #include <algorithm>
     7 
     8 template<class T>
     9 class TooBig
    10 {
    11 
    12 };
    13 
    14 void outint(int n) {std::cout<< n << " ";}
    15 
    16 int main()
    17 {
    18     using std::list;
    19     using std::cout;
    20     using std::endl;
    21 
    22     TooBig<int> f100(100);
    23     int vals[10] = {50, 100, 90, 180, 60, 210, 415, 88, 188, 201};
    24     list<int> yadayada = {vals, vals+10};
    25     list<int> etcetera = {vals, vals+10}; 
    26 
    27     cout<<"Original lists:
    ";
    28     for_each(yadayada.begin(), yadayada.end(), outint);
    29     cout<<endl;
    30     for_each(etcetera.begin(), etcetera.end(), outint);
    31     cout<<endl;
    32     yadayada.remove_if(f100);
    33     etcetera.remove_if(TooBig<int>(200));
    34     cout<<"Trimmed lists:
    ";
    35     for_each(yadayada.begin(), yadayada.end(), outint);
    36     cout<<endl;
    37     for_each(etcetera.begin(), etcetera.end(), outint);
    38     cout<<endl; 
    39     return 0;   
    40 }

    再来一个例子:

    假设已经有了一个接受两个参数的模板函数:

    template <class T>

    bool tooBig(const T & val, const T & lim)

    {

        return val >lim;

    }

    则可以使用将它转换为单个参数的函数对象:

    template<class T>

    class TooBig2

    {

    private:

        T cutoff;

    public:

        TooBig2(const T & t):cutoff(t) { }

        bool operator() (const T & v) {return tooBig<T>(v, cutoff);}

    };

    即可以这样做:

    TooBig2<int> tB100(100);

    int x;

    cin>>x;

    if(tB100(x))

        ...

    类函数符TooBig2是一个函数适配器,使函数能够满足不同的接口。

    ====================================================

    二、预定义的函数符

    STL定义了多个基本函数符,它们执行诸如将两个值相加、比较两个值是否相等操作。

    提供这些函数对象是为了支持将函数作为参数的STL函数。

    例如,考虑函数transform(),它有两个版本。

    第一个版本使用接受4个参数的函数:

      前两个参数是指定容器区间的迭代器;

      第3个参数是指定将结果复制到哪里的迭代器;

      最后一个参数是一个函数符;

      const int LIM =5;

      double arr1[LIM] = {36, 39, 42, 45, 48};

      vector<double> gr8(arr1, arr1+LIM);

      ostream_iterator<double, char> out(cout, " ");

      transform(gr8.begin(), gr8.end(), out, sqrt);

    第二个版本使用接受5个参数的函数:

      第3个参数标识第二个区间的起始位置;

      如果m8是另一个vector<double>对象,mean(double, double)返回两个值的平均值,

      则下面的代码将输出来自gr8和m8的值的平均值:

      transform(gr8.begin(), gr8.end(), m8.begin(), out, mean);

    现在假设要将两个数组相加,不能将+作为参数,因为对于类型double来说,+是内置的运算符,而不是函数。

    可以定义一个将两个数相加的函数,然后使用它:

    double add(double x, double y)  {return x+y;}

    ...

    transform(gr8.begin(), gr8.end(), m8.begin(), out, add);

    但是这样做的话,就必须为每种类型单独定义一个add函数。更好的办法是定义一个模板(除非STL已经有一个模板了);

    头文件functional定义了多个模板类函数对象,其中包括plus<>()。

    因此可以用plus<>类完成常规的相加运算;

    #include <functional>

    ...

    plus<double> add;

    double y =add(2.2, 3.4);

    transform(gr8.begin(), gr8.end(), m8.begin(), out, plus<double>);  //它使得将函数对象作为参数非常方便。

    对于所有内置的运算符,STL都提供了等价的函数符。

    ====================================================

    三、自适应函数符和函数适配器

     自适应的英文名叫adapter,其实也可以理解成适配器。函数符适配器。

    适配的目的是满足将原先不满足要求的函数符,适配成满足要求的。就像是插座转换器一样。

    例如:

    transform只能接受一元函数参数。

    multiplies()函数符可以执行乘法运算符, 但是它是二元函数。

    因此需要函数适配器,将接受2个参数的函数符转换为接受1个参数的函数符。

    前面的示例TooBig2提供了这种方法。即用定义类函数符的方法。

    那么是否能将这个过程自动化呢?是可以的。

    就叫做自动化适配

    STL使用binder1st和binder2nd类自动完成这一过程。

    它们将自适应二元函数转换为自适应一元函数。

    而且前提是被适配的函数符必须是自适应的。自动化适配

    接下来看一个例子:

    binder1st(f2, val) f1;

    这样的话f1(x)等价于f2(val,x);

    f2被适配,当然f2必须是一个自适应函数时,才能实现。

    这样看上去还是有点麻烦,STL提供了函数bind1st(),以简化binder1st类的使用。

    bind1st(multiples<double>(), 2.5);

    将gr8中的每个元素与2.5相乘,并显示结果的代码如下:

    transform(gr8.begin(), gr8.end(), out, bind1st(multiples<double>(), 2.5));

     1 #include <iostream>
     2 #include <vector>
     3 #include <iterator>
     4 #include <algorithm>
     5 #include <functional>
     6 
     7 void Show(double);
     8 const int LIM = 6;
     9 
    10 
    11 int main()
    12 {
    13        using namespace std;
    14     double arr1[LIM] = {28, 29, 30, 35, 38, 59};
    15     double arr2[LIM] = {63, 65, 69, 75, 80, 99};
    16     vector<double> gr8(arr1, arr1+LIM);
    17     vector<double> m8(arr2, arr2+LIM);
    18     cout.setf(ios_base::fixed);
    19     cout.precision(1);
    20     cout<<"gr8:	";
    21     for_each(gr8.begin(), gr8.end(), Show);
    22     cout<<endl;
    23     cout<<"m8: 	";
    24     for_each(m8.begin(), m8.end(), Show);
    25     cout<<endl;
    26     
    27     vector<double> sum(LIM);
    28     transform(gr8.begin(), gr8.end(), m8.begin(), sum.begin(), plus<double>());
    29     cout<<"sum: 	";
    30     for_each(sum.begin(), sum.end(), Show);
    31     cout<<endl;
    32     
    33     vector<double> prod(LIM);
    34     transform(gr8.begin(), gr8.end(), m8.begin(), prod.begin(), bind1st(multiplies<double>(), 2.5));
    35     cout<<"prod: 	";
    36     for_each(prod.begin(), prod.end(), Show);
    37     cout<<endl;    
    38    return 0;
    39 }
    40 
    41 void Show(double v)
    42 {
    43     std::cout.width(6);
    44     std::cout<< v <<' ';
    45 }
  • 相关阅读:
    第一个vbscript程序
    判定VBscript方法是否存在
    我的模块加载系统 v24
    libnet/libnids库函数介绍
    程序员应知道的12件事
    基于poll实现的echo服务器
    师者
    OSI/RM参考模型和TCP/IP协议的关系
    TCP(虚电路)和UDP的区别
    python中 __name__及__main()__的妙处
  • 原文地址:https://www.cnblogs.com/grooovvve/p/10467800.html
Copyright © 2011-2022 走看看