STL 之 函数对象(函数符)

函数对象

    也叫做 函数符 (functor)。函数符是可以以函数方式与()结合使用的任意对象。这包括函数名、指向函数的指针和重载了()操作符的类对象(即定义了函数operator()()的类)。

 

    例如,这样定义一个类:
    class Linear
    { private:
         double slope,y0;
      public:
         Linear (double _sl = 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);

    其中y1将使用表达式0 + 1 * 12.5来计算,y2将使用表达式10.0 + 2.5 * 0.4来计算。

 

举例说明函数符调用

 

    函数for_each(),它将指定的函数用于区间中的每个成员:
    for_each(books.begin(), books.end(), ShowReview);

 

    通常,第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(...)的表达式。


    在上面范例中,f是指向函数的指针,而f(...)调用该函数。如果最后的for_each()参数是一个对象,则f(...)将是调用重载的()操作符的对象。

 

函数符概念

    生成器(generator),是不用参数就可以调用的函数符。

    一元函数(unary function),是用一个参数可以调用的函数符。

    二元函数(binary function),是用两个参数可以调用的函数符。

    (例如,提供给for_each()的函数符应当是一元函数,因为它每次用于一个容器元素。)

 

    返回bool值的一元函数是 断言 (predicate)

    返回bool值的二元函数是 二元断言 (binary predicate)

 

    list模板有一个将断言作为参数的remove_if()成员,该函数将断言应用于区间中的每个元素,如果断言返回true,则删除这些元素。

    例如,下面的代码删除链表scores中所有大于100的元素:
    bool tooBig(int n) { return n > 100; }
    list<int> scores;
    ...
    scores.remove_if(tooBig);

 

    现在假设又要删除另一个链表中所有大于200的值。就必须重新设计一个断言。如果能将取舍值作为第二个参数传递给tooBig(),则可以使用不同的值调用该函数,但断言又只能有一个参数。不过,如果设计一个tooBig类,则可以使用类成员而不是函数参数,来传递额外的信息:

    template<class T>
    class tooBig
    { private:
         T cutoff;
      public:
         tooBig (const T & t): cutoff(t) {}
         bool operator() (const T & v) { return v > cutoff; }
    };

 

    这里,一个值(V)作为函数参数传递,而第二个参数(cutoff)是由类构造函数设置的。有了该定义后,就可以将不同的tooBig对象初始化为不同的取舍值,供调用remove_if()时使用:
    scores.remove_if(tooBig<int>(200));

    函数符(tooBig<int>(200))是一个匿名对象,它是由构造函数调用创建的。

 

拓展设计

    假设已经有了一个接受两个参数的模板函数:
    template <class T>
    bool tooBig (const T & val, const T & lim)
    { return val > lim; }

 

    则可以使用类将它转换为单个参数的函数对象:
    template <class T>
    class TooBig2
    { private:
         T cutoff;
      public:
         tooBig (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))
          // same as if ( tooBig(x,100) )
        ...

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

 

预定义的函数符

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

 

    例如,考虑函数transform()。它有两个版本。第一个版本接受4个参数,前两个参数指定容器区间的迭代器,第三个参数是指定将结果复制到哪里的迭代器,最后一个参数是一个函数符,它被应用于区间中的每个元素,生成结果中的新元素:

    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);

    上述代码计算每个元素的平方根,并将结果发送到输出流。目标迭代器也可以位于原始区间中。

    例如将上述范例中的out替换为gr8.begin()后,新值将覆盖原来的值。

 

    第二个版本使用一个接受两个参数的函数,并将该函数用于两个区间中的元素。它用另一个参数(即第3个)标识第二个区间的起始位置。
    例如,如果m8是另一个vector<double>对象,mean(double,double)返回两个值的平均值,则下面的代码将输出来自gr8和m8的值的平均值:
    transform(gr8.begin(),gr8.end(),m8.begin(),out,mean);

 

    对于所有内置的算术操作符、关系操作符和逻辑操作符,STL都提供了等价的函数符。

    下图列出了这些函数符的名称。它们可以用于处理C++内置类型或任何用户定义类型(如果重载了相应的操作符)。

 

 

操作符和相应的函数符

 


    例如使用plus<>()。它在头文件functional中定义。
    #include <functional>
    ...
    plus<double> add;
            // create a plus<double> object
    double y = add(2.2,3.4);   // using plus<double>::operator()()

 

    将它用作函数符:
    transform(gr8.begin(),gr8.end(),m8.begin(),out,plus<double>());

    这里没有创建命名的对象,而是用plus<double>构造函数构造了一个函数符,括号表示调用默认的构造函数。

 

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

    上图列出的预定义函数符都是自适应的。STL有5个相关的概念:
    自适应生成器   (adaptable generator)
    自适应一元函数 (adaptable unary function)
    自适应二元函数 (adaptable binary function)
    自适应断言     (adaptable predicate)
    自适应二元断言 (adaptable binary predicate)

 

    使函数符成为自适应的原因是,它携带了标识参数类型和返回值类型的typedef成员。这些成员分别是result_typefirst_argument_typesecond_argument_type

    例如,plus<int>对象的返回值类型被标识为plus<int>::result_type,这是int的typedef。

 

    函数符自适应性的意义在于:函数适配器对象可以使用函数对象,并认为存在这些typedef成员。
    例如,接受一个自适应函数符参数的函数可以使用result_type成员来声明一个与函数的返回值类型匹配的变量。

 

    STL提供了使用这些工具的函数适配器类。例如,假设要将矢量gr8的每个元素都增加2.5倍,则需要使用接受一个一元函数参数的transform()版本。multiplies()函数符可以执行乘法,但它是二元函数。因此需要一个函数适配器,将接受两个参数的函数符转换为接受一个参数的函数符。STL使用binderlstbinder2nd类自动完成这一过程,它们将自适应二元函数转换为自适应一元函数。

 

    假设有一个自适应二元函数对象f2(),则可以创建一个binderlst对象,该对象与一个将被用作f2()的第一个参数的特定值(val)相关联:
    binderlst (f2,val) f1;

    使用单个参数调用f1(x)时,返回的值与将val作为第一参数、将f1()的参数作为第二参数调用f2()返回的值相同。

 

    STL提供了函数bindlst(),以简化binderlst类的使用。可以用其提供用于构建binderlst对象的函数名称和值,它将返回一个这种类型的对象。
    例如,要将二元函数multiplies()转换为将参数乘以2.5的一元函数,则可以:
    bindlst (multiplies<double>(), 2.5)

 

    因此,将gr8中的每个元素与2.5相乘,并显示结果的代码如下:
    transform(gr8.begin(), gr8.end(), out,
         bindlst (multiplies<double>(), 2.5) );

 

    binder2nd类与此类似,只是将常数赋给第二个参数,而不是第一个参数。它有一个名为bind2nd的助手函数,该函数的工作方式类似于bindlst。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值