[GeekBand] STL 仿函数入门详解

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[GeekBand] STL 仿函数入门详解相关的知识,希望对你有一定的参考价值。

本文参考文献::GeekBand课堂内容,授课老师:张文杰

            :C++ Primer 11 中文版(第五版) page 37

                   :网络资料: 叶卡同学的部落格  http://www.leavesite.com/

                                                          

前言:本文主要通过关联容器set解释下仿函数的实现及工作原理。

 

一、STL六大组件简介

1、Containers(容器):各种数据结构,如Vector,List,Deque,Set,Map,用来存放数据
2、Algorithms(算法):如、 Sort,Search。
3、Iterators(迭代器):扮演容器与算法之间的胶合剂,是所谓的“泛型指针”,
4、Functors(仿函数): 行为类似函数,可作为算法的某种策略(Policy
5、Adapters(配接器、适配器):一种用来修饰容器(Containers)或仿函数(Functors)或迭代器(Iterators)接口的东西,
6、Allocators(分配器):负责空间配置与管理

具体的关系如图,以后会更新各个组件的关系,本文重点关注下仿函数相关知识要点

 

                                                 技术分享

 

二、函数指针与仿函数区别

1、函数指针,就是一个指针,它指向一个函数。运用时相当于回调了这个函数。

示例如下:

      这里通过fp指针,指向了PrintTest函数,这里 fp();就是调用 PrintTest函数,打印了Hello World 

      其实仿函数就类似于函数指针的使用。下面会通过例子来说明。

#include "stdafx.h"
typedef void(*Test)(char* s);
void printTest(char* s);
int main(int argc, char* argv[])
{
    Test fp;      //通常是用宏Test来声明一个函数指针fp
    fp = printTest;
    fp("Hello World!\\n");
    return 0;
}
void printTest(char* src)
{
    printf(src);
}

 

结果如下:

     技术分享

 

再看一遍仿函数的定义:

     Functors(仿函数)something that performs a function, 即类似函数的东西,那么它是什么样子的,又是如何实现的呢?

2.1 为什么使用仿函数

使用仿函数可以使迭代和计算分离开来。因而你的functor可以应用于不同场合,在STL的算法中就大量使用了functor

set<Programmer, ProgrammerIdGreater> set1(programmerArray, programmerArray + 6);
    cout << "Traversal And Sort The Programmer by Number " << endl;
    //print
    for_each(set1.begin(), set1.end(), mem_fn(&Programmer::Print));
    cout << "\\n" << endl;

这里通过set容器,通过 <>来进行了自定义的比较操作,这里实际上是通过适配器调用了重载了仿函数()运算符

下一句for_each这句就更明显了!这里通过类中重载()运算符的方法使用了一个函数对象

2.2、仿函数的实现

就是一个类看上去是一个函数,其实现方法就是类中重载了一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了

//仿函数(理解为函数指针),对()进行重载,就是一旦用括号了,括号内的内容已经排序完毕
//我们如果要让仿函数支持适配器,那么就必须从binary_function派生出来。
struct ProgrammerIdGreater : public binary_function<Programmer, Programmer, bool> 
{
    bool operator() (const Programmer& PosFront, const Programmer& PosBehind) const
    {

        //降序排列
        //return (PosFront.GetId() <= PosBehind.GetId()) ? false : true;


        //升序排列
        return (PosFront.GetId() < PosBehind.GetId()) ? true : false;
    }
};


//一元的都继承自unary_function,二元则继承自binary_function, 因为继承自这两个函数的仿函数均定义了相应型别供
//配接时使用,也就具有了配接能力。
/*
template <class Arg, class Result>
struct unary_function {
typedef Arg argument_type;
typedef Result result_type;
};

template <class Arg1, class Arg2, class Result>
struct binary_function {
typedef Arg1 first_argument_type;
typedef Arg2 second_argument_type;
typedef Result result_type;
};

*/

struct ProgrammerNameComparer : public binary_function<Programmer, Programmer, bool>
{
    bool operator() (const Programmer& PosFront, const Programmer& PosBehind) const 
    {
        //按照名称降序排列
        //return (PosFront.GetName() <= PosBehind.GetName()) ? false : true;
        //按照名称升序排列
        return (PosFront.GetName() <= PosBehind.GetName()) ? true : false;
    }
};

 

 

三、以Set容器的完整示例

// Programmer.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "programmer.h" 
#include <iostream>
#include <set>
#include <algorithm>
#include <functional>
using namespace std;
/*
    set的iterator类型一般是const的引用类型,因此当set保存的是类类型时,对iterator解引用无法调用类的非const成员。
    解决办法:1.set中不存储对象本身,改为存储对象指针
    利用const_cast<Programmer&> 进行转型
*/
int main(int argc, char** argv) 
{
    const Programmer programmerArray[6] = 
    {
          Programmer(1, L"Scott Meyers"),
          Programmer(2, L"Martin Fowler"),
          Programmer(3, L"Bill Gates"),
          Programmer(4, L"P.J. Plaught"),
          Programmer(5, L"Stanley B. Lippman"),
          Programmer(6, L"Andrei Alexandrescu"),
    };
    //仿函数ProgrammerIdGreater排序,此时重载了小括号
    //以序列号进行排序
    set<Programmer, ProgrammerIdGreater> set1(programmerArray, programmerArray + 6);
    cout << "Traversal And Sort The Programmer by Number " << endl;
    //print
    for_each(set1.begin(), set1.end(), mem_fn(&Programmer::Print));
    cout << "\\n" << endl;


    //查找目标,并将目标修改为David Vandevoorde
    set<Programmer, ProgrammerIdGreater>::iterator it = set1.find(Programmer(3, L"Bill Gates"));
    if (it != set1.end())
    {
        const_cast<Programmer&>(*it).SetName(L"David Vandevoorde");
    }
    cout << "Change The Name Of Third Element And Traversal" << endl;
    //print
    for_each(set1.begin(), set1.end(), mem_fn(&Programmer::Print));
    cout << "\\n" << endl;
    

    //此时并没有保存到数组programmerArray[6]中
    //按照名称进行排序
    cout << "Traversal And Sort The Programmer  by Name " << endl;
    set<Programmer, ProgrammerNameComparer> set2(set1.begin(), set1.end());
    //print
    for_each(set2.begin(), set2.end(), mem_fn(&Programmer::Print));
    return 0;
}

 

#ifndef __PROGRAMMER_H__
#define __PROGRAMMER_H__

#include <iostream>
#include <string>
using namespace std;

struct Programmer 
{
public:
    //构造函数
    Programmer(const int id, const wstring name)
        : Id(id), Name(name)
    { 

    }
    void Print() const 
    {
        wcout << L"[" << Id << L"]:" << Name << endl;
    }
    const int GetId() const
    {
        return Id; 
    }
    const wstring& GetName() const 
    {
        return Name;
    }
    void SetName(const wstring& name) 
    {
        Name = name;
    }
private:
    int Id;
    wstring Name;
};


//仿函数(理解为函数指针),对()进行重载,就是一旦用括号了,括号内的内容已经排序完毕
//我们如果要让仿函数支持适配器,那么就必须从binary_function派生出来。
struct ProgrammerIdGreater : public binary_function<Programmer, Programmer, bool> 
{
    bool operator() (const Programmer& PosFront, const Programmer& PosBehind) const
    {

        //降序排列
        //return (PosFront.GetId() <= PosBehind.GetId()) ? false : true;


        //升序排列
        return (PosFront.GetId() < PosBehind.GetId()) ? true : false;
    }
};


//一元的都继承自unary_function,二元则继承自binary_function, 因为继承自这两个函数的仿函数均定义了相应型别供
//配接时使用,也就具有了配接能力。
/*
template <class Arg, class Result>
struct unary_function {
typedef Arg argument_type;
typedef Result result_type;
};

template <class Arg1, class Arg2, class Result>
struct binary_function {
typedef Arg1 first_argument_type;
typedef Arg2 second_argument_type;
typedef Result result_type;
};

*/

struct ProgrammerNameComparer : public binary_function<Programmer, Programmer, bool>
{
    bool operator() (const Programmer& PosFront, const Programmer& PosBehind) const 
    {
        //按照名称降序排列
        //return (PosFront.GetName() <= PosBehind.GetName()) ? false : true;
        //按照名称升序排列
        return (PosFront.GetName() <= PosBehind.GetName()) ? true : false;
    }
};

#endif 

 

关于Set容器的使用请参考。非在本文讨论范围内。

       http://blog.csdn.net/lyhvoyage/article/details/22989659

四、总结

仿函数类似函数指针,通过这样对比来记忆。

仿函数要重载operator()

仿函数都有其型别。因此他可以将仿函数的型别当template参数传递

执行速度,仿函数比函数指针更快。

 

以上是关于[GeekBand] STL 仿函数入门详解的主要内容,如果未能解决你的问题,请参考以下文章

STL 源码剖析笔记之仿函数

C++STL之stackqueue的使用和模拟实现+优先级队列(附仿函数)+容器适配器详解

C++入门基础教程:STL函数对象

C++STL之优先级队列详解

C++STL之优先级队列详解

[GeekBand] STL与泛型编程