转载声明:本文转自网络,稍加整理以备学习和參考之用。

函数对象/仿函数

提到C++ STL。首先被人想到的是它的三大组件:Containers, Iterators, Algorithms,即容器,迭代器和算法。容器为用户提供了经常使用的数据结构,算法大多是独立于容器的经常使用的基本算法,迭代器是由容器提供的一种接口。算法通过迭代器来操控容器。接下来要介绍的是另外的一种组件。函数对象(Function
Object,JJHou译作Functor仿函数)。

什么是函数对象

  顾名思义,函数对象首先是一个对象,即某个类的实例。

其次,函数对象的行为和函数一致,即是说能够像调用函数一样来使用函数对象,如參数传递、返回值等。

这样的行为是通过重载类的()操作符来实现的,

仿函数:用途和适用的场合

  之所以要开发仿函数(functors),是由于函数不能容纳不论什么有意义的状态。

比如。使用函数,你不能为某个元素加一个随意值。再将其应用于一个范围。可是,使用仿函数可轻易做到这一点。

举个栗子。。。



#include <iostream>
#include <algorithm>
using namespace std;
//回调函数
void call_back(char elem)
{
 cout << elem << endl;
}
//仿函数
struct Functor
{
 void operator() (char elem) 
 {
  cout << elem << endl;
 
};
int main()
{
 string strA = "hello";
 string strB = "world";
  
 for_each(strA.begin(),strA.end(),Functor());
 cout<<"===========GAP==============="<<endl;
 for_each(strB.begin(),strB.end(),call_back);
 getchar();
 return 0;
}

h

e

l

l

o

===========GAP===============

w

o

r

l

d

可能会有疑问两者有什么差别?

假如我要for_each遍历的不是字符串而是int类型的vector呢?

是不是又要重写一个int类型作为參数的回调函数,那假设有N种类型的容器遍历岂不是要写N个回调函数或N个仿函数类?

非也!!!

C++有类模板 也有 函数模板 相同能够用于回调

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
//模板函数
template<typename T>
void call_back(T elem)
 cout<< elem <<endl;
}
//仿函数
template<typename T>
class Functor
{
public:
 Functor()
  :m_val(0)
 {
  cout<< "Functor()" <<endl;
 }
 ~Functor()
 {
  cout<<"~Functor()"<<endl;
 }
 void operator() (T elem) 
 {
  Do(elem);
 }
  
 //举个栗子
 void Do(T elem)
 {
  m_val+=elem;
  cout<<elem<<"/"<<m_val<<endl;
 }
private:
 T m_val;
};
  
int main()
{
 vector<int> vec;
 vec.push_back(1);
 vec.push_back(2);
 vec.push_back(3);
 vec.push_back(4);
 vec.push_back(5);
 for_each(vec.begin(),vec.end(),call_back<int>);
 cout<<"===========GAP==============="<<endl; 
 for_each(vec.begin(),vec.end(),Functor<int>());
 return 0;
}

1

2

3

4

5

===========GAP===============

Functor()

1/1

2/3

3/6

4/10

5/15

~Functor()

~Functor()

~Functor()

三次析构的原因:

先附上for_each的源代码(VC2008)

1
2
3
4
5
6
7
8
9
10
11
template<class _InIt,class _Fn1>
inline _Fn1 for_each(_InIt _First, _InIt _Last, _Fn1 _Func)
// perform function for each element
 _DEBUG_RANGE(_First, _Last);
 _DEBUG_POINTER(_Func);
 _CHECKED_BASE_TYPE(_InIt) _ChkFirst(_CHECKED_BASE(_First));
 _CHECKED_BASE_TYPE(_InIt) _ChkLast(_CHECKED_BASE(_Last));
 for (; _ChkFirst != _ChkLast; ++_ChkFirst)
  _Func(*_ChkFirst);
 return (_Func);
}
?
1
2
3
4
5
Functor<int>() 产生暂时对象传參(值) 构造一次,析构一次 
for_each參数值传递,拷贝构造一次,析构一次(函数内部)
for_each返回仿函数的对象(值),拷贝构造一次,析构一次
由于没有重载拷贝构造函数 所以打印出第一次创建暂时对象时的普通构造函数
实际上在这个过程中一共产生过三个仿函数对象

假设把代码改变下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
 
template<typename T>
class Functor
{
public:
 Functor()
  :m_val(0)
 {
  cout<< "Functor()"<<this<<endl;
 }
 Functor(Functor& that)
 {
  this->m_val = that.m_val;
  cout<< "Copy Functor()" <<this<<endl;
 }
 ~Functor()
 {
  cout<<"~Functor()"<<this<<endl;
 }
 void operator() (T elem) 
 {
  Do(elem);
 }
  
 //举个栗子
 void Do(T elem)
 {
  m_val+=elem;
  cout<<elem<<"/"<<m_val<<endl;
 }
 T getVal()
 {
  return m_val;
 }
private:
 T m_val;
};
  
int main()
{
 vector<int> vec;
 vec.push_back(1);
 vec.push_back(2);
 vec.push_back(3);
 vec.push_back(4);
 vec.push_back(5);
 
 Functor<int> func;
 Functor<int>& ref = for_each(vec.begin(),vec.end(),func);
 cout<<ref.getVal()<<endl;
  
 return 0;
}

执行结果

Functor()0032F800           //main函数中的实參仿函数对象

