本文参考文献::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 ;
}
void printTest(char* src)
{
printf(src);
}

结果如下:

再看一遍仿函数的定义:

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

2.1 为什么使用仿函数

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

set<Programmer, ProgrammerIdGreater> set1(programmerArray, programmerArray + );
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[] =
{
Programmer(, L"Scott Meyers"),
Programmer(, L"Martin Fowler"),
Programmer(, L"Bill Gates"),
Programmer(, L"P.J. Plaught"),
Programmer(, L"Stanley B. Lippman"),
Programmer(, L"Andrei Alexandrescu"),
};
//仿函数ProgrammerIdGreater排序,此时重载了小括号
//以序列号进行排序
set<Programmer, ProgrammerIdGreater> set1(programmerArray, programmerArray + );
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(, 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 ;
}
#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 仿函数入门详解的更多相关文章

  1. Linq之旅:Linq入门详解(Linq to Objects)

    示例代码下载:Linq之旅:Linq入门详解(Linq to Objects) 本博文详细介绍 .NET 3.5 中引入的重要功能:Language Integrated Query(LINQ,语言集 ...

  2. STL bind1st bind2nd详解

    STL bind1st bind2nd详解   先不要被吓到,其实这两个配接器很简单.首先,他们都在头文件<functional>中定义.其次,bind就是绑定的意思,而1st就代表fir ...

  3. SQL注入攻防入门详解

    =============安全性篇目录============== 本文转载 毕业开始从事winfrm到今年转到 web ,在码农届已经足足混了快接近3年了,但是对安全方面的知识依旧薄弱,事实上是没机 ...

  4. SQL注入攻防入门详解(2)

    SQL注入攻防入门详解 =============安全性篇目录============== 毕业开始从事winfrm到今年转到 web ,在码农届已经足足混了快接近3年了,但是对安全方面的知识依旧薄弱 ...

  5. Quartz 入门详解

    Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用.Quartz可以用来创建简单或为运行十个,百个, ...

  6. Redis快速入门详解

    Redis入门详解 Redis简介 Redis安装 Redis配置 Redis数据类型 Redis功能 持久化 主从复制 事务支持 发布订阅 管道 虚拟内存 Redis性能 Redis部署 Redis ...

  7. [转]SQL注入攻防入门详解

    原文地址:http://www.cnblogs.com/heyuquan/archive/2012/10/31/2748577.html =============安全性篇目录============ ...

  8. C++仿函数(functor)详解

    C++仿函数(functor)详解 所谓的仿函数(functor),是通过重载()运算符模拟函数形为的类. 因此,这里需要明确两点: 1 仿函数不是函数,它是个类: 2 仿函数重载了()运算符,使得它 ...

  9. [置顶] xamarin android toolbar(踩坑完全入门详解)

    网上关于toolbar的教程有很多,很多新手,在使用toolbar的时候踩坑实在太多了,不好好总结一下,实在浪费.如果你想学习toolbar,你肯定会去去搜索androd toolbar,既然你能看到 ...

随机推荐

  1. VC 常见问题百问

    http://www.cnblogs.com/cy163/archive/2006/06/19/429796.html 经典Vc书 Charles Petzold 的<Programming W ...

  2. NYOJ 552 小数阶乘

    小数阶乘 时间限制:1000 ms  |  内存限制:65535 KB 难度:1 描写叙述 编写一个程序,求一个数m的阶乘. 输入 有多组測试数据,以EOF结束. 每组測试数据有1个整数m. 输出 每 ...

  3. nyoj999 师傅又被妖怪抓走了 (预处理+bfs+状态压缩)

    题目999 题目信息 执行结果 本题排行 讨论区 师傅又被妖怪抓走了 时间限制:1000 ms | 内存限制:65535 KB 难度:3 描写叙述 话说唐僧复得了孙行者,师徒们一心同体,共诣西方.自宝 ...

  4. RMAN冷备份、一致性备份脚本

    RMAN冷备份.一致性备份脚本 run{ shutdown immediate; startup mount; allocate channel c1 type disk; allocate chan ...

  5. DE1-SOC调试linux应用程序

    参考http://www.alterawiki.com/wiki/SoCEDSGettingStarted#Getting_Started_with_Linux_Application_Debuggi ...

  6. PHP Web木马扫描器代码

    <?php header('content-type:text/html;charset=gbk'); set_time_limit(0);//防止超时 /** * * php目录扫描监控增强版 ...

  7. Spring Boot系列二 Spring @Async异步线程池用法总结

    1. TaskExecutor Spring异步线程池的接口类,其实质是java.util.concurrent.Executor Spring 已经实现的异常线程池: 1. SimpleAsyncT ...

  8. 计算机图形学(二)输出图元_3_画线算法_2_DDA算法

    DDA算法        数字微分分析仪(digital differential analyzer, DDA)方法是一种线段扫描转换算法.基于使用等式(3.4)或等式(3.5)计算的&x或& ...

  9. 《Java设计模式》之抽象工厂模式

    场景问题 举个生活中常见的样例--组装电脑.我们在组装电脑的时候.通常须要选择一系列的配件,比方CPU.硬盘.内存.主板.电源.机箱等. 为讨论使用简单点.仅仅考虑选择CPU和主板的问题. 其实,在选 ...

  10. 2015第30周四Java日志组件

    Java 日志 API 从功能上来说,日志 API 本身所需求的功能非常简单,只需要能够记录一段文本即可.API 的使用者在需要进行记录时,根据当前的上下文信息构造出相应的文本信息,调用 API 完成 ...