zoukankan      html  css  js  c++  java
  • STL——仿函数(函数对象)

    一、仿函数(也叫函数对象)概观

    仿函数的作用主要在哪里?从第6章可以看出,STL所提供的各种算法,往往有两个版本,其中一个版本表现出最常用(或最直观)的某种运算,第二个版本则表现出最泛化的演算流程,允许用户“以template参数来指定所要采行的策略”。以sort()为例,其第一版本是以operator<为排序时的元素位置调整依据,第二版本则允许用户指定任何“操作”,务求排序后的两两相邻元素都能令该操作结果为true。

    要将某种“操作”当做算法的参数,唯一办法就是先将该“操作”(可能拥有数条以上的指令)设计为一个函数,再将函数指针当做算法的一个参数;或是将该“操作”设计为一个所谓的仿函数(就语言层面来说是个class),再以该仿函数产生一个对象,并以此对象作为算法的一个参数。

    根据以上陈述,既然函数指针可以达到“将整组操作当做算法的参数”,那又何必有所谓的仿函数呢?原因在于函数指针毕竟不能满足STL对抽象性的要求,也不能满足软件积木的要求——函数指针无法和STL其他组件(如配接器adapter)搭配,产生更灵活的变化。同时,函数指针无法保存信息,而仿函数可以

    就实现观点而言,仿函数其实上就是一个“行为类似函数”的对象。为了能够“行为类似函数”,其类别定义中必须自定义(或说改写,重载)function call运算子(operator())。拥有这样的运算子后,我们就可以在仿函数的对象后面加上一对小括号,以此调用仿函数所定义的operator()。如下:

    #include <functional>
    #include <iostream>
    using namespace std;
    
    int main()
    {
        greater<int> ig;
        cout << boolalpha << ig(4, 6);       // 
        cout << greater<int>()(6, 4);

    其中第一种用法比较为大家所熟悉,greater<int>ig的意思是产生一个名为ig的对象,ig(4, 6)则是调用其operator() ,并给予两个参数4,6。第二种用法中的greater<int>() 意思是产生一个临时(无名的)对  象,之后的(6, 4)才是指定两个参数6,5。

    上述第二种语法在一般情况下不常见,但是对仿函数而言,却是主流用法。(STL中的仿函数绝大部分采用这种用法)

    STL 仿函数的分类,若以操作数的个数划分,可分为一元和二元仿函数;若以功能划分,可分为算术运算、关系运算、逻辑运算三大类。任何应用程序欲使用STL内建的仿函数,都必须含入<functional>头文件,SGI则将它们实际定义于<stl_function.h>文件中。

    二、可配接(adaptable)的关键

    STL仿函数应该有能力被函数配接器修饰,彼此像积木一样地串接。为了拥有配接能力,每一个仿函数必须定义自己的相应型别,就像迭代器如果要融入整个STL大家庭,也必须依照规定定义自己的5个相应型别一样。这些相应型别是为了让配接器能够取出,获得仿函数的某些信息(仿函数能够保存信息,函数指针则不能)。相应型别都只是一些typedef,所有必要操作在编译期就全部完成了,对程序的执行效率没有任何影响,不带来任何额外负担。

    仿函数的相应型别主要用来表现函数参数型别和传回值型别。为了方便起见,<stl_function.h>定义了两个classes,分别代表一元仿函数和二元仿函数(STL不支持三元仿函数),其中没有任何data members或member functions,唯有一些型别定义。任何仿函数,只要依个人需求选择继承其中一个class,便自动拥有了那些相应型别,也就自动拥有了配接能力。

    1. unary_function

    unary_function 用来呈现一元函数的参数型别和返回值型别。其定义非常简单:

    // STL 规定,每一个Adaptable Unary Function 都应该继承此类别
    template <class Arg, class Result>
    struct unary_function
    {
        typedef Arg argument_type;
        typedef Result result_type;
    };

    一旦某个仿函数继承了uanry_function,其用户便可以取得该仿函数的参数型别,并以相同手法取得其返回值型别;

    // 以下仿函数继承了uanry_function。
    template <class T>
    struct negate:public uanry_function<T,T>
    {
        T operator() (const T& x) const { return -x; };
    };
    
    // 以下配接器(adapter)用来表示某个仿函数的逻辑负值
    template <class Predicate>
    class unary_negate
        ...
    public:
    // 模板中,需要typename来指明后面的定义是一个类型
    bool operator() (const typename Predicate::argument_type& x) const { .... } };

    2. binary_function

    binary_function用来呈现二元函数的第一参数型别、第二参数型别,以及返回值型别。其定义非常简单:

    // STL规定,每一个Adaptable Binary Function 都应该继承此类别
    template <class Arg1, class Arg2, class Result>
    struct binary_function
    {
        typedef Arg1 first_argument_type;
        typedef Arg2 second_argument_type;
        typedef Result result_type;
    };

    一旦某个仿函数继承了binary_function,其用户便可以这样取得该仿函数的各种型别:

    // 以下仿函数继承了binary_function
    template <class T>
    struct plus : public binary_function<T, T, T>
    {
        T operator() (const T& x, const T& y) const { return x+y; };
    };
    
    // 以下配接器(adapter)用来将某个二元仿函数转化为一元仿函数
    template <class Operation>
    class binder1st
        ....
    protected:
        Operation op;
        typename Operation::first_argument_type value;
    public:
        // 注意,这里的返回值和参数,都需要加上typename,告诉编译器其为一个类型值
        typename Operation::result_type operator() (const typename Operation::second_argument_type& x) const { ... }
    };

    3. 算术类仿函数 参见相关源码;

    4. 关系运算类仿函数 参见相关源码;

    5. 逻辑运算类仿函数 参见相关源码;

    6. 证同、选择、投射 参见相关源码;

  • 相关阅读:
    Linux 修改最大线程数
    Openresty+Nginx+Lua+Nginx_http_upstream_check_module 搭建
    SSDB 性能测试
    面向对象:类的成员
    封装,多态,类的约束,super()深入了解
    面向对象:继承
    面向对象:类的空间问题,类之间关系
    面向对象初识
    软件开发规范
    模块(四)包和logging日志
  • 原文地址:https://www.cnblogs.com/yyxt/p/4986981.html
Copyright © 2011-2022 走看看