Copy Functor()0032F68C  //值传递 对【实參对象】拷贝构造了形參对象

1/1

2/3

3/6

4/10

5/15

Copy Functor()0032F7E8 //返回对象的值类型  对【形參对象】拷贝构造

~Functor()0032F68C       //析构形參对象

15

~Functor()0032F7E8       //析构返回值对象

~Functor()0032F800      //析构实參对象

如今一目了然了吧!

使用回调函数高效 由上面的样例能够看出 构造1次 拷贝构造2次 析构3次  是有代价的

最后回到仿函数和回调函数

差别在于:

  1. 使用仿函数能够声明在业务相关的类内部 缩小作用域

  2. 使用仿函数能够使用类的成员属性和成员函数

  3. 仿函数是一个类 能够使用面向对象的各种机制(封装
    继承 多态)

  4. 若使用回调函数 那么仅仅能声明为某个类的静态成员函数或全局函数。使用类内部的资源须要用一些手段传參,没有直接使用成员函数便捷

Functor仿函数的更多相关文章

  1. 仿函数(二、stl中常用仿函数)

    提到C++ STL,首先被人想到的是它的三大组件:Containers, Iterators, Algorithms,即容器,迭代器和算法.容器为用户提供了常用的数据结构,算法大多是独立于容器的常用的 ...

  2. js Functor Copy

    原文地址:https://segmentfault.com/a/1190000006051586?utm_source=tuicool&utm_medium=referral 本处仅仅个人存档 ...

  3. Java基础常见英语词汇

    Java基础常见英语词汇(共70个) ['ɔbdʒekt] ['ɔ:rientid]导向的                             ['prəʊɡræmɪŋ]编程 OO: object ...

  4. IT软件开发常用英语词汇

    Aabstract 抽象的abstract base class (ABC)抽象基类abstract class 抽象类abstraction 抽象.抽象物.抽象性access 存取.访问access ...

  5. computer English

    算法常用术语中英对照Data Structures 基本数据结构Dictionaries 字典PriorityQueues 堆Graph Data Structures 图Set Data Struc ...

  6. IT软件开发中常用的英语词汇

    Aabstract 抽象的abstract base class (ABC)抽象基类abstract class 抽象类abstraction 抽象.抽象物.抽象性access 存取.访问access ...

  7. 整理C++面试题for非CS程序猿——更新至【48】

    结合网上的C++面试题+自己的面经,进行整理记录,for我这种非CS的程序猿.(不定期更新,加入了自己的理解,如有不对,请指出) [1] new/delete和malloc/free的区别和联系? 1 ...

  8. 看到了必须要Mark啊,最全的编程中英文词汇对照汇总(里面有好几个版本的,每个版本从a到d的顺序排列)

    java:  第一章: JDK(Java Development Kit) java开发工具包 JVM(Java Virtual Machine) java虚拟机 Javac  编译命令 java   ...

  9. php英语单词大全95

    abstract抽象的 -挨伯丝拽克特 access存取.访问 -挨克色丝 account账户 -厄靠恩特 action动作 -爱克身 activate激活 -爱克特维特 active活动的 -爱克得 ...

随机推荐

  1. Qt Creator键盘快捷键速查

    原地址:http://bbs.qter.org/forum.php?mod=viewthread&tid=904&extra=page%3D2 一般操作的键盘快捷键 操作 快捷键 操作 ...

  2. C# 验证识别基类

    原文C# 验证识别基类 网上找了些代码 自己又改了下 先放出来了 处理简单的验证码足够了 001 using System; 002 using System.Collections.Generic; ...

  3. XML 关键字

    SGML--Standard Generalized Marked Language 标准通用标记语言GML--Generalized Marked Language 通用标记语言XML--Extes ...

  4. perl eval函数

    29.2.32 eval • eval BLOCK • eval EXPR • eval eval 关键字在Perl 里起两种不同的但相关的作用.这些目的是用两种形式的语法 来表现的, eval BL ...

  5. 【LeetCode从零单排】No 3 Longest Substring Without Repeating Characters

    题目 Given a string, find the length of the longest substring without repeating characters. For exampl ...

  6. Oracle性能分析7:创建索引

    在创建索引时,我们往往希望可以预估索引大小,以评估对现有project环境的影响,我们也希望创建索引的过程可以最小化的影响我们正在执行的project环境,并能查看索引的状况. 预估索引大小 预估索引 ...

  7. adb 命令使用的时候出现Error

    在使用adb 命令行的时候会出现Error 重启adb server 即可,使用管理员运行Cmd 命令窗口 adb kill-server adb start-server 如果是连接真机查看的时候再 ...

  8. 解决SVG跨浏览器兼容性问题

    Raphael JS:SVG/VML+JS实现跨浏览器的矢量图形实现方案 http://blog.csdn.net/tiewen/article/details/8535748 SVG那些小事儿 ht ...

  9. Flume 1.5日志收集和存款mongodb安装结构

    Flume该演示是不是说.你可以自己搜索. 但现在的互联网主要是Flume 1.4前版本号的信息.Flume 1.5在轰动的大变化.假设你准备尝试,我在这里给大家介绍一下程序最小化结构,和使用Mong ...

  10. Swift - 数组排序方法(附样例)

    下面通过一个样例演示如何对数组元素进行排序.数组内为自定义用户对象,最终要实现按用户名排序,数据如下: 1 2 3 4 var userList = [UserInfo]() userList.app ...