STL与泛型编程<十四>:仿函数介绍

简介

仿函数(functor)就是定义了operator()的一种型别(或实例化的对象),可以如下使用

FunctionObjectType fo;
...
fo(...)  //并不是使用函数fo(),而是调用fo的operator()函数

明显其和函数的很像,只不过函数是在函数体内实现,而其实在operator()里实现相关功能,伪代码如下

class FunctionObjectType
{
    public:
        operator() 
        {
            ...
        }
}

显然仿函数更复杂,那么必然会有其优势的
1. 仿函数更灵活,因为其是一种型别,可以拥有状态
2. 每个仿函数都是一种型别,此可以把它当作tmpelate的参数进行传递,这个是普通函数做不到的,从而指定某种行为模式,还有一个好处就是容器的型别也会因仿函数的不同而不同
3. 仿函数比一般函数指针快(深表怀疑!!!有时间试试)

仿函数可当排序准则

见如下例子

#include <iostream>
#include <ostream>
#include <iterator>
#include <string>
#include <set>
using namespace std;

class Person
{
    public:
        string first_name;
        string last_name;
        Person(const string& str1, const string& str2)
        : first_name(str1), last_name(str2){}
};

class PersonSort
{
    public:
        bool operator() (const Person& p1, const Person& p2) const //开始忘写() 
        {
            return (p1.first_name < p2.first_name) || (!(p1.first_name < p2.first_name) && (p1.last_name < p2.last_name)); 
        }   
};

int main(void)
{
    Person p1("wang","er");
    Person p2("zhao","si");
    Person p3("wang","san");
    set<Person,PersonSort> col;  //为什么可以这样,可以看看set的定义
    col.insert(p1);
    col.insert(p2);
    col.insert(p3);

    set<Person,PersonSort>::iterator pos;
    for (pos=col.begin(); pos!=col.end(); ++pos)
        //cout << *pos << " ";  //开始这里出错了,要想不错就得重载operator<<运算符 
        cout << "name : " << pos->first_name << ' ' << pos->last_name << endl;
    return 0;
}  
/*
输出:
wang er
wang san
zhao si
*/

关于仿函数的operator()返回值?这个其实是看具体要求的,你需要用到的仿函数的地方,且带入算法时不需要带入参数,其中仿函数的operator()的参数是算法自动把其区间的元素值带进去进行运算的。

拥有内部状态的仿函数

见下例子

#include <iostream>
#include <algorithm>
#include <iterator>
#include <list>
using namespace std;

class IntSequence
{
    private:
        int _value;
    public:
        IntSequence(int i) : _value(i) {}
        int operator() ()   //返回值为什么得是int而不是void,因为后面的generate_n()里的参数这样要求的 
        {
            return _value++;
        }
};

template<typename T>
inline void PRINT_ELEMENTS(const T& col, const char* str=" ")
{
    typename T::const_iterator pos;
    cout << str;
    for (pos=col.begin(); pos!=col.end(); ++pos)
        cout << *pos << ' ';
    cout << endl;   
}

int main(void)
{
    list<int> col;
    generate_n(back_inserter(col),9,IntSequence(1));
    //generate_n(col.begin(),9,IntSequence(1)); //为什么这样打印不出来,因此之前初始化的col根本就没元素
    PRINT_ELEMENTS(col); // 1 2 3 4 5 6 7 8 9

    generate(++col.begin(),--col.end(),IntSequence(42));
    PRINT_ELEMENTS(col); // 1 42 43 44 45 46 47 48 9
    return 0;
}  

下是generate_n的代码,因此可以看出

template<typename _OutputIterator, typename _Size, typename _Generator>
    _OutputIterator
    generate_n(_OutputIterator __first, _Size __n, _Generator __gen)
    {
      // concept requirements
      __glibcxx_function_requires(_OutputIteratorConcept<_OutputIterator,
            // "the type returned by a _Generator"
            __typeof__(__gen())>)

      for (__decltype(__n + 0) __niter = __n;
       __niter > 0; --__niter, ++__first)
    *__first = __gen();
      return __first;
    }

仿函数是pass by value(传值),而不是pass by reference(引用),因此算法并不会改变随参数而来的仿函数的状态。其实算法当然是可以改变仿函数的状态,但由于传递的只是一个副本,改变的也只是一个副本。
如要改变仿函数的状态,有两种方法
1. 指明pass by reference传递仿函数
2. for_each的回返值

pass by reference传递仿函数

调用算法时指明仿函数型别是个reference型别

#include <iostream>
#include <algorithm>
#include <iterator>
#include <list>
using namespace std;

class IntSequence
{
    private:
        int _value;
    public:
        IntSequence(int i) : _value(i) {}
        int operator() ()   //返回值为什么得是int而不是void,因为后面的generate_n()里的参数这样要求的 
        {
            return _value++;
        }
};

template<typename T>
inline void PRINT_ELEMENTS(const T& col, const char* str=" ")
{
    typename T::const_iterator pos;
    cout << str;
    for (pos=col.begin(); pos!=col.end(); ++pos)
        cout << *pos << ' ';
    cout << endl;   
}

int main(void)
{
    list<int> col;
    IntSequence seq(1);
    generate_n<back_insert_iterator<list<int> >, int, IntSequence& >
    (back_inserter(col),4,seq); //调用算法时指明是引用就可以了
    PRINT_ELEMENTS(col); // 1 2 3 4

    generate_n(back_inserter(col),4,IntSequence(42));
    PRINT_ELEMENTS(col); // 1 2 3 4 42 43 44 45

    generate_n(back_inserter(col),4,seq);
    PRINT_ELEMENTS(col); // 1 2 3 4 42 43 44 45 5 6 7 8

    return 0;
}  

for_each的回返值

例子

#include <iostream>
#include <algorithm>
#include <iterator>
#include <vector>
using namespace std;

class MeanValue
{
    private:
        long _num;  //计数 
        long _sum;  //和 
    public:
        //MeanValue(long num, long sum) : _num(num), _sum(sum) {}
        MeanValue() : _num(0), _sum(0) {}
        void operator() (int elem)   //返回值为什么得是int而不是void,因为后面的generate_n()里的参数这样要求的 
        {
            _num++;
            _sum += elem; 
        }

        double value()
        {
            return static_cast<double>(_sum) / static_cast<double>(_num);
        }
};

template<typename T>
inline void PRINT_ELEMENTS(const T& col, const char* str=" ")
{
    typename T::const_iterator pos;
    cout << str;
    for (pos=col.begin(); pos!=col.end(); ++pos)
        cout << *pos << ' ';
    cout << endl;   
}

int main(void)
{   
    vector<int> col;
    for (int i=1; i<9; ++i)
        col.push_back(i);
    MeanValue mv = for_each(col.begin(),col.end(),MeanValue()); //刚开始提出错误,因为之前写MeanValue时没有提供默认构造函数

    cout << "mean value : " << mv.value() << endl;
    return 0;
}  

判断式和仿函数

所谓判断式就是返回布尔值的一个函数或仿函数,见下例子

#include <iostream>
#include <list>
#include <algorithm>
#include <iterator>
using namespace std;

class Nth
{
    private:
        int nth;
        int count;
    public:
        Nth (int n) : nth(n), count(0){}
        bool operator() (int) 
        {
            return ++count == nth;
        }   
};//改仿函数就是移除第nth个元素

int main(void)
{
    list<int> col;
    for (int i=1; i<=9; ++i)
        col.push_back(i);
    copy(col.begin(),col.end(),ostream_iterator<int>(cout," ")); // 1 2 3 4 5 6 7 8 9
    cout << endl;

    list<int>::iterator pos;
    pos = remove_if(col.begin(),col.end(),Nth(3)); // 移除3 
    col.erase(pos,col.end());

    copy(col.begin(),col.end(),ostream_iterator<int>(cout," "));
    cout << endl;
    return 0;
}

奇怪?按道理应该只移除一个3,可是却移除了3和6,为什么?
先看看remove_if的源代码,见下
这里写图片描述
这个算法使用find_if()来搜寻被移除的第一个元素,然而,接下来它使用传进来的判断式op的副本去处理剩余的元素,这时原始状态下的Nth再一次被使用,因而会移除剩余的第3个元素,也就是整体的第6个元素。
还有,见图把,解释不清楚了。总的来说就是你的仿函数设计的不合理,不应该改变判断式的状态。

总结

仿函数就是写一个class,然后为这个class重载一个operator()操作就可以了,至于operator() 函数的返回值及参数值你就看算法的需要而定了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